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
|
// Call the native function with bound args
|
||||||
const result = await fn.fn.call(this, ...nativeArgs)
|
try {
|
||||||
this.stack.push(result)
|
const result = await fn.fn.call(this, ...nativeArgs)
|
||||||
break
|
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')
|
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)])
|
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