diff --git a/Cargo.lock b/Cargo.lock index d1f5c8de..e79b58dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.105" +version = "0.8.106" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.105" +version = "0.8.106" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.105" +version = "0.8.106" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.105" +version = "0.8.106" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.105" +version = "0.8.106" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.105" +version = "0.8.106" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.105" +version = "0.8.106" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.105" +version = "0.8.106" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.105" +version = "0.8.106" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.105" +version = "0.8.106" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.105" +version = "0.8.106" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index c17df2b0..bf7b7e28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.8.105" +version = "0.8.106" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/array/common.rs b/crates/oxide_builtins/src/array/common.rs index f4c41571..963a304f 100644 --- a/crates/oxide_builtins/src/array/common.rs +++ b/crates/oxide_builtins/src/array/common.rs @@ -187,10 +187,12 @@ pub(crate) fn unit_string_value(vm: &mut H, unit: u16) -> JsValue { /// 索引位置存在性判定(规范 HasProperty:自身与原型链任一层命中即存在, /// 数组元素区 hole 视同缺失,TypedArray 整数索引按视图长度判在界)。 +/// 数组/类数组目标非 deferred namespace,触发恒 no-op。 pub(crate) fn arraylike_index_present(vm: &mut H, ptr: *mut JsObject, i: usize) -> bool { // SAFETY: ptr 来自 this/参数对象,指向存活对象。 let key_si = vm.string_key_si(&i.to_string()); vm.has_property(unsafe { &*ptr }, key_si) + .unwrap_or(false) } /// 元素读取失败时直接把异常作为 NativeResult::Err 返回。 diff --git a/crates/oxide_builtins/src/array/element.rs b/crates/oxide_builtins/src/array/element.rs index 56333e15..88671c2e 100644 --- a/crates/oxide_builtins/src/array/element.rs +++ b/crates/oxide_builtins/src/array/element.rs @@ -26,7 +26,8 @@ fn gated_get( vm: &mut H, arr_ptr: *mut JsObject, index: usize, recv: JsValue, ) -> Result, JsValue> { let key_si = vm.string_key_si(&index.to_string()); - if !vm.has_property(unsafe { &*arr_ptr }, key_si) { + // 数组目标非 deferred namespace,触发恒 no-op;错误按缺失处理(不可达)。 + if !vm.has_property(unsafe { &*arr_ptr }, key_si).unwrap_or(false) { return Ok(None); } let val = match vm.ordinary_get(unsafe { &*arr_ptr }, key_si, recv) { @@ -40,7 +41,9 @@ fn gated_get( /// 成功。数组元素区与命名属性双路径交由 delete_own_property_outcome 判定。 fn delete_prop_or_throw(vm: &mut H, obj: &mut JsObject, key_si: u32) -> Result<(), JsValue> { match crate::object::delete_own_property_outcome(vm, obj, key_si) { - crate::object::DeleteOutcome::NonConfigurable => Err(array_type_error(vm, "Cannot delete property")), + // deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值上抛。 + Err(exc) => Err(exc), + Ok(crate::object::DeleteOutcome::NonConfigurable) => Err(array_type_error(vm, "Cannot delete property")), _ => Ok(()), } } @@ -769,7 +772,8 @@ pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { let lower_si = vm.string_key_si(&lower.to_string()); let upper_si = vm.string_key_si(&upper.to_string()); // 先下后上读值(getter 副作用序对齐规范);hole 与原型缺失判端不存在。 - let lower_val = if vm.has_property(unsafe { &*arr_ptr }, lower_si) { + // 数组目标非 deferred namespace,触发恒 no-op;错误按缺失处理(不可达)。 + let lower_val = if vm.has_property(unsafe { &*arr_ptr }, lower_si).unwrap_or(false) { match vm.ordinary_get(unsafe { &*arr_ptr }, lower_si, recv) { Ok(v) => Some(v), Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)), @@ -782,7 +786,7 @@ pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { if let Some(v) = lower_val { vm.set_reg(0, v); } - let upper_val = if vm.has_property(unsafe { &*arr_ptr }, upper_si) { + let upper_val = if vm.has_property(unsafe { &*arr_ptr }, upper_si).unwrap_or(false) { match vm.ordinary_get(unsafe { &*arr_ptr }, upper_si, recv) { Ok(v) => Some(v), Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)), diff --git a/crates/oxide_builtins/src/json.rs b/crates/oxide_builtins/src/json.rs index 25f23738..9ee422a8 100644 --- a/crates/oxide_builtins/src/json.rs +++ b/crates/oxide_builtins/src/json.rs @@ -627,6 +627,8 @@ fn same_value_number(a: f64, b: f64) -> bool { /// 均静默不抛(规范明注)。 fn apply_child_result(vm: &mut H, child_ptr: *mut JsObject, child_si: u32, new_val: JsValue) { if new_val.is_undefined() { + // 子节点为 JSON.parse 新建的 plain 对象(非 deferred namespace),触发 + // 恒 no-op;删除失败静默不抛(规范明注)。 let _ = crate::object::delete_own_property(vm, unsafe { &mut *child_ptr }, child_si); } else { let _ = vm.define_data_property( diff --git a/crates/oxide_builtins/src/module.rs b/crates/oxide_builtins/src/module.rs index 826d5355..b8346459 100644 --- a/crates/oxide_builtins/src/module.rs +++ b/crates/oxide_builtins/src/module.rs @@ -70,11 +70,14 @@ pub struct DeferredNsState { /// undefined(哨兵:自导入/祖先引用,模块函数在导入方 prelude 期不可得)。 pub module: JsValue, /// `[[Evaluated]]`:依赖模块是否已求值(mixed 依赖构造时即为真; - /// 首次触发成功后置真,不回退)。 + /// 求值完成(成功或失败)后置真,不回退)。 pub evaluated: bool, /// `[[Namespace]]`:求值后返回的命名空间对象(mixed 依赖构造时即为真实 ns; /// 求值前为 undefined)。 pub namespace: JsValue, + /// 求值失败时缓存的原始错误值(再读再抛同一错误对象,保身份); + /// 成功求值与求值前恒 `None`。 + pub error: Option, } /// 模块命名空间条目表:挂在 ns 对象 `native_data` 上,是 exotic [[Get]] 的权威 @@ -125,6 +128,52 @@ pub fn deferred_state(obj: &JsObject) -> Option<&DeferredNsState> { table_ref.deferred.as_ref() } +/// 可变访问 deferred namespace 状态盒(求值触发点写回 `[[Evaluated]]` / +/// `[[Namespace]]` / 失败错误缓存)。 +/// +/// # 边界与前提 +/// - 返回 `None`:非 module namespace / 无条目表 / 非 deferred 对象(`deferred` 为 `None`)。 +/// - 返回 `Some`:deferred 对象且已安装状态盒,供求值触发点读写内部槽。 +/// +/// # 注意事项 +/// - 经 `native_data` 裸指针取 `&mut`(Box 堆分配、GC 不搬移,指针跨调用稳定), +/// 不借 `&mut JsObject` 本体。 +#[expect(clippy::mut_from_ref)] +pub fn deferred_state_mut(obj: &JsObject) -> Option<&mut DeferredNsState> { + let table = ns_table_ptr(obj); + if table.is_null() { + return None; + } + // SAFETY: table 归本 ns 对象持有,生命周期见 `deferred_state`。 + let table_ref = unsafe { &mut *table }; + table_ref.deferred.as_mut() +} + +/// deferred namespace 读路径的权威命名空间对象:已求值且 `[[Namespace]]` 为 +/// 对象时返回真实 ns(活值与真实槽位都在真实 ns 的条目表,deferred 对象自身 +/// 槽位是逐名预注册占位);其余形态(非 deferred / 未求值 / 哨兵)返回对象自身。 +/// +/// # 边界与前提 +/// - 非 deferred 对象恒返回自身,调用方无需先判 deferred 标志。 +/// - 求值成功后 `[[Namespace]]` 为 `__moduleEval` 返回的真实 ns 对象;求值失败 +/// 或哨兵(自导入/祖先)形态下无真实 ns,读路径回落对象自身。 +pub fn deferred_ns_read_target(obj: &JsObject) -> &JsObject { + if let Some(state) = deferred_state(obj) { + if state.evaluated { + let ns = state.namespace; + if ns.is_object() { + let ptr = ns.as_js_object_ptr(); + if !ptr.is_null() { + // SAFETY: ns 由 [[Namespace]] 槽持有(GC 边已登记), + // 与 deferred 对象同代存活。 + return unsafe { &*ptr }; + } + } + } + } + obj +} + /// 在条目表中按 interned 键定位条目下标。 fn entry_index(table: &ModuleNsTable, key_si: u32) -> Option { table.entries.iter().position(|e| e.name_si == key_si) @@ -232,9 +281,10 @@ pub fn module_ns_export(obj: &JsObject, key_si: u32) -> Option { /// 收集条目表持有的 JsValue 边(已初始化 `Value` 与全部 `Cell` 内值),供 GC mark。 /// -/// deferred namespace 对象额外持有 `[[Module]]` 与 `[[Namespace]]` 两条边: -/// 依赖模块函数与求值后命名空间对象须经本单一注册点入 mark,漏登则函数在 -/// deferred ns 存活期间被回收,触发点解引用悬垂。 +/// deferred namespace 对象额外持有 `[[Module]]`、`[[Namespace]]` 与失败缓存 +/// `error`(Some 时)三条边:依赖模块函数、求值后命名空间对象与失败错误对象 +/// 须经本单一注册点入 mark,漏登则对应值在 deferred ns 存活期间被回收, +/// 触发点解引用悬垂。 pub fn module_ns_native_edges(obj: &JsObject) -> Vec { let table = ns_table_ptr(obj); if table.is_null() { @@ -261,6 +311,10 @@ pub fn module_ns_native_edges(obj: &JsObject) -> Vec { if let Some(deferred) = &table_ref.deferred { edges.push(deferred.module); edges.push(deferred.namespace); + // 失败缓存的错误对象是 GC 边:漏登则 deferred ns 存活期被回收,再读解引用悬垂。 + if let Some(err) = &deferred.error { + edges.push(*err); + } } edges } @@ -844,6 +898,10 @@ pub fn module_seal(vm: &mut H, args: &[u8]) -> NativeResult { } /// `__moduleEval(fn)`:同步执行依赖模块(fn 为编译期 CREATE_CLOSURE 的函数对象),返回其命名空间。 +/// +/// # 副作用 +/// - 调用前置「正在求值模块」标记为 `Some(fn)`、返回后还原(save/restore,嵌套 +/// `__moduleEval` 安全):deferred namespace 求值触发的 cyclic 守卫据此判自求值。 pub fn module_eval(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return type_error(vm, "__moduleEval: 1 argument required"); @@ -856,10 +914,15 @@ pub fn module_eval(vm: &mut H, args: &[u8]) -> NativeResult { if ptr.is_null() || !unsafe { (*ptr).is_function() } { return type_error(vm, "__moduleEval: target is not a function"); } - match vm.call_function_sync(fn_val, JsValue::undefined(), &[]) { + // 置位标记,调用返回后无条件还原(异常展开亦不残留)。 + let saved = vm.evaluating_module(); + vm.set_evaluating_module(Some(fn_val)); + let result = match vm.call_function_sync(fn_val, JsValue::undefined(), &[]) { Ok(v) => NativeResult::Ok(v), Err(e) => NativeResult::Err(crate::error::create_error(vm, &e)), - } + }; + vm.set_evaluating_module(saved); + result } /// `__moduleDeferObject(fn, real_ns)`:创建 deferred namespace 对象(`import defer * as` 产物)。 @@ -871,7 +934,7 @@ pub fn module_eval(vm: &mut H, args: &[u8]) -> NativeResult { /// 2. 建 null 原型对象,定义 `@@toStringTag` = "Deferred Module"(不可写不可枚举不可配置)。 /// 3. 置 module namespace + deferred 双标志,置 non-extensible。 /// 4. 安装条目表并写状态盒:`[[Module]]` = fn、`[[Evaluated]]` = `real_ns` 为对象、 -/// `[[Namespace]]` = `real_ns`(或 undefined)。 +/// `[[Namespace]]` = `real_ns`(或 undefined)、失败错误缓存恒 `None`。 /// /// # 副作用 /// - 新对象创建起即标记 module namespace exotic 并置 non-extensible(同 `__moduleObject`); @@ -912,6 +975,7 @@ pub fn module_defer_object(vm: &mut H, args: &[u8]) -> NativeResult { module: fn_val, evaluated: ns_val.is_object(), namespace: ns_val, + error: None, }); } NativeResult::Ok(JsValue::from_js_object(obj)) @@ -1069,4 +1133,46 @@ mod tests { let state = deferred_state(ns_obj).expect("deferred state 应存活"); assert_eq!(state.module.as_js_object_ptr(), fn_ptr, "[[Module]] 指针应稳定"); } + + #[test] + fn module_defer_ns_gc_edge_keeps_error_alive() { + let (mut vm, _fn_val) = vm_with_function(); + // 失败缓存的错误对象(模拟求值失败后缓存的原值)。 + let err_val = crate::error::create_type_error(&mut vm, "cached failure"); + let err_ptr = err_val.as_js_object_ptr(); + + // 创建 deferred ns(哨兵形态)。 + vm.set_reg(1, JsValue::undefined()); + vm.set_reg(2, JsValue::undefined()); + let ns_val = module_defer_object(&mut vm, &[0, 1, 2]).unwrap(); + + // 直接写入 error 字段(模拟失败缓存),置已求值。 + { + let ns_obj = unsafe { &*ns_val.as_js_object_ptr() }; + let state = deferred_state_mut(ns_obj).expect("deferred state 应已安装"); + state.error = Some(err_val); + state.evaluated = true; + } + + // 仅 ns 为根:清掉持有 err 与 fn 的寄存器,err 唯一可达路径是 ns 的 error 边。 + vm.set_reg(0, ns_val); + vm.set_reg(1, JsValue::undefined()); + vm.set_reg(2, JsValue::undefined()); + + // 首收集达稳态(fn 无根被回收),记录稳态计数。 + vm.collect_session_gc(); + let steady_count = vm.session_object_count(); + + // 再收集一次:若 error 边漏登,err 被回收、计数回落。 + vm.collect_session_gc(); + assert_eq!( + vm.session_object_count(), + steady_count, + "失败缓存错误应跨收集存活(GC 边已登记)" + ); + // 原地 sweep 不搬移:ns 与状态盒原地保留,error 指针稳定。 + let ns_obj = unsafe { &*ns_val.as_js_object_ptr() }; + let state = deferred_state(ns_obj).expect("deferred state 应存活"); + assert_eq!(state.error.unwrap().as_js_object_ptr(), err_ptr, "error 指针应稳定"); + } } diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index f79e6502..af6c6ce7 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -259,6 +259,12 @@ pub fn object_get_own_property_symbols(vm: &mut H, args: &[u8]) -> Na Err(err) => return NativeResult::Err(err), }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + let obj = unsafe { &*obj_ptr }; + if let Err(exc) = ensure_deferred_own_keys(vm, obj) { + return NativeResult::Err(exc); + } + let symbols: Vec = { let obj = unsafe { &*obj_ptr }; own_symbol_key_values(vm, obj) @@ -332,17 +338,28 @@ pub enum DeleteOutcome { /// # 注意事项 /// - 数组元素存在性以 `prop_meta_at` 的 hole 标记判定,删除后重新写入元素 /// 会自动清除 hole 标记恢复存在 -pub fn delete_own_property_outcome(vm: &mut H, obj: &mut JsObject, key_si: u32) -> DeleteOutcome { +pub fn delete_own_property_outcome( + vm: &mut H, obj: &mut JsObject, key_si: u32, +) -> Result { + // deferred namespace 求值触发([[Delete]],symbol-like 键内部 no-op): + // 触发失败(cyclic / 失败缓存重抛)以异常值上抛,由调用边界恢复。 + // uncaught 侧通道已还原为原错误值,原值重抛保身份。 + if let Err(msg) = vm.ensure_deferred_ns_evaluation(obj, Some(key_si)) { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_from_text(vm, &msg)); + return Err(exc); + } // TA 数值键:界内索引不可配置(删除失败,元素零修改、镜像槽不同步); // 数字无效键视为缺失(删除成功,镜像槽同步 undefined,对非全局内置 TA // 为 no-op);非数字串 / symbol 键落下方普通属性路径。live 长 0 // (detach / 收缩越界)时全数值键自然归数字无效,与读 / 写 / 定义面同口径。 if obj.is_typed_array_obj() { match crate::typed_array::ta_index_gate(vm, obj, key_si) { - crate::typed_array::TaIndexGate::NumericValid(_) => return DeleteOutcome::NonConfigurable, + crate::typed_array::TaIndexGate::NumericValid(_) => return Ok(DeleteOutcome::NonConfigurable), crate::typed_array::TaIndexGate::NumericInvalid => { vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined()); - return DeleteOutcome::Missing; + return Ok(DeleteOutcome::Missing); } crate::typed_array::TaIndexGate::Ordinary => {} } @@ -355,21 +372,21 @@ pub fn delete_own_property_outcome(vm: &mut H, obj: &mut JsObject, ke let meta = obj.prop_meta_at(index); // 已是 hole 视为不存在;非 configurable 不可删。 if meta.is_some_and(|m| m.is_hole()) { - return DeleteOutcome::Missing; + return Ok(DeleteOutcome::Missing); } if meta.is_some_and(|m| !m.attributes.configurable()) { - return DeleteOutcome::NonConfigurable; + return Ok(DeleteOutcome::NonConfigurable); } obj.mark_hole_at(index); obj.bump_generation(); - return DeleteOutcome::Deleted; + return Ok(DeleteOutcome::Deleted); } // 大索引越出元素区时是命名属性(shape 链),落穿下方查删路径。 } // 非下标键:length 是虚拟属性(无 shape 槽、不在元素区),但描述符声明 // configurable:false,删除恒失败。 if key_si == vm.perm_intern("length") { - return DeleteOutcome::NonConfigurable; + return Ok(DeleteOutcome::NonConfigurable); } } @@ -383,7 +400,7 @@ pub fn delete_own_property_outcome(vm: &mut H, obj: &mut JsObject, ke // 属性缺失:delete 按规范返 true,镜像槽同步 undefined 保持 // "槽 = A 侧原始存储"不变式(与入口预载缺位语义幂等)。 vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined()); - return DeleteOutcome::Missing; + return Ok(DeleteOutcome::Missing); }; // walk_own_keys 已返回绝对存储索引(数组含元素区偏移),直接使用。 if obj @@ -391,7 +408,7 @@ pub fn delete_own_property_outcome(vm: &mut H, obj: &mut JsObject, ke .map(|meta| !meta.attributes.configurable()) .unwrap_or(false) { - return DeleteOutcome::NonConfigurable; + return Ok(DeleteOutcome::NonConfigurable); } // 数组重建前保存元素区(值 + meta),重建后恢复到命名属性之前。 @@ -456,13 +473,13 @@ pub fn delete_own_property_outcome(vm: &mut H, obj: &mut JsObject, ke // 删除成功:镜像槽同步 undefined(成员形删除 / Reflect.deleteProperty / // 0x9C 清槽同源收口,后两者对此幂等)。 vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined()); - DeleteOutcome::Deleted + Ok(DeleteOutcome::Deleted) } /// 删除对象自身属性并投影为布尔(`Reflect.deleteProperty` 语义):成功返回 true, -/// 属性不可配置返回 false,恒不抛错。 -pub fn delete_own_property(vm: &mut H, obj: &mut JsObject, key_si: u32) -> bool { - delete_own_property_outcome(vm, obj, key_si) != DeleteOutcome::NonConfigurable +/// 属性不可配置返回 false;deferred namespace 求值触发失败以异常值上抛。 +pub fn delete_own_property(vm: &mut H, obj: &mut JsObject, key_si: u32) -> Result { + Ok(delete_own_property_outcome(vm, obj, key_si)? != DeleteOutcome::NonConfigurable) } /// JS `Object()` 构造逻辑:创建空对象(prototype 为 null,由 VM 补装内置原型)。 @@ -533,7 +550,9 @@ fn namespace_export_get(obj: &JsObject, key_si: u32) -> Result, if is_symbol_key(key_si) { return Ok(None); } - match crate::module::module_ns_export(obj, key_si) { + // deferred namespace 求值后活值在真实 ns 条目表,读路径按权威对象查询。 + let target = crate::module::deferred_ns_read_target(obj); + match crate::module::module_ns_export(target, key_si) { None => Ok(None), Some(crate::module::ModuleNsQuery::Initialized(v)) => Ok(Some(v)), Some(crate::module::ModuleNsQuery::Uninitialized) => Err(crate::module::NS_UNINITIALIZED_MESSAGE), @@ -580,6 +599,10 @@ pub fn object_keys(vm: &mut H, args: &[u8]) -> NativeResult { }; let obj = unsafe { &*obj_ptr }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, obj) { + return NativeResult::Err(exc); + } let owned_keys: Vec<(u32, u32)> = walk_own_keys(vm, obj) .into_iter() .filter(|(_si, offset)| { @@ -685,6 +708,10 @@ pub fn object_assign(vm: &mut H, args: &[u8]) -> NativeResult { if source_ptr.is_null() { continue; } + // [[OwnPropertyKeys]] 触发(源对象,deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, unsafe { &*source_ptr }) { + return NativeResult::Err(exc); + } let source_keys: Vec<(u32, u32)> = { let source = unsafe { &*source_ptr }; walk_own_keys(vm, source) @@ -769,6 +796,15 @@ impl From for DefineDescFailure { pub(crate) fn define_from_descriptor( vm: &mut H, obj_ptr: *mut JsObject, key_si: u32, desc_val: JsValue, ) -> Result<(), DefineDescFailure> { + // [[DefineOwnProperty]] 触发(deferred namespace 先求值,不经 GOP 传导; + // symbol-like 键内部 no-op)。触发失败时 uncaught 侧通道已还原为原错误值, + // 原值重抛保身份(cyclic 无原值时按文本重建)。 + if let Err(msg) = vm.ensure_deferred_ns_evaluation(unsafe { &*obj_ptr }, Some(key_si)) { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_from_text(vm, &msg)); + return Err(DefineDescFailure::User(exc)); + } if !desc_val.is_object() { return Err(DefineDescFailure::Engine("Property description must be an object".to_string())); } @@ -1060,6 +1096,16 @@ pub fn object_define_property(vm: &mut H, args: &[u8]) -> NativeResul /// # 副作用 /// - 在 epoch 分配一个描述符对象 fn own_descriptor_of(vm: &mut H, obj: &JsObject, key_si: u32) -> Result, JsValue> { + // deferred namespace 求值触发([[GetOwnProperty]],symbol-like 键内部 no-op)。 + // 触发失败时 uncaught 侧通道已还原为原错误值,原值重抛保身份。 + if let Err(msg) = vm.ensure_deferred_ns_evaluation(obj, Some(key_si)) { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_from_text(vm, &msg)); + return Err(exc); + } + // 求值后描述符读真实 ns(deferred 对象自身槽位是预注册占位,活值在真实 ns)。 + let obj = crate::module::deferred_ns_read_target(obj); // TA 数值键:界内索引返回元素值 + 常量四字段描述符(writable/enumerable/ // configurable 恒真);数字无效与非数字串键落下方槽位路径(真实 own 属性 // 原样返回,无属性则 None)。 @@ -1194,6 +1240,10 @@ pub fn object_get_own_property_descriptors(vm: &mut H, args: &[u8]) - }; let obj = unsafe { &*obj_ptr }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, obj) { + return NativeResult::Err(exc); + } // 三段自身键序:字符串/整数键(整键升序在前)后接 Symbol 键插入序。 let mut keys = walk_own_keys(vm, obj); @@ -1245,6 +1295,20 @@ fn push_desc_prop(obj: &mut JsObject, shape_forge: &ShapeForge, prop_si: u32, va obj.push_prop(val); } +/// deferred namespace [[OwnPropertyKeys]] 触发(无键):失败时 uncaught 侧通道 +/// 已还原为原错误值,原值重抛保身份(cyclic 无原值时按文本重建)。 +fn ensure_deferred_own_keys(vm: &mut H, obj: &JsObject) -> Result<(), JsValue> { + match vm.ensure_deferred_ns_evaluation(obj, None) { + Ok(()) => Ok(()), + Err(msg) => { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_from_text(vm, &msg)); + Err(exc) + } + } +} + /// 校验并取出 Object 类方法的目标对象:`args[0]` 为接收者,`args[1]` 为待转换值, /// 缺参或值经 ToObject 失败(null/undefined)时抛 TypeError。 fn require_obj_arg(vm: &mut H, args: &[u8], fn_name: &str) -> Result<*mut JsObject, JsValue> { @@ -1313,6 +1377,10 @@ pub fn object_freeze(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(val); } let obj_ptr = val.as_js_object_ptr(); + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, unsafe { &*obj_ptr }) { + return NativeResult::Err(exc); + } { let obj = unsafe { &*obj_ptr }; // 模块命名空间导出保持 writable:true,冻结须失败(规范 SetIntegrityLevel)。 @@ -1363,6 +1431,10 @@ pub fn object_seal(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(val); } let obj_ptr = val.as_js_object_ptr(); + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, unsafe { &*obj_ptr }) { + return NativeResult::Err(exc); + } { let obj = unsafe { &mut *obj_ptr }; let keys = walk_own_keys(vm, obj); @@ -1418,6 +1490,10 @@ pub fn object_is_frozen(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(JsValue::bool(true)); } let obj = unsafe { &*obj_ptr }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, obj) { + return NativeResult::Err(exc); + } if obj.is_frozen() { return NativeResult::Ok(JsValue::bool(true)); } @@ -1454,6 +1530,10 @@ pub fn object_is_sealed(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(JsValue::bool(true)); } let obj = unsafe { &*obj_ptr }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, obj) { + return NativeResult::Err(exc); + } if obj.is_sealed() { return NativeResult::Ok(JsValue::bool(true)); } @@ -1500,6 +1580,10 @@ pub fn object_get_own_property_names(vm: &mut H, args: &[u8]) -> Nati let obj_ptr = native_try!(require_obj_arg(vm, args, "getOwnPropertyNames")); let obj = unsafe { &*obj_ptr }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, obj) { + return NativeResult::Err(exc); + } let keys = walk_own_keys(vm, obj); let n = keys.len(); let array_proto = vm.builtin_proto(ProtoKind::ArrayProto); @@ -2121,6 +2205,10 @@ pub fn object_proto_define_getter(vm: &mut H, args: &[u8]) -> NativeR pub fn object_entries(vm: &mut H, args: &[u8]) -> NativeResult { let obj_ptr = native_try!(require_obj_arg(vm, args, "entries")); let obj = unsafe { &*obj_ptr }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, obj) { + return NativeResult::Err(exc); + } let keys = walk_own_keys(vm, obj); let owned_keys: Vec<(u32, u32)> = keys .into_iter() @@ -2296,6 +2384,10 @@ pub fn object_group_by(vm: &mut H, args: &[u8]) -> NativeResult { pub fn object_values(vm: &mut H, args: &[u8]) -> NativeResult { let obj_ptr = native_try!(require_obj_arg(vm, args, "values")); let obj = unsafe { &*obj_ptr }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(exc) = ensure_deferred_own_keys(vm, obj) { + return NativeResult::Err(exc); + } let keys = walk_own_keys(vm, obj); let owned_keys: Vec<(u32, u32)> = keys .into_iter() diff --git a/crates/oxide_builtins/src/reflect.rs b/crates/oxide_builtins/src/reflect.rs index 3d4ec971..adc96573 100644 --- a/crates/oxide_builtins/src/reflect.rs +++ b/crates/oxide_builtins/src/reflect.rs @@ -157,7 +157,11 @@ pub fn reflect_delete_property(vm: &mut H, args: &[u8]) -> NativeResu Err(e) => return NativeResult::Err(from_engine_error(vm, &e)), }; let target = unsafe { &mut *target_ptr }; - NativeResult::Ok(JsValue::bool(delete_own_property(vm, target, key_si))) + match delete_own_property(vm, target, key_si) { + Ok(deleted) => NativeResult::Ok(JsValue::bool(deleted)), + // deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值上抛。 + Err(exc) => NativeResult::Err(exc), + } } /// `Reflect.get(target, key, receiver)`:读取属性(含原型链与 accessor)。 @@ -213,7 +217,11 @@ pub fn reflect_has(vm: &mut H, args: &[u8]) -> NativeResult { Ok(si) => si, Err(e) => return NativeResult::Err(from_engine_error(vm, &e)), }; - NativeResult::Ok(JsValue::bool(vm.has_property(unsafe { &*target_ptr }, key_si))) + // deferred namespace 目标先触发求值(cyclic 抛错 / 失败缓存重抛)。 + match vm.has_property(unsafe { &*target_ptr }, key_si) { + Ok(found) => NativeResult::Ok(JsValue::bool(found)), + Err(msg) => NativeResult::Err(from_engine_error(vm, &msg)), + } } /// `Reflect.isExtensible(target)`:对象是否可扩展。 @@ -240,6 +248,13 @@ pub fn reflect_own_keys(vm: &mut H, args: &[u8]) -> NativeResult { return type_error(vm, "Reflect.ownKeys target is not an object"); }; let target = unsafe { &*target_ptr }; + // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 + if let Err(msg) = vm.ensure_deferred_ns_evaluation(target, None) { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_from_text(vm, &msg)); + return NativeResult::Err(exc); + } // Symbol 键分流在消费端追加:底层字符串枚举源(Object.keys / JSON / for-in) // 语义不变,不泄漏 Symbol 键。 diff --git a/crates/oxide_builtins/src/structured_clone.rs b/crates/oxide_builtins/src/structured_clone.rs index 9eed7d9a..83d1fe56 100644 --- a/crates/oxide_builtins/src/structured_clone.rs +++ b/crates/oxide_builtins/src/structured_clone.rs @@ -335,6 +335,14 @@ fn fill_object_props( vm: &mut H, state: &mut CloneState, src: &JsObject, clone_ptr: *mut JsObject, ) -> Result<(), JsValue> { let src_val = JsValue::from_js_object(src as *const JsObject as *mut JsObject); + // [[OwnPropertyKeys]] 触发(源对象,deferred namespace 先求值,cyclic 抛错)。 + // plain 对象判定已排除 module namespace,此触发对实际可达路径恒 no-op。 + if let Err(msg) = vm.ensure_deferred_ns_evaluation(src, None) { + let exc = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_from_text(vm, &msg)); + return Err(exc); + } let str_keys = walk_own_keys(vm, src); let sym_keys = walk_own_symbol_keys(vm, src); for (si, store) in str_keys { diff --git a/crates/oxide_runtime_api/src/host.rs b/crates/oxide_runtime_api/src/host.rs index 3b5874c5..577b74fe 100644 --- a/crates/oxide_runtime_api/src/host.rs +++ b/crates/oxide_runtime_api/src/host.rs @@ -248,7 +248,10 @@ pub trait VmHost { fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option; /// 存在性判定(规范 HasProperty):自身与原型链任一层 P 为自有属性即存在; /// 数组元素区 hole 视同缺失,TypedArray 整数索引按视图长度判在界。 - fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool; + /// + /// deferred namespace 目标先触发 `EnsureDeferredNamespaceEvaluation`(cyclic + /// 守卫抛 TypeError / 失败缓存原值重抛),触发失败以 `Err` 返回。 + fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result; fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option; // 属性访问 @@ -284,6 +287,30 @@ pub trait VmHost { // 调用基础设施 fn call_function_sync(&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue]) -> Result; + /// deferred namespace 求值触发(`EnsureDeferredNamespaceEvaluation`): + /// 对 deferred 且未求值的 module namespace 同步执行依赖模块函数,缓存结果 + /// 命名空间或失败错误对象。 + /// + /// # 步骤 + /// 1. 非 module namespace / 非 deferred / 无状态盒 → no-op(`Ok`)。 + /// 2. symbol-like 键(Symbol 或 deferred 的 `"then"`)→ no-op(走 Ordinary* 路径)。 + /// 3. 已求值:失败缓存原值重抛(`Err`),成功 no-op。 + /// 4. cyclic 守卫:`[[Module]]` 为 undefined 哨兵且标记在场 → TypeError; + /// 标记等于 `[[Module]]` 函数(自求值中)→ TypeError。 + /// 5. 求值:`call_function_sync([[Module]])`,成功缓存 `[[Namespace]]`, + /// 失败缓存原错误值(还原 uncaught 侧通道供调用方原值重抛)。 + /// + /// # 边界与前提 + /// - `key_si` 为 `None` 表示 `[[OwnPropertyKeys]]` 触发(无键,恒触发)。 + /// + /// # 副作用 + /// - 写状态盒 `[[Evaluated]]` / `[[Namespace]]` / 失败错误缓存;可能执行用户 + /// 模块代码(含分配与 GC)。 + fn ensure_deferred_ns_evaluation(&mut self, obj: &JsObject, key_si: Option) -> Result<(), String>; + /// 读「正在求值模块」cyclic 守卫标记(`None` = 无模块求值中)。 + fn evaluating_module(&self) -> Option; + /// 置「正在求值模块」cyclic 守卫标记(save/restore 由调用方负责)。 + fn set_evaluating_module(&mut self, val: Option); /// 构造调用(Construct(C, args)):native 构造器以新对象为 receiver 值传递调用, /// bytecode 构造器压构造帧执行(含 derived 构造器 super() 语义与 new.target /// 传播),返回值非对象时回退到新对象。 diff --git a/crates/oxide_vm/src/bindings/bind_symbol.rs b/crates/oxide_vm/src/bindings/bind_symbol.rs index a2d2009c..41559ec9 100644 --- a/crates/oxide_vm/src/bindings/bind_symbol.rs +++ b/crates/oxide_vm/src/bindings/bind_symbol.rs @@ -70,9 +70,11 @@ pub fn bind_symbol(core: &Arc, session: &KernelSession, global: &mut // well-known symbol 以符号原语绑定,下标与内建符号表(0..WELL_KNOWN_SYMBOL_COUNT) // 一一对应;名称表是 id/名映射的唯一来源,属性名去掉 `Symbol.` 前缀。 + // 原语按 (realm 编号, 局部下标) 编码,与键空间 `encode_symbol_key` 同口径, + // 非零 realm 下与键物化值保持同一符号。 for (id, full_name) in oxide_types::private_key::WELL_KNOWN_SYMBOL_NAMES.iter().enumerate() { let name = full_name.strip_prefix("Symbol.").unwrap_or(full_name); - bind_well_known_symbol(core, ctor, name, JsValue::symbol(id as u32)); + bind_well_known_symbol(core, ctor, name, JsValue::symbol_realm(realm_id, id as u32)); } bind_constructor!(core, global, "Symbol", ctor_ptr, oxide_builtins::symbol::symbol_constructor::, 0, hash: true); diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index c0519924..a789c1a1 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -283,6 +283,11 @@ impl Vm { self.pc += 1; let value = self.regs[a]; let obj = unsafe { &mut *obj_ptr }; + // 规范 PrivateFieldAdd 步 1:对象不可扩展时拒加私有字段(模块命名空间 + // 等 exotic 对象创建起即 non-extensible)。 + if !obj.is_extensible() { + return self.raise_type_error("Cannot add private field to a non-extensible object"); + } if self .kernel_core .shape_forge() @@ -720,7 +725,11 @@ impl Vm { // 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);严格模式分派见 // finish_member_delete。 let outcome = oxide_builtins::object::delete_own_property_outcome(self, obj, prop_name_si); - self.finish_member_delete(rd, outcome) + match outcome { + // deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值重抛。 + Err(exc) => self.raise_captured(exc).map(|_| true), + Ok(outcome) => self.finish_member_delete(rd, outcome), + } } pub(crate) fn dispatch_delete_prop_dynamic(&mut self, rd: usize, b: usize) -> Result { @@ -738,7 +747,11 @@ impl Vm { // 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);严格模式分派见 // finish_member_delete。 let outcome = oxide_builtins::object::delete_own_property_outcome(self, obj, prop_name_si); - self.finish_member_delete(rd, outcome) + match outcome { + // deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值重抛。 + Err(exc) => self.raise_captured(exc).map(|_| true), + Ok(outcome) => self.finish_member_delete(rd, outcome), + } } /// 成员形 delete 的结果分派:不可配置属性在严格模式抛 TypeError(规范要求 @@ -794,8 +807,11 @@ impl Vm { // SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。 let obj = unsafe { &mut *global_ptr }; // 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);不可配置返回 - // false 且属性保留。 - let deleted = oxide_builtins::object::delete_own_property(self, obj, si); + // false 且属性保留。全局对象非 deferred namespace,触发恒 no-op。 + let deleted = match oxide_builtins::object::delete_own_property(self, obj, si) { + Ok(d) => d, + Err(exc) => return self.raise_captured(exc), + }; // 仅删除成功才清镜像槽(无槽 0 不写,reg 0 是返回槽不可误写)。 if deleted && slot > 0 { self.regs[slot] = JsValue::undefined(); diff --git a/crates/oxide_vm/src/vm/error.rs b/crates/oxide_vm/src/vm/error.rs index 3911652f..3238ae75 100644 --- a/crates/oxide_vm/src/vm/error.rs +++ b/crates/oxide_vm/src/vm/error.rs @@ -126,9 +126,11 @@ impl Vm { /// 使外围 try/catch 可捕获(native 函数抛错时原值存于 `last_uncaught_value`)。 /// /// # 注意事项 - /// 仅在主 dispatch(`native_call_depth == 0`)下展开——此时 try_stack 只含当前 + /// 主 dispatch(`native_call_depth == 0`)就地展开——此时 try_stack 只含当前 /// 字节码的处理器,展开后 pc/regs[0] 不会被中途的原生调用栈覆盖。原生 builtin - /// 内部(depth > 0)必须传播错误,由其调用边界(dispatch_native_call)转换。 + /// 内部(depth > 0)传播错误,由其调用边界(dispatch_native_call)转换;异常值 + /// 同步物化入异常通道(不取 uncaught 侧通道,builtin 边界 `from_engine_error` + /// 仍须从侧通道还原原值),供内联字节码体在状态还原前由体内 try 处理器捕获。 pub(crate) fn raise_call_error(&mut self, err: &str) -> Result { if self.native_call_depth == 0 { let exc = self @@ -141,27 +143,34 @@ impl Vm { self.unwind()?; return Ok(JsValue::undefined()); } + let exc = self + .last_uncaught_value + .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, err)); + let kind = self.thrown_error_kind(exc); + self.exception_value = Some(exc); + self.pending_error_kind = Some(kind); Err(err.to_string()) } /// 恢复已捕获的原始异常值(强转失败载荷)并走异常展开,保原值 kind: /// 深度 0 置原值入异常通道就地展开到外围 catch;深度 >0 返回 kind 前缀 - /// 文本 `Err`,由原生调用边界恢复为异常对象。 + /// 文本 `Err`,由原生调用边界恢复为异常对象,异常值同步物化入异常通道 + /// (供内联字节码体在状态还原前由体内 try 处理器捕获)。 /// /// # 步骤 /// 1. 深度 0:原值与 kind 写入异常通道,就地 `unwind`。 - /// 2. 深度 >0:以 `Kind: message` 文本 `Err` 返回,不就地展开。 + /// 2. 深度 >0:原值与 kind 写入异常通道,以 `Kind: message` 文本 `Err` 返回。 /// /// # 边界与前提 /// - `exc` 为调用方已提取的原始异常值;本入口不重取 uncaught 槽。 /// /// # 副作用 - /// - 深度 0 写 `exception_value`/`pending_error_kind`,pc 经展开改写。 + /// - 写 `exception_value`/`pending_error_kind`;深度 0 时 pc 经展开改写。 pub(crate) fn raise_captured(&mut self, exc: JsValue) -> Result<(), String> { + let kind = self.thrown_error_kind(exc); + self.exception_value = Some(exc); + self.pending_error_kind = Some(kind); if self.native_call_depth == 0 { - let kind = self.thrown_error_kind(exc); - self.exception_value = Some(exc); - self.pending_error_kind = Some(kind); return self.unwind(); } Err(oxide_builtins::typed_array::element_error_text(self, exc)) diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 0a1e8755..2f0e203f 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -242,6 +242,11 @@ pub struct Vm { /// String 后,for-of 需要重新抛出原值。放在 VM 顶层字段(不在 InlineSyncState 中) /// 以便跨内联调用的恢复过程存活。 pub(crate) last_uncaught_value: Option, + /// 「正在求值模块」cyclic 守卫标记(deferred namespace 求值触发用): + /// `run` 在 dispatch 前置 `Some(undefined)`(入口模块哨兵)、`module_eval` + /// 在 `call_function_sync` 前置 `Some(fn)`,两者均 save/restore(嵌套安全)。 + /// 触发点据此判自求值/祖先求值中的 cyclic 并抛 TypeError。 + pub(crate) evaluating_module: Option, /// 数组 length define 强转期用户代码抛出的原始异常:与 `last_uncaught_value` /// 分离,避免入口误取其它操作忽略调用残留的值。仅 `define_array_length` 写入, /// 由 Object/Reflect define 入口消费。 diff --git a/crates/oxide_vm/src/vm/props.rs b/crates/oxide_vm/src/vm/props.rs index a4fa006a..df81f8e1 100644 --- a/crates/oxide_vm/src/vm/props.rs +++ b/crates/oxide_vm/src/vm/props.rs @@ -2,6 +2,7 @@ //! 数组下标反解、非对象基删除与属性解析及自身属性槽查找。 use oxide_runtime_api as coercion; +use oxide_runtime_api::VmHost; use oxide_types::object::JsObject; use oxide_types::private_key::{encode_symbol_key, int_key_value, is_int_key, make_int_key, INT_KEY_COUNT}; @@ -212,25 +213,35 @@ impl Vm { /// 与 `resolve_property` 的差异:原型链上每层都检查数组元素区 /// (`resolve_property` 只在顶层检查元素区,链上仅走 shape 槽), /// 继承自父数组的索引属性在此判存在。 - pub(crate) fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool { + /// + /// deferred namespace 目标在原型链每层触发 `EnsureDeferredNamespaceEvaluation` + /// (`in` 运算符逐级走 HasProperty;非 deferred 对象触发为 no-op),触发失败 + /// (cyclic 守卫抛 TypeError / 失败缓存原值重抛)走可捕获异常展开。 + pub(crate) fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result { let length_si = self.length_si; let mut current = Some(obj); let mut depth = 0usize; while let Some(obj) = current { + // deferred namespace 求值触发(symbol-like 键内部 no-op):链上每层 + // 独立判定,`key in obj`(obj 原型为 deferred ns)在 ns 层触发。 + if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) { + self.raise_call_error(&msg)?; + return Ok(false); + } if obj.is_array() && prop_name_si == length_si { - return true; + return Ok(true); } // 统一数值键门(exotic [[HasProperty]]):同 `ordinary_get_inner` // 的口径,链上每层同口径判定。 if obj.is_typed_array_obj() { match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) { - oxide_builtins::typed_array::TaIndexGate::NumericValid(_) => return true, - oxide_builtins::typed_array::TaIndexGate::NumericInvalid => return false, + oxide_builtins::typed_array::TaIndexGate::NumericValid(_) => return Ok(true), + oxide_builtins::typed_array::TaIndexGate::NumericInvalid => return Ok(false), oxide_builtins::typed_array::TaIndexGate::Ordinary => {} } } if self.get_own_property_slot(obj, prop_name_si).is_some() { - return true; + return Ok(true); } if depth >= MAX_PROTO_CHAIN_DEPTH { break; @@ -239,7 +250,7 @@ impl Vm { let proto = obj.proto(); current = proto.is_object().then(|| unsafe { &*proto.as_js_object_ptr() }); } - false + Ok(false) } /// 查找自身属性槽下标:数组 length 虚拟属性返回 `None`,元素区返回下标 diff --git a/crates/oxide_vm/src/vm/vmhost.rs b/crates/oxide_vm/src/vm/vmhost.rs index c9f0757f..f4074560 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -209,7 +209,7 @@ impl oxide_runtime_api::VmHost for Vm { fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option { self.resolve_property(obj, prop_name_si) } - fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool { + fn has_property(&mut self, obj: &JsObject, prop_name_si: u32) -> Result { self.has_property(obj, prop_name_si) } fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option { @@ -252,6 +252,71 @@ impl oxide_runtime_api::VmHost for Vm { fn call_function_sync(&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue]) -> Result { self.call_function_sync(callee, receiver, args) } + fn ensure_deferred_ns_evaluation(&mut self, obj: &JsObject, key_si: Option) -> Result<(), String> { + // 非 deferred module namespace 直接 no-op(热路径零成本)。 + if !obj.is_module_namespace() || !obj.is_deferred() { + return Ok(()); + } + // symbol-like 键(Symbol 或 deferred 的 "then")走 Ordinary* 路径,不触发。 + if let Some(key) = key_si { + if oxide_types::private_key::is_symbol_key(key) { + return Ok(()); + } + if key == self.perm_intern("then") { + return Ok(()); + } + } + // 读状态盒(经 native_data 裸指针,Box 堆分配 GC 不搬移,指针稳定)。 + let Some(state) = oxide_builtins::module::deferred_state_mut(obj) else { + return Ok(()); + }; + if state.evaluated { + // 已求值:失败缓存原值重抛(保错误对象身份),成功 no-op。 + if let Some(err) = state.error { + // 还原 uncaught 侧通道为缓存的原错误值,调用方([[Get]] 等) + // 经 raise_call_error 原值重抛,保 sameValue 身份。 + self.last_uncaught_value = Some(err); + return Err(self.error_text(err)); + } + return Ok(()); + } + // cyclic 守卫:[[Module]] 为 undefined 哨兵(自导入/祖先 defer)时, + // 标记在场即求值中 → TypeError;标记已清(祖先已求值完毕)按已求值处理。 + if state.module.is_undefined() { + if self.evaluating_module.is_some() { + return Err(self.error_message_text("TypeError", "cyclic module evaluation")); + } + state.evaluated = true; + return Ok(()); + } + // [[Module]] 为函数:标记等于本函数即自求值中(安全网)→ TypeError。 + if self.evaluating_module == Some(state.module) { + return Err(self.error_message_text("TypeError", "cyclic module evaluation")); + } + // EvaluateSync:调依赖模块函数,缓存结果命名空间或失败错误。 + match self.call_function_sync(state.module, JsValue::undefined(), &[]) { + Ok(ns) => { + state.namespace = ns; + state.evaluated = true; + } + Err(text) => { + // 原错误值经 uncaught 侧通道取走存入状态盒,并还原侧通道供 + // 调用方([[Get]] 等)原值重抛。 + let exc = self.last_uncaught_value.take(); + state.error = exc; + state.evaluated = true; + self.last_uncaught_value = exc; + return Err(text); + } + } + Ok(()) + } + fn evaluating_module(&self) -> Option { + self.evaluating_module + } + fn set_evaluating_module(&mut self, val: Option) { + self.evaluating_module = val; + } fn construct_ctor(&mut self, ctor: JsValue, args: &[JsValue]) -> Result { Vm::construct_ctor(self, ctor, args) } diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 454f586b..5863dc1a 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -1,6 +1,7 @@ use crate::vm::{Completion, FrameArgs, FrameContinuation, PendingAsyncEscape, TryHandler, Vm}; use crate::vm_trace; use oxide_bytecode::opcode; +use oxide_runtime_api::VmHost; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::make_private_name_id; use oxide_types::value::JsValue; @@ -233,6 +234,12 @@ impl Vm { let prop_name_si = self.property_key_si(self.regs[b])?; let value = self.regs[a]; let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; + // [[DefineOwnProperty]] 触发(类字段初始化 CreateDataPropertyOrThrow 路径): + // deferred namespace 先求值,触发失败走可捕获异常展开。 + if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) { + self.raise_call_error(&msg)?; + return Ok(()); + } match self.define_data_property(obj, prop_name_si, value, PropAttributes::DEFAULT_DATA) { Ok(()) => Ok(()), Err(msg) => self.raise_error_kind("TypeError", &msg), @@ -251,6 +258,11 @@ impl Vm { let prop_name_si = self.property_key_si(self.regs[b])?; let value = self.regs[a]; let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; + // [[DefineOwnProperty]] 触发(同 `dispatch_define_prop`,带描述符形态)。 + if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) { + self.raise_call_error(&msg)?; + return Ok(()); + } match self.define_data_property(obj, prop_name_si, value, PropAttributes(attrs)) { Ok(()) => Ok(()), Err(msg) => self.raise_error_kind("TypeError", &msg), diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index bbe512cc..80f4e3ca 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -1,6 +1,6 @@ use crate::vm::{ForInIter, FrameArgs, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH}; use crate::vm_trace; -use oxide_runtime_api::{push_units_to, to_boolean, to_units_full}; +use oxide_runtime_api::{push_units_to, to_boolean, to_units_full, VmHost}; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::{ encode_symbol_key, int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, INT_KEY_COUNT, @@ -453,8 +453,8 @@ impl Vm { let obj = unsafe { &*obj_ptr }; let prop_name_si = self.property_key_si(key_val)?; // `in` 即 HasProperty:原型链各层都要看数组元素区与 TypedArray 整数索引, - // 读值解析不覆盖这两处。 - let found = self.has_property(obj, prop_name_si); + // 读值解析不覆盖这两处。deferred namespace 目标先触发求值(cyclic 抛错)。 + let found = self.has_property(obj, prop_name_si)?; self.regs[rd] = JsValue::bool(found); Ok(()) } @@ -473,6 +473,17 @@ impl Vm { obj_val = oxide_runtime_api::to_object(obj_val, self)?; } + // [[OwnPropertyKeys]] 触发(for-in 枚举源):deferred namespace 先求值, + // cyclic 守卫抛 TypeError,触发失败走可捕获异常展开。 + let obj_ptr = obj_val.as_js_object_ptr(); + if !obj_ptr.is_null() { + let obj = unsafe { &*obj_ptr }; + if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, None) { + self.raise_call_error(&msg)?; + return Ok(()); + } + } + let mut keys_vec: Vec<(JsValue, u32)> = Vec::new(); let mut seen = std::collections::HashSet::new(); let mut current = obj_val; @@ -569,7 +580,8 @@ impl Vm { // 模块命名空间 exotic:EnumerateObjectProperties 构建键表时逐键走 // `? [[GetOwnProperty]]`,未初始化导出抛 ReferenceError。校验须在排序与 // 迭代器建立之前(INIT 期),不遗留 for-in 迭代器;非 live ns 无条目表, - // `module_ns_export` 返回 None,落普通路径零行为变化。 + // `module_ns_export` 返回 None,落普通路径零行为变化。deferred 对象求值后 + // 校验按真实 ns 条目表(deferred 自身槽位是预注册占位)。 if obj_val.is_object() { let ns_obj = unsafe { &*obj_val.as_js_object_ptr() }; if ns_obj.is_module_namespace() { @@ -577,8 +589,9 @@ impl Vm { if is_symbol_key(*si) { continue; } + let target = oxide_builtins::module::deferred_ns_read_target(ns_obj); if let Some(oxide_builtins::module::ModuleNsQuery::Uninitialized) = - oxide_builtins::module::module_ns_export(ns_obj, *si) + oxide_builtins::module::module_ns_export(target, *si) { return self .raise_error_kind("ReferenceError", oxide_builtins::module::NS_UNINITIALIZED_MESSAGE); diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 241029f8..b5281990 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -3,6 +3,7 @@ use crate::{ic_trace, vm_trace}; use oxide_kernel::prop_forge::PropTemplate; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_runtime_api as coercion; +use oxide_runtime_api::VmHost; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; use oxide_types::value::JsValue; @@ -54,7 +55,15 @@ impl Vm { // 模块命名空间 exotic [[Get]]:条目表是权威状态,预注册但未初始化的 // 导出读抛 ReferenceError;非 live ns(无表)落普通属性路径零行为变化。 if obj.is_module_namespace() { - if let Some(state) = oxide_builtins::module::module_ns_export(obj, prop_name_si) { + // deferred namespace 求值触发(symbol-like 键内部 no-op);触发 + // 失败(cyclic / 失败缓存重抛)走可捕获异常展开,外围 try/catch + // 可收到原错误值。 + if let Err(msg) = self.ensure_deferred_ns_evaluation(obj, Some(prop_name_si)) { + return self.raise_call_error(&msg); + } + // 求值后活值在真实 ns 条目表,deferred 对象自身槽位是预注册占位。 + let target = oxide_builtins::module::deferred_ns_read_target(obj); + if let Some(state) = oxide_builtins::module::module_ns_export(target, prop_name_si) { return match state { oxide_builtins::module::ModuleNsQuery::Initialized(v) => Ok(v), oxide_builtins::module::ModuleNsQuery::Uninitialized => { @@ -360,8 +369,9 @@ impl Vm { prop_name_si, use_frame_push ); - // 模块命名空间 exotic [[Set]] 恒 false(规范 10.4.6.8):严格抛 TypeError, - // sloppy 静默 no-op;Reflect.set 以 strict=true 调用并投影为 false。 + // 模块命名空间 exotic [[Set]] 恒 false(规范 10.4.6.8,deferred 形态同口径 + // 不触发求值):严格抛 TypeError,sloppy 静默 no-op;Reflect.set 以 + // strict=true 调用并投影为 false。 if obj.is_module_namespace() { if strict { return self.write_protection_failure(builtin, "Cannot assign to a module namespace export"); @@ -1713,7 +1723,7 @@ mod tests { assert_eq!(obj.logical_len(), 2_000_001, "length 扩到 index+1"); assert_eq!(obj.array_prop_count, 1, "物理区不物化"); assert_eq!(vm.ordinary_get(obj, si, a).expect("get"), JsValue::int(9)); - assert!(vm.has_property(obj, si), "in 为 true"); + assert!(vm.has_property(obj, si).expect("has_property"), "in 为 true"); let keys = oxide_builtins::object::walk_own_keys(&vm, obj); assert!(keys.iter().any(|(k, _)| *k == si), "枚举含大索引键"); } @@ -1729,7 +1739,7 @@ mod tests { let obj = unsafe { &*b.as_js_object_ptr() }; assert_eq!(obj.logical_len(), 0, "2^32-1 不扩展 length"); assert_eq!(vm.ordinary_get(obj, si, b).expect("get"), JsValue::int(9)); - assert!(vm.has_property(obj, si)); + assert!(vm.has_property(obj, si).expect("has_property")); } #[test] @@ -1763,7 +1773,7 @@ mod tests { let obj = unsafe { &*d.as_js_object_ptr() }; assert_eq!(obj.logical_len(), 5); - assert!(!vm.has_property(obj, si), "截断后 3000000 键被删"); + assert!(!vm.has_property(obj, si).expect("has_property"), "截断后 3000000 键被删"); assert_eq!(vm.ordinary_get(obj, si, d).expect("get"), JsValue::undefined()); } @@ -1839,11 +1849,12 @@ mod tests { .expect("set"); let outcome = - oxide_builtins::object::delete_own_property_outcome(&mut vm, unsafe { &mut *a.as_js_object_ptr() }, si); + oxide_builtins::object::delete_own_property_outcome(&mut vm, unsafe { &mut *a.as_js_object_ptr() }, si) + .expect("delete"); assert_eq!(outcome, oxide_builtins::object::DeleteOutcome::Deleted); let obj = unsafe { &*a.as_js_object_ptr() }; assert_eq!(obj.logical_len(), 2_000_001, "length 不变"); - assert!(!vm.has_property(obj, si)); + assert!(!vm.has_property(obj, si).expect("has_property")); } #[test] @@ -1941,4 +1952,109 @@ mod tests { let obj = unsafe { &*boxed.as_js_object_ptr() }; assert_eq!(vm.ordinary_get(obj, length_si, boxed).expect("get"), JsValue::int(4), "length 不变"); } + + // ---- deferred namespace 求值触发测试 ---- + + use std::sync::Arc; + + /// 建带编译服务的 VM 与一个依赖模块函数(deferred ns 的 `[[Module]]` 载荷)。 + fn deferred_vm_with_function() -> (Vm, JsValue) { + let mut vm = Vm::new(); + vm.set_compiler_service(Arc::new(oxide_compiler::DefaultCompilerService)); + let fn_val = vm + .create_dynamic_function(&[], "return 42;", false, false) + .expect("create function"); + (vm, fn_val) + } + + /// 建 deferred namespace 对象(`fn_val` 为依赖模块函数,未求值态)。 + fn make_deferred_ns(vm: &mut Vm, fn_val: JsValue) -> JsValue { + vm.set_reg(1, fn_val); + vm.set_reg(2, JsValue::undefined()); + oxide_builtins::module::module_defer_object(vm, &[0, 1, 2]).unwrap() + } + + #[test] + fn deferred_ns_get_triggers_evaluation() { + let (mut vm, fn_val) = deferred_vm_with_function(); + let ns_val = make_deferred_ns(&mut vm, fn_val); + let ns_ptr = ns_val.as_js_object_ptr(); + // 未求值态。 + let ns = unsafe { &*ns_ptr }; + assert!(!oxide_builtins::module::deferred_state(ns).unwrap().evaluated, "求值前应为 false"); + + // 读属性触发求值(模块函数返回 42,非对象无妨,只求值完成)。 + let x_si = vm.kernel_core.perm_interner().intern("x").0; + let ns = unsafe { &*ns_ptr }; + let _ = vm.ordinary_get(ns, x_si, ns_val); + + // 求值后 [[Evaluated]] 置真。 + let ns = unsafe { &*ns_ptr }; + assert!(oxide_builtins::module::deferred_state(ns).unwrap().evaluated, "求值后应为 true"); + } + + #[test] + fn deferred_ns_cyclic_get_throws_type_error() { + let (mut vm, _fn_val) = deferred_vm_with_function(); + // 哨兵形态:[[Module]] = undefined(自导入/祖先 defer)。 + vm.set_reg(1, JsValue::undefined()); + vm.set_reg(2, JsValue::undefined()); + let ns_val = oxide_builtins::module::module_defer_object(&mut vm, &[0, 1, 2]).unwrap(); + let ns_ptr = ns_val.as_js_object_ptr(); + + // 模拟「正在求值模块」:置 cyclic 守卫标记。 + vm.set_evaluating_module(Some(JsValue::undefined())); + + // 读属性触发 cyclic 守卫 → TypeError(builtin 上下文返 Err)。 + let x_si = vm.kernel_core.perm_interner().intern("x").0; + let ns = unsafe { &*ns_ptr }; + let result = vm.ordinary_get(ns, x_si, ns_val); + assert!(result.is_err(), "cyclic 应抛 TypeError"); + } + + #[test] + fn deferred_ns_failure_rethrows_same_error() { + let (mut vm, _fn_val) = deferred_vm_with_function(); + // 模块函数抛错。 + let throwing_fn = vm + .create_dynamic_function(&[], "throw new Error('boom');", false, false) + .expect("create function"); + vm.set_reg(1, throwing_fn); + vm.set_reg(2, JsValue::undefined()); + let ns_val = oxide_builtins::module::module_defer_object(&mut vm, &[0, 1, 2]).unwrap(); + let ns_ptr = ns_val.as_js_object_ptr(); + let x_si = vm.kernel_core.perm_interner().intern("x").0; + + // 首次读:触发求值,模块函数抛错,缓存错误。 + let ns = unsafe { &*ns_ptr }; + assert!(vm.ordinary_get(ns, x_si, ns_val).is_err(), "首次读应抛错"); + let ns = unsafe { &*ns_ptr }; + let cached_err = oxide_builtins::module::deferred_state(ns).unwrap().error.expect("失败应缓存错误"); + + // 再读:重抛同一错误对象(sameValue 身份)。 + let ns = unsafe { &*ns_ptr }; + assert!(vm.ordinary_get(ns, x_si, ns_val).is_err(), "再读应再抛"); + // 重抛经 uncaught 侧通道还原,取回验证指针身份。 + let rethrown = vm.take_uncaught_value().expect("重抛应还原 uncaught 侧通道"); + assert!( + std::ptr::eq(rethrown.as_js_object_ptr(), cached_err.as_js_object_ptr()), + "再读应重抛同一错误对象" + ); + } + + #[test] + fn deferred_ns_symbol_like_key_does_not_trigger() { + let (mut vm, fn_val) = deferred_vm_with_function(); + let ns_val = make_deferred_ns(&mut vm, fn_val); + let ns_ptr = ns_val.as_js_object_ptr(); + + // 读 "then" 键(deferred 的 symbol-like 键)不触发求值。 + let then_si = vm.kernel_core.perm_interner().intern("then").0; + let ns = unsafe { &*ns_ptr }; + let _ = vm.ordinary_get(ns, then_si, ns_val); + + // 求值未触发:[[Evaluated]] 仍为 false。 + let ns = unsafe { &*ns_ptr }; + assert!(!oxide_builtins::module::deferred_state(ns).unwrap().evaluated, "symbol-like 键不应触发求值"); + } } diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index d764ca58..a744a49a 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -519,7 +519,26 @@ impl Vm { sub.n_args, sub.is_arrow ); - let result = self.dispatch(); + // 体自身 try 处理器基线:异常逃出体内时先由体内处理器捕获 + // (catch_pc 在体字节码内,状态还原后指向调用方字节码即悬空)。 + let try_base = self.try_stack.len(); + let mut result = self.dispatch(); + loop { + if result.is_ok() || self.try_stack.len() <= try_base { + break; + } + // 体内处理器捕获:pc 落体字节码 catch/finally,继续执行体余部。 + // 捕获体再抛(re-throw)时展开会消费外层处理器并跳外字节码 catch_pc, + // 与活动字节码不一致——re-dispatch 返回 Err 即该形态,还原状态后由 + // 调用边界按外层处理器正常展开(与修复前同口径)。 + if self.unwind().is_err() { + break; + } + result = self.dispatch(); + if result.is_err() { + break; + } + } self.native_call_depth -= 1; vm_trace!("call_bytecode: restoring state pc={} result={:?}", saved.pc, result.as_ref().ok()); @@ -724,7 +743,14 @@ impl Vm { self.top_level_this = this_val; self.regs[254] = this_val; + // 「正在求值模块」cyclic 守卫标记:入口模块函数对象在 run 不可得(顶层 + // CREATE_CLOSURE 产物),用 undefined 哨兵——sentinel 检查只看 is_some(), + // 依赖模块的 real-function 检查恒不匹配 undefined,安全。save/restore 保 + // 嵌套/重入安全,dispatch 返回即还原(微任务 drain 前模块已非求值中)。 + let saved_evaluating = self.evaluating_module.take(); + self.evaluating_module = Some(JsValue::undefined()); let result = self.execute_task(|vm| vm.dispatch()); + self.evaluating_module = saved_evaluating; // 顶层执行结束后 drain 微任务队列:Promise reactions 与 thenable 委托在此执行。 self.drain_microtasks(); // 指令周期采样:run 末聚合本 run 的样本并输出 top-K 直方图(关闭时零开销 diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index b1fede22..49391226 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -135,6 +135,7 @@ impl Vm { try_stack: Vec::new(), exception_value: None, last_uncaught_value: None, + evaluating_module: None, pending_length_exception: None, pending_exception: None, pending_error_kind: None, @@ -295,6 +296,7 @@ impl Vm { try_stack: Vec::new(), exception_value: None, last_uncaught_value: None, + evaluating_module: None, pending_length_exception: None, pending_exception: None, pending_error_kind: None,