diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index f6b8461d..d4d2678f 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -375,6 +375,14 @@ pub(crate) struct InlineSyncState { pub(crate) spill_stack: Vec, pub(crate) cell_stack: Vec>, pub(crate) inline_callee: Option, + /// 三个内嵌 dispatch 调度标志(`Vm::generator_dispatch` / `async_dispatch` / + /// `construct_dispatch`)的属主快照。save 时记录外层值并清零 VM 侧、restore + /// 时写回:嵌套 state-swap 调用不得继承外层调度上下文,否则其内部构造帧 + /// 弹出后 frames 清空,`do_return` 会把嵌套调用误判为属主内嵌 dispatch 提前 + /// 交付,跳过被调函数剩余字节码(详见 `do_return` 交付条件注释)。 + pub(crate) generator_dispatch: bool, + pub(crate) async_dispatch: bool, + pub(crate) construct_dispatch: bool, /// inline 目标函数的严格模式标志(内联执行期间写路径的 strict/sloppy 判定 /// 来源;嵌套内联时随本快照保存/恢复)。 pub(crate) inline_strict: bool, @@ -504,6 +512,8 @@ pub struct Vm { pub(crate) delegated_iterator: Option, /// 当前是否处于生成器内嵌 dispatch 循环:生成器帧弹出且 frames 清空时, /// `do_return` 据此把结果交付给恢复方(而非当作普通顶层返回继续执行)。 + /// 作用域限定于置位处包裹的那次 `dispatch()`:经 `InlineSyncState` 在 + /// state-swap 边界保存/清零/恢复,嵌套内联调用不继承本标志。 pub(crate) generator_dispatch: bool, /// 生成器调用时参数初始化步:body 起点标记(SUSPEND_BODY)据此判定"挂起在 body 前"。 pub(crate) generator_init_step: bool, @@ -516,11 +526,13 @@ pub struct Vm { /// 循环)取走并快照挂起状态。false = 正常返回/异常。 pub(crate) async_suspended: bool, /// 当前是否处于异步函数内嵌 dispatch 循环:异步帧弹出且 frames 清空时, - /// `do_return` 据此把结果交付给恢复方(与 generator_dispatch 同语义)。 + /// `do_return` 据此把结果交付给恢复方(与 generator_dispatch 同语义, + /// 同样经 InlineSyncState 限定作用域于属主 dispatch)。 pub(crate) async_dispatch: bool, /// 当前是否处于构造器内嵌 dispatch 循环(`call_constructor_bytecode_inline`): /// 构造帧弹出且 frames 清空时,`do_return` 据此把构造结果(regs[0], - /// 已做非对象回退 this)交付给恢复方(与 generator_dispatch 同语义)。 + /// 已做非对象回退 this)交付给恢复方(与 generator_dispatch 同语义, + /// 同样经 InlineSyncState 限定作用域于属主 dispatch)。 pub(crate) construct_dispatch: bool, /// 当前正在执行的异步生成器上下文对象(`OBJ_TYPE_ASYNC_GENERATOR`,持有 /// `AsyncGeneratorState` 快照)。AWAIT dispatch 据此登记异步生成器恢复反应; diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 07feabcd..434da12e 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -588,6 +588,9 @@ impl Vm { } } // 生成器/异步/构造内嵌 dispatch:帧全部弹出后把结果交付恢复方,而非继续执行。 + // 三个调度标志经 InlineSyncState 在 state-swap 边界清零(嵌套内联调用不继承 + // 外层调度上下文),此处为真当且仅当本 dispatch 循环即标志的属主——嵌套 + // 调用内的构造帧弹出只会清帧、不触发交付,被调函数剩余字节码照常执行。 if self.frames.is_empty() && (self.generator_dispatch || self.async_dispatch || self.construct_dispatch) { // 构造内嵌:弹帧已把构造结果(非对象回退 this)写入 regs[0],直接交付。 let delivered = if self.construct_dispatch { self.regs[0] } else { result }; diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 50591aab..e404f264 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -100,6 +100,21 @@ macro_rules! inline_save_field { ($recv:ident, $window_regs:ident, inline_callee, opt_copy) => { $recv.inline_callee }; + ($recv:ident, $window_regs:ident, generator_dispatch, flag_zero) => {{ + let prev = $recv.generator_dispatch; + $recv.generator_dispatch = false; + prev + }}; + ($recv:ident, $window_regs:ident, async_dispatch, flag_zero) => {{ + let prev = $recv.async_dispatch; + $recv.async_dispatch = false; + prev + }}; + ($recv:ident, $window_regs:ident, construct_dispatch, flag_zero) => {{ + let prev = $recv.construct_dispatch; + $recv.construct_dispatch = false; + prev + }}; ($recv:ident, $window_regs:ident, inline_strict, copy) => { $recv.inline_strict }; @@ -189,6 +204,15 @@ macro_rules! inline_restore_field { ($recv:ident, $saved:ident, inline_callee, opt_copy) => { $recv.inline_callee = $saved.inline_callee }; + ($recv:ident, $saved:ident, generator_dispatch, flag_zero) => { + $recv.generator_dispatch = $saved.generator_dispatch + }; + ($recv:ident, $saved:ident, async_dispatch, flag_zero) => { + $recv.async_dispatch = $saved.async_dispatch + }; + ($recv:ident, $saved:ident, construct_dispatch, flag_zero) => { + $recv.construct_dispatch = $saved.construct_dispatch + }; ($recv:ident, $saved:ident, inline_strict, copy) => { $recv.inline_strict = $saved.inline_strict }; @@ -240,6 +264,9 @@ macro_rules! inline_core_fields { (spill_stack, move_field), // V M (cell_stack, move_field), // V M (inline_callee, opt_copy), // V M + (generator_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 + (async_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 + (construct_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 (inline_strict, copy), // M (inline_frames_base, copy), // M (inline_args_base, copy), // M diff --git a/crates/oxide_vm/tests/inline_dispatch_scoping_tests.rs b/crates/oxide_vm/tests/inline_dispatch_scoping_tests.rs new file mode 100644 index 00000000..742aa8a0 --- /dev/null +++ b/crates/oxide_vm/tests/inline_dispatch_scoping_tests.rs @@ -0,0 +1,75 @@ +//! 内嵌 dispatch 调度标志作用域测试:`do_return` 的"属主内嵌 dispatch 交付"条件 +//! (frames 清空且 generator/async/construct 调度标志为真)只应终止标志的属主 +//! dispatch。state-swap 边界(C 侧 builtin 经 `ordinary_get` 无帧调用 accessor +//! 等嵌套内联 dispatch)不得继承外层调度上下文——否则被调 getter 内 +//! `new 字节码构造器()` 的构造帧是帧栈唯一帧,其弹出后 frames 清空,嵌套 +//! dispatch 被误判为属主而提前交付,getter 构造之后的字节码(throw、后续语句) +//! 被整体跳过,C 侧读到构造残留值而非本应传播的异常。 +//! 覆盖生成器体内 C 侧路径触发的构造后 throw 与取值两种形态;帧边界 +//! (字节码 GET_PROP 压 getter 帧)形态与循环失控形态(test262 族)另行回测。 + +use oxide_compiler::compiler::Compiler; +use oxide_vm::vm::Vm; + +fn eval_str(source: &str) -> String { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse"); + let module = Compiler::new().compile(&program).expect("compile"); + let mut vm = Vm::new(); + let result = vm.run(&module).expect("run"); + vm.lookup_str(result).unwrap_or_else(|| format!("{result}")) +} + +// ── 生成器体内 Object.assign(C 侧 ordinary_get 无帧调 getter): +// getter 构造后再 throw,异常须穿透 next()(钉的是传播本身;builtin 侧 +// 错误消息保真是独立缺口,不在本钉断言内) ── +#[test] +fn c_side_accessor_construct_then_throw_in_generator_propagates() { + let s = eval_str( + "function E() { this.tag = 1; }\n\ + var o = { get v() { var e = new E(); throw new Error('boom'); } };\n\ + function* f() { Object.assign({}, o); }\n\ + var ok = false;\n\ + try { f().next(); } catch (e) { ok = e instanceof Error; }\n\ + ok", + ); + assert_eq!(s, "true", "C 侧 accessor 构造后 throw 应穿透生成器 next(),实际 {s:?}"); +} + +// ── 同场景取值形态:getter 构造后 return 值须原样交付(而非构造残留) ── +#[test] +fn c_side_accessor_construct_then_return_in_generator_delivers_value() { + let s = eval_str( + "function E() { this.tag = 1; }\n\ + var o = { get v() { var e = new E(); return 42; } };\n\ + function* f() { return Object.assign({}, o).v; }\n\ + f().next().value", + ); + assert_eq!(s, "42", "C 侧 accessor 构造后的 return 值应原样交付,实际 {s:?}"); +} + +// ── 帧边界对照:字节码 GET_PROP 压 getter 帧执行(非 state-swap), +// 构造后 throw 本就穿透(防过度修正改变帧路径行为) ── +#[test] +fn frame_bound_accessor_construct_then_throw_in_generator_propagates() { + let s = eval_str( + "function E() { this.tag = 1; }\n\ + var o = { get v() { var e = new E(); throw new Error('boom'); } };\n\ + function* f() { return o.v; }\n\ + var caught = '';\n\ + try { f().next(); } catch (e) { caught = e.message; }\n\ + caught", + ); + assert_eq!(s, "boom", "帧边界 getter 构造后 throw 应穿透生成器 next(),实际 {s:?}"); +} + +// ── 顶层对照:无调度标志时 C 侧 accessor 构造后 return 本就正常 ── +#[test] +fn c_side_accessor_construct_then_return_top_level_delivers_value() { + let s = eval_str( + "function E() { this.tag = 1; }\n\ + var o = { get v() { var e = new E(); return 42; } };\n\ + Object.assign({}, o).v", + ); + assert_eq!(s, "42", "顶层 C 侧 accessor 构造后 return 值应原样交付,实际 {s:?}"); +}