forked from defunkt/ReefVM
convert native exceptions to shrimp exceptions
This commit is contained in:
parent
3647159286
commit
b58f848a65
34
src/vm.ts
34
src/vm.ts
|
|
@ -562,9 +562,37 @@ export class VM {
|
|||
}
|
||||
|
||||
// Call the native function with bound args
|
||||
const result = await fn.fn.call(this, ...nativeArgs)
|
||||
this.stack.push(result)
|
||||
break
|
||||
try {
|
||||
const result = await fn.fn.call(this, ...nativeArgs)
|
||||
this.stack.push(result)
|
||||
break
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorValue = toValue(errorMessage)
|
||||
|
||||
// no exception handlers, let it crash
|
||||
if (this.exceptionHandlers.length === 0) {
|
||||
throw new Error(`Uncaught exception in native function: ${errorMessage}`)
|
||||
}
|
||||
|
||||
// use existing THROW logic
|
||||
const throwHandler = this.exceptionHandlers.pop()!
|
||||
|
||||
while (this.callStack.length > throwHandler.callStackDepth)
|
||||
this.callStack.pop()
|
||||
|
||||
this.scope = throwHandler.scope
|
||||
this.stack.push(errorValue)
|
||||
|
||||
// Jump to `finally` if present, otherwise jump to `catch`
|
||||
const targetAddress = throwHandler.finallyAddress !== undefined
|
||||
? throwHandler.finallyAddress
|
||||
: throwHandler.catchAddress
|
||||
|
||||
// subtract 1 because pc will be incremented
|
||||
this.pc = targetAddress - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (fn.type !== 'function')
|
||||
|
|
|
|||
|
|
@ -2201,4 +2201,163 @@ test("raw flag - raw function can return any Value type", async () => {
|
|||
expect(numbers.value).toEqual([toValue(1), toValue(2), toValue(3)])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('native function error caught by try/catch - direct call', async () => {
|
||||
const bytecode = toBytecode(`
|
||||
PUSH_TRY .catch
|
||||
LOAD failing_fn
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
POP_TRY
|
||||
JUMP .end
|
||||
|
||||
.catch:
|
||||
STORE err
|
||||
PUSH 'caught: '
|
||||
LOAD err
|
||||
STR_CONCAT #2
|
||||
JUMP .end
|
||||
|
||||
.end:
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
vm.set('failing_fn', () => {
|
||||
throw new Error('native error')
|
||||
})
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'string', value: 'caught: native error' })
|
||||
})
|
||||
|
||||
test('native function error caught by try/catch - inside reef function', async () => {
|
||||
const bytecode = toBytecode(`
|
||||
MAKE_FUNCTION () .try_body
|
||||
STORE call_native
|
||||
JUMP .after_try_body
|
||||
|
||||
.try_body:
|
||||
LOAD failing_fn
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
RETURN
|
||||
|
||||
.after_try_body:
|
||||
|
||||
PUSH_TRY .catch
|
||||
LOAD call_native
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
POP_TRY
|
||||
JUMP .end
|
||||
|
||||
.catch:
|
||||
STORE err
|
||||
PUSH 'caught: '
|
||||
LOAD err
|
||||
STR_CONCAT #2
|
||||
JUMP .end
|
||||
|
||||
.end:
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
vm.set('failing_fn', () => {
|
||||
throw new Error('native error')
|
||||
})
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'string', value: 'caught: native error' })
|
||||
})
|
||||
|
||||
test('async native function error caught by try/catch', async () => {
|
||||
const bytecode = toBytecode(`
|
||||
PUSH_TRY .catch
|
||||
LOAD async_fail
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
POP_TRY
|
||||
JUMP .end
|
||||
|
||||
.catch:
|
||||
STORE err
|
||||
PUSH 'async caught: '
|
||||
LOAD err
|
||||
STR_CONCAT #2
|
||||
JUMP .end
|
||||
|
||||
.end:
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
vm.set('async_fail', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1))
|
||||
throw new Error('async error')
|
||||
})
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'string', value: 'async caught: async error' })
|
||||
})
|
||||
|
||||
test('native function error with finally block', async () => {
|
||||
const bytecode = toBytecode(`
|
||||
PUSH 0
|
||||
STORE cleanup_count
|
||||
|
||||
PUSH_TRY .catch
|
||||
PUSH_FINALLY .finally
|
||||
LOAD failing_fn
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
POP_TRY
|
||||
JUMP .finally
|
||||
|
||||
.catch:
|
||||
STORE err
|
||||
PUSH 'error handled'
|
||||
JUMP .finally
|
||||
|
||||
.finally:
|
||||
LOAD cleanup_count
|
||||
PUSH 1
|
||||
ADD
|
||||
STORE cleanup_count
|
||||
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
vm.set('failing_fn', () => {
|
||||
throw new Error('native error')
|
||||
})
|
||||
|
||||
await vm.run()
|
||||
const cleanupCount = vm.scope.get('cleanup_count')
|
||||
expect(cleanupCount).toEqual({ type: 'number', value: 1 })
|
||||
})
|
||||
|
||||
test('uncaught native function error crashes VM', async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD failing_fn
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
vm.set('failing_fn', () => {
|
||||
throw new Error('uncaught error')
|
||||
})
|
||||
|
||||
await expect(vm.run()).rejects.toThrow('Uncaught exception in native function: uncaught error')
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user