Compare commits
No commits in common. "b58f848a65443780e87bd561c7b91ba2fb0c1d7a" and "030eb7487165b3ba502965a8b7fa09c4b5fdb0da" have entirely different histories.
b58f848a65
...
030eb74871
36
src/vm.ts
36
src/vm.ts
|
|
@ -5,7 +5,7 @@ import { OpCode } from "./opcode"
|
||||||
import { Scope } from "./scope"
|
import { Scope } from "./scope"
|
||||||
import type { Value, NativeFunction, TypeScriptFunction } from "./value"
|
import type { Value, NativeFunction, TypeScriptFunction } from "./value"
|
||||||
import { toValue, toNumber, isTrue, isEqual, toString, fromValue, fnFromValue } from "./value"
|
import { toValue, toNumber, isTrue, isEqual, toString, fromValue, fnFromValue } from "./value"
|
||||||
import { extractParamInfo, getOriginalFunction } from "./function"
|
import { extractParamInfo, wrapNative, isWrapped, getOriginalFunction } from "./function"
|
||||||
|
|
||||||
export class VM {
|
export class VM {
|
||||||
pc = 0
|
pc = 0
|
||||||
|
|
@ -562,37 +562,9 @@ export class VM {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the native function with bound args
|
// Call the native function with bound args
|
||||||
try {
|
const result = await fn.fn.call(this, ...nativeArgs)
|
||||||
const result = await fn.fn.call(this, ...nativeArgs)
|
this.stack.push(result)
|
||||||
this.stack.push(result)
|
break
|
||||||
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,163 +2201,4 @@ 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