diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index 67590f69..f63b9075 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -79,6 +79,7 @@ impl Vm { let overflow_base = self.spill_stack.len(); let saved_overflow_base = self.native_overflow_base; let saved_overflow_count = self.native_overflow_count; + let saved_pack_end = self.native_pack_end; if args.len() > Self::SYNC_NATIVE_ARG_LIMIT { self.spill_stack.extend_from_slice(&args[Self::SYNC_NATIVE_ARG_LIMIT..]); self.native_overflow_base = overflow_base; @@ -94,6 +95,9 @@ impl Vm { let saved_r254 = self.regs[254]; let pack_limit = args.len().min(Self::SYNC_NATIVE_ARG_LIMIT); let arg_regs = self.pack_sync_native_call_args(receiver, callee, &args[..pack_limit]); + // 实参区在飞期间生效:native 体内一切恢复边界的镜像重载据此跳过 + // regs[0..pack_limit),防止外层 builtin 尚未读取的实参被覆写。 + self.native_pack_end = pack_limit; // SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置; // native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。 let func: NativeFn = unsafe { native_fn_ptr_to_fn(native_fn) }; @@ -106,6 +110,7 @@ impl Vm { } self.native_overflow_base = saved_overflow_base; self.native_overflow_count = saved_overflow_count; + self.native_pack_end = saved_pack_end; // 窗口回拷 + regs[253]/[254] 单回,缓冲归还池复用。 self.regs[..window].copy_from_slice(&saved_window); self.regs[253] = saved_r253; @@ -304,7 +309,7 @@ impl Vm { self.bytecode = sub_bytecode; self.activate_immutables(gen, sub_idx, &sub.constants); self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); - self.reload_builtin_mirror_slots(&sub.builtin_reg_map); + self.reload_builtin_mirror_slots(&sub.builtin_reg_map, 0); self.active_reg_limit = sub_n_registers.max(1); self.pc = 0; diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 758fb671..7cffc3ba 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -268,6 +268,12 @@ pub struct Vm { /// `restore_inline_state` 归还。热回调循环内 save/restore 反复使用同一块 /// 缓冲,只在嵌套(池已被外层取走)时新分配。 pub(crate) inline_reg_pool: Option>, + /// 外层 native pack 实参区上界(独占):`call_function_sync` native 分支在飞 + /// 期间实参已 pack 进 `regs[0..pack_end)`,恢复边界(`restore_inline_state` / + /// `restore_frame`)的镜像重载须跳过该域——重载目标恰是外层 builtin 尚未 + /// 读取的实参寄存器。0 = 无 pack 上下文;跨嵌套 native 调用存/还原 + /// (与 overflow 描述符同构)。 + pub(crate) native_pack_end: usize, /// 生成器体 `dispatch()` 让出时的信号:YIELD 置 Some(让出值),恢复方( /// generator 内嵌 dispatch 循环)取走并判定挂起。None = 正常返回/异常。 pub(crate) generator_suspended: Option, diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 27412706..f082539a 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -271,10 +271,11 @@ impl Vm { } /// 重载活动帧模块(`active_flat_id`)的 builtin 镜像槽:先按值取出名集再 - /// 写寄存器,避免借用交叉。帧恢复 / 重执行边界调用。 - pub(crate) fn reload_active_module_mirror_slots(&mut self) { + /// 写寄存器,避免借用交叉。帧恢复 / 重执行边界调用;`pack_end` 为外层 + /// native pack 实参区上界(独占),落域镜像槽跳过。 + pub(crate) fn reload_active_module_mirror_slots(&mut self, pack_end: usize) { let map = self.active_module().map(|m| m.builtin_reg_map.clone()).unwrap_or_default(); - self.reload_builtin_mirror_slots(&map); + self.reload_builtin_mirror_slots(&map, pack_end); } /// 从全局对象 A 侧重载模块 builtin 名集的镜像槽:属性在位取原始存储值, @@ -283,14 +284,19 @@ impl Vm { /// # 边界与前提 /// - 缺位臂语义为"写 undefined",各入口(run / 帧 / inline / 恢复)统一, /// 使成员形删除后的裸读与入口预载看到同一值。 + /// - `pack_end` 为 native pack 实参区上界(独占):落 [0, pack_end) 的镜像 + /// 槽跳过,实参值只存在于寄存器、无刷新源,窗口拷回负责其还原。 /// # 副作用 /// - 只写镜像槽寄存器(槽下标受编译期登记约束)。 - pub(crate) fn reload_builtin_mirror_slots(&mut self, map: &[(String, u32)]) { + pub(crate) fn reload_builtin_mirror_slots(&mut self, map: &[(String, u32)], pack_end: usize) { if map.is_empty() { return; } let global = self.session.global_object(); for (name, reg) in map { + if (*reg as usize) < pack_end { + continue; + } let si = self.kernel_core.perm_interner().intern(name.as_str()).0; let val = self .kernel_core diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 49f0b1b7..c9de739f 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -332,8 +332,9 @@ impl Vm { let _window_regs: Vec = Vec::new(); inline_core_fields!(vm, saved, inline_restore, _window_regs); // 窗口回拷已复活调用方陈旧镜像槽:重载调用方模块的 builtin 名集 - // (active_flat_id 已随宏回写还原到调用方)。 - vm.reload_active_module_mirror_slots(); + // (active_flat_id 已随宏回写还原到调用方);外层 native pack 实参区 + // 在飞时跳过该域,实参值由窗口拷回还原、不在此刷新。 + vm.reload_active_module_mirror_slots(vm.native_pack_end); } /// 首次执行的 VM 就绪:清空执行核心(regs/pc/bytecode/各栈段/迭代器/内联态), @@ -486,7 +487,7 @@ impl Vm { receiver }; self.regs[255] = JsValue::undefined(); - self.reload_builtin_mirror_slots(&sub.builtin_reg_map); + self.reload_builtin_mirror_slots(&sub.builtin_reg_map, 0); let _ = callee; vm_trace!( @@ -556,8 +557,9 @@ impl Vm { self.active_reg_limit = frame.caller_active_reg_limit; self.pc = frame.return_addr; // 窗口回拷已复活调用方陈旧镜像槽:重载调用方模块的 builtin 名集 - // (active_flat_id 已随保存栈弹回还原到调用方)。 - self.reload_active_module_mirror_slots(); + // (active_flat_id 已随保存栈弹回还原到调用方);外层 native pack + // 实参区在飞时跳过该域,实参值由窗口拷回还原、不在此刷新。 + self.reload_active_module_mirror_slots(self.native_pack_end); } /// 重新执行当前已加载的 bytecode:清空执行状态并重置 IC 缓存后再次 dispatch。 @@ -572,7 +574,7 @@ impl Vm { self.active_reg_limit = self.root_reg_limit; self.regs[254] = self.top_level_this; // 寄存器文件清空后镜像槽须重载(与 run 入口同语义),否则裸读回陈旧值。 - self.reload_active_module_mirror_slots(); + self.reload_active_module_mirror_slots(0); crate::ic_helper::clear_ic_caches(self.bytecode_mut()); self.dispatch() } @@ -644,7 +646,7 @@ impl Vm { self.root_reg_limit = module.n_registers.max(1); self.active_reg_limit = self.root_reg_limit; - self.reload_builtin_mirror_slots(&module.builtin_reg_map); + self.reload_builtin_mirror_slots(&module.builtin_reg_map, 0); // 顶层 this:脚本为全局对象(ECMA-262 全局执行上下文); // ES module 顶层环境 GetThisBinding 返回 undefined。记录到 diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 5266be2c..e8825cd8 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -104,6 +104,7 @@ impl Vm { top_level_strict: false, top_level_this: JsValue::undefined(), inline_reg_pool: None, + native_pack_end: 0, generator_suspended: None, delegated_iterator: None, generator_dispatch: false, @@ -234,6 +235,7 @@ impl Vm { top_level_strict: false, top_level_this: JsValue::undefined(), inline_reg_pool: None, + native_pack_end: 0, generator_suspended: None, delegated_iterator: None, generator_dispatch: false, diff --git a/crates/oxide_vm/tests/native_pack_window_tests.rs b/crates/oxide_vm/tests/native_pack_window_tests.rs new file mode 100644 index 00000000..17dd667d --- /dev/null +++ b/crates/oxide_vm/tests/native_pack_window_tests.rs @@ -0,0 +1,270 @@ +//! native pack 实参区 × 恢复边界镜像重载的引擎钉:native pack 协议在飞 +//! (`.call`/`.apply` 转发、直接成员调用)期间,pack 与实参读取之间若存在用户 +//! 内联窗(length getter / 回调),窗口拷回后的调用方模块镜像重载会把顶层 +//! 镜像名集(Array/Object 着落于 pack 区寄存器)重写回寄存器,覆写外层 +//! builtin 尚未读取的实参值。期望值一律取 node v20.19.2 实测值。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn compile(source: &str) -> oxide_bytecode::module::CompiledModule { + let allocator = Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse"); + Compiler::new().compile(&program).expect("compile") +} + +/// 运行脚本并取最终值(钉的期望值统一为字符串形态)。 +fn eval_str(source: &str) -> String { + let module = compile(source); + let mut vm = Vm::new(); + let result = vm.run(&Arc::new(module)).expect("run"); + vm.lookup_str(result).expect("结果应为字符串").to_string() +} + +// 共用的 length getter 装置:基元/对象 this 的 length 走用户 accessor, +// accessor 内联窗在 pack 与实参读取之间打开。 +const LENGTH_GETTER: &str = "var savedLen = 1; \ + Object.defineProperty(Number.prototype, 'length', { \ + configurable: true, \ + get: function () { return savedLen; }, \ + set: function (v) { savedLen = v; } }); \ + "; + +/// 在 body 脚本前拼入 length getter 装置,返回完整脚本。 +fn with_length_getter(body: &str) -> String { + format!("{LENGTH_GETTER}{body}") +} + +/// `.call` 转发:splice 的 deleteCount 落入 reg 1(顶层镜像着色域), +/// length getter 内联窗先于实参读取,窗口拷回后的镜像重载不得覆写实参区。 +#[test] +fn splice_call_length_getter_keeps_delete_count() { + assert_eq!( + eval_str(&with_length_getter( + "var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \ + delete Number.prototype.length; \ + [r.length, savedLen].join('|')" + )), + "1|2" + ); +} + +/// 对象 this 同形态:装箱非触发条件,覆写对对象接收者同样成立。 +#[test] +fn splice_object_this_length_getter_keeps_delete_count() { + assert_eq!( + eval_str( + "var savedLen = 1; \ + var o = {}; \ + Object.defineProperty(o, 'length', { \ + configurable: true, \ + get: function () { return savedLen; }, \ + set: function (v) { savedLen = v; } }); \ + var r = Array.prototype.splice.call(o, 0, 1, 'a', 'b'); \ + [r.length, savedLen].join('|')" + ), + "1|2" + ); +} + +/// 最小 getter 体(无副作用):覆写与 getter 复杂度无关,是最小判别形。 +#[test] +fn splice_minimal_getter_keeps_delete_count() { + assert_eq!( + eval_str( + "var savedLen = 1; \ + Object.defineProperty(Number.prototype, 'length', { \ + configurable: true, \ + get: function () { return 1; }, \ + set: function (v) { savedLen = v; } }); \ + var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \ + delete Number.prototype.length; \ + [r.length, savedLen].join('|')" + ), + "1|2" + ); +} + +/// 2 实参 pack 区 [0, 2) 恰好覆盖第一个镜像寄存器:deleteCount 读值不得失真。 +#[test] +fn splice_two_arg_pack_keeps_delete_count() { + assert_eq!( + eval_str(&with_length_getter( + "var r = Array.prototype.splice.call(5, 0, 1); \ + delete Number.prototype.length; \ + [r.length, savedLen].join('|')" + )), + "1|0" + ); +} + +/// 大 getter 体(字符串 churn、寄存器需求上升):覆写与窗宽无关,窗宽充分。 +#[test] +fn splice_churn_getter_keeps_delete_count() { + assert_eq!( + eval_str( + "var savedLen = 1; \ + Object.defineProperty(Number.prototype, 'length', { \ + configurable: true, \ + get: function () { \ + var s = ''; \ + for (var i = 0; i < 200; i++) { s = s + 'churn' + i; } \ + globalThis.__churn = s; \ + return savedLen; \ + }, \ + set: function (v) { savedLen = v; } }); \ + var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \ + delete Number.prototype.length; \ + [r.length, savedLen].join('|')" + ), + "1|2" + ); +} + +/// fill 的 start/end 落 pack 区 reg 1/2:区间两端读值不得折算失真, +/// 填充范围与未填充位保持 node 语义(length getter 开窗)。 +#[test] +fn fill_arraylike_length_getter_keeps_range() { + assert_eq!( + eval_str( + "var o = {}; \ + Object.defineProperty(o, 'length', { \ + configurable: true, \ + get: function () { return 2; }, \ + set: function (v) {} }); \ + var r = Array.prototype.fill.call(o, 'v', 0, 1); \ + [r[0] === 'v' ? 1 : 0, r[1] === undefined ? 1 : 0].join('|')" + ), + "1|1" + ); +} + +/// 5 实参 pack 区 [0, 5) 全量覆盖镜像着色集:全部实参存活,结果与插入区 +/// 逐位保持。 +#[test] +fn splice_five_arg_pack_keeps_all_args() { + assert_eq!( + eval_str(&with_length_getter( + "var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b', 'c'); \ + delete Number.prototype.length; \ + [r.length, r[0], r[1], r[2], savedLen].join('|')" + )), + "1||||3" + ); +} + +/// getter 自读镜像(`Array === Array`):内联入口重载自愈,修后保真—— +/// 入口重载语义不动,恢复边界 gate 不影响入口侧镜像可见性。 +#[test] +fn splice_getter_mirror_self_read_stays_true() { + assert_eq!( + eval_str( + "var savedLen = 1; \ + Object.defineProperty(Number.prototype, 'length', { \ + configurable: true, \ + get: function () { return Array === Array && Object === Object ? 1 : 0; }, \ + set: function (v) { savedLen = v; } }); \ + var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \ + delete Number.prototype.length; \ + [r.length, savedLen].join('|')" + ), + "1|2" + ); +} + +/// getter 内嵌套用户 CALL(帧路径):帧返回后的恢复边界同门控,帧路径 +/// 结果与无嵌套时逐位一致。 +#[test] +fn splice_getter_nested_call_keeps_result() { + assert_eq!( + eval_str( + "var savedLen = 1; \ + function probe() { return 42; } \ + Object.defineProperty(Number.prototype, 'length', { \ + configurable: true, \ + get: function () { var x = probe(); return savedLen; }, \ + set: function (v) { savedLen = v; } }); \ + var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \ + delete Number.prototype.length; \ + [r.length, savedLen].join('|')" + ), + "1|2" + ); +} + +/// indexOf 的 fromIndex 落 pack 区 reg 1:arraylike 接收者 length getter +/// 开窗后 fromIndex 读值不得折算为 0。 +#[test] +fn index_of_arraylike_length_getter_keeps_from_index() { + assert_eq!( + eval_str( + "var savedLen = 3; \ + var a = { 0: 10, 1: 20, 2: 10 }; \ + Object.defineProperty(a, 'length', { \ + configurable: true, \ + get: function () { return savedLen; }, \ + set: function (v) { savedLen = v; } }); \ + String(Array.prototype.indexOf.call(a, 10, 1))" + ), + "2" + ); +} + +/// reduce 的 init 落 pack 区 reg 2:arraylike length getter 开窗后 +/// init 读值不得被镜像值(Object 内置函数)替换。 +#[test] +fn reduce_arraylike_length_getter_keeps_init() { + assert_eq!( + eval_str( + "var savedLen = 1; \ + var a = { 0: 12 }; \ + Object.defineProperty(a, 'length', { \ + configurable: true, \ + get: function () { return savedLen; }, \ + set: function (v) { savedLen = v; } }); \ + String(Array.prototype.reduce.call(a, function (x, y) { return x + y; }, 1))" + ), + "13" + ); +} + +/// `.apply` 转发同形态:TailCall 链 pack_end 嵌套存还后实参区保持。 +#[test] +fn splice_apply_forward_keeps_delete_count() { + assert_eq!( + eval_str(&with_length_getter( + "var r = Array.prototype.splice.apply(5, [0, 1, 'a', 'b']); \ + delete Number.prototype.length; \ + [r.length, savedLen].join('|')" + )), + "1|2" + ); +} + +/// 无用户内联窗回归:无窗时 pack 区无覆写风险点,结果保持 node 语义。 +#[test] +fn splice_no_window_result_unchanged() { + assert_eq!( + eval_str( + "var r = Array.prototype.splice.call(5, 0, 1, 'a', 'b'); \ + [r.length, r[0], r[1]].join('|')" + ), + "0||" + ); +} + +/// 1 实参 pack 区 [0, 1) 不与镜像着色集求交:reg 0 恒为结果槽,此形无风险。 +#[test] +fn splice_one_arg_pack_stays_safe() { + assert_eq!( + eval_str(&with_length_getter( + "var r = Array.prototype.splice.call(5, 0); \ + delete Number.prototype.length; \ + [r.length, savedLen].join('|')" + )), + "1|0" + ); +}