From 1cb943beee93a78511f1dde8b4bfe9a3d75fea2e Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 18 Oct 2026 06:10:48 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A1=A5=E9=BD=90=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E7=BB=84=E4=B8=8E=E5=AE=8C=E6=95=B4=E6=80=A7?= =?UTF-8?q?=E7=BA=A7=E5=88=AB=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 +- Cargo.toml | 2 +- crates/oxide_builtins/src/object.rs | 523 ++++++++++++++---- crates/oxide_builtins/src/reflect.rs | 2 +- crates/oxide_builtins/src/regexp.rs | 6 +- crates/oxide_vm/src/dispatch/call.rs | 12 +- crates/oxide_vm/src/dispatch/property.rs | 52 +- crates/oxide_vm/src/vm/dynamic.rs | 4 +- crates/oxide_vm/src/vm_dispatch_misc.rs | 30 + crates/oxide_vm/src/vm_props.rs | 145 +++-- crates/oxide_vm/src/vm_support.rs | 23 +- .../tests/builtins_arraybuffer_state_tests.rs | 5 +- .../oxide_vm/tests/builtins_function_tests.rs | 15 +- .../oxide_vm/tests/builtins_regexp_tests.rs | 22 +- .../tests/builtins_typed_array_tests.rs | 10 +- crates/oxide_vm/tests/new_target_tests.rs | 15 +- 16 files changed, 684 insertions(+), 224 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9434e0d5..7d6a9dd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.20.9" +version = "0.20.10" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.20.9" +version = "0.20.10" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.20.9" +version = "0.20.10" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.20.9" +version = "0.20.10" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.20.9" +version = "0.20.10" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.20.9" +version = "0.20.10" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.20.9" +version = "0.20.10" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.20.9" +version = "0.20.10" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.20.9" +version = "0.20.10" [[package]] name = "oxide_parser" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.20.9" +version = "0.20.10" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.20.9" +version = "0.20.10" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.20.9" +version = "0.20.10" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.20.9" +version = "0.20.10" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 69d8ccba..f778e1f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.20.9" +version = "0.20.10" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 5ac20fde..5082ee08 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -203,15 +203,20 @@ fn symbol_key_to_value(key: u32) -> JsValue { /// 代理感知的自身键收集(规范 [[OwnPropertyKeys]] 完整形态):普通对象经 /// walk_own_keys 加 walk_own_symbol_keys(整数键升序、字符串键插入序、Symbol /// 键插入序);代理经 ownKeys trap——缺失落穿 target 的自身键,存在时以 trap -/// 结果为主、target 键中不在 trap 的追加末尾(trap 序优先),target 上没有 -/// 的键丢弃(无存储槽可定位,描述符按缺失处理)。 +/// 结果为准(ToList 去重,重复抛 TypeError,非字符串/符号元素抛 TypeError), +/// target 不可配置键须全部在 trap 结果中,target 不可扩展时 trap 结果须与 +/// target 键集完全一致;target 上不有的键以哨兵存储下标返回(无槽位,描述符 +/// 按缺失处理)。 /// /// # 步骤 /// 1. 非代理:walk_own_keys 加 walk_own_symbol_keys,存储对象即自身。 /// 2. 代理:先递归落穿 target(target 自身为代理时再路由)取 target 键。 /// 3. ownKeys trap 存在时 ToList 其结果(数组直读元素区、非数组按 length -/// 读整数索引区),逐元素 ToPropertyKey 去重。 -/// 4. 合并:trap 键在前(存储下标按 target 键定位),target 键补尾。 +/// 读整数索引区),逐元素先 ToPropertyKey 再校验字符串/符号类型(规范 +/// 步 7),重复键抛 TypeError(规范步 8)。 +/// 4. 不变量检查:target 不可配置键须在 trap 结果中;target 不可扩展时 +/// 键集完全一致(缺失键与多余键均抛 TypeError)。 +/// 5. 返 trap 结果列表(trap 序),target 上有的键定位存储下标。 /// /// # 边界与前提 /// - `obj` 须为存活对象;代理的 target 由构造器校验为非空对象指针。 @@ -221,9 +226,19 @@ fn symbol_key_to_value(key: u32) -> JsValue { /// /// # 返回 /// (键列表, 存储对象):消费方读 meta/值一律经存储对象,不读代理本体。 +/// +/// # 错误语义 +/// - `Err` 携异常值:trap 抛错经 uncaught 侧通道还原原值重抛保身份; +/// 不变量与元素类型失败为 TypeError 值。 pub type OwnKeys<'a> = (Vec<(u32, u32)>, &'a JsObject); -pub fn own_keys_proxy_aware<'a, H: VmHost>(vm: &mut H, obj: &'a JsObject) -> Result, String> { +/// ownKeys 代理路径统一错误出口:优先从 uncaught 侧通道还原原始异常值 +/// (保 trap 抛出原值的身份),侧通道为空时按格式化文本构造异常。 +fn own_keys_trap_exc(vm: &mut H, msg: String) -> JsValue { + vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_from_text(vm, &msg)) +} + +pub fn own_keys_proxy_aware<'a, H: VmHost>(vm: &mut H, obj: &'a JsObject) -> Result, JsValue> { if !obj.is_proxy_obj() { let mut keys = walk_own_keys(vm, obj); keys.extend(walk_own_symbol_keys(vm, obj)); @@ -234,33 +249,44 @@ pub fn own_keys_proxy_aware<'a, H: VmHost>(vm: &mut H, obj: &'a JsObject) -> Res let target_obj = unsafe { &*target.as_js_object_ptr() }; // 落穿:target 自身为代理时递归再路由,普通对象取其自身键。 let (target_keys, storage) = own_keys_proxy_aware(vm, target_obj)?; - let trap_result = vm.proxy_call_trap(obj, vm.perm_intern("ownKeys"), &[target])?; + let trap_result = match vm.proxy_call_trap(obj, vm.perm_intern("ownKeys"), &[target]) { + Ok(r) => r, + Err(msg) => return Err(own_keys_trap_exc(vm, msg)), + }; let Some(result) = trap_result else { return Ok((target_keys, storage)); }; if !result.is_object() { - return Err(oxide_runtime_api::format_error_message( - "TypeError", - "Proxy ownKeys trap returned a non-object", - )); + return Err(crate::error::create_type_error(vm, "Proxy ownKeys trap returned a non-object")); } // SAFETY: result 是非空对象指针。 let result_obj = unsafe { &*result.as_js_object_ptr() }; // ToList:数组直读元素区(hole 视为不存在);非数组按 length 属性读整数 - // 索引区。 + // 索引区;逐元素先 ToPropertyKey 再校验元素须为字符串或符号(规范步 7), + // 重复键抛 TypeError(规范步 8)。 let mut trap_keys: Vec = Vec::new(); if result_obj.is_array() { for i in 0..result_obj.array_prop_count { if result_obj.prop_meta_at(i).is_some_and(|m| m.is_hole()) { continue; } - let si = vm.to_property_key_si(result_obj.get_prop_at(i))?; - if !trap_keys.contains(&si) { - trap_keys.push(si); + let raw = result_obj.get_prop_at(i); + let si = vm.to_property_key_si(raw).map_err(|msg| own_keys_trap_exc(vm, msg))?; + if !raw.is_string() && !raw.is_symbol() { + return Err(crate::error::create_type_error( + vm, + "Proxy ownKeys trap returned a key that is not a string or symbol", + )); } + if trap_keys.contains(&si) { + return Err(crate::error::create_type_error(vm, "Proxy ownKeys trap returned duplicate entries")); + } + trap_keys.push(si); } } else { - let length_val = vm.ordinary_get(result_obj, vm.perm_intern("length"), result)?; + let length_val = vm + .ordinary_get(result_obj, vm.perm_intern("length"), result) + .map_err(|msg| own_keys_trap_exc(vm, msg))?; let len = oxide_runtime_api::to_length(length_val); for i in 0..len { // 大索引(2^30 以上)走字符串键形态(规范数字串)。 @@ -269,24 +295,69 @@ pub fn own_keys_proxy_aware<'a, H: VmHost>(vm: &mut H, obj: &'a JsObject) -> Res } else { vm.perm_intern(&i.to_string()) }; - let val = vm.ordinary_get(result_obj, key_si, result)?; - let si = vm.to_property_key_si(val)?; - if !trap_keys.contains(&si) { - trap_keys.push(si); + let val = vm.ordinary_get(result_obj, key_si, result).map_err(|msg| own_keys_trap_exc(vm, msg))?; + let si = vm.to_property_key_si(val).map_err(|msg| own_keys_trap_exc(vm, msg))?; + if !val.is_string() && !val.is_symbol() { + return Err(crate::error::create_type_error( + vm, + "Proxy ownKeys trap returned a key that is not a string or symbol", + )); + } + if trap_keys.contains(&si) { + return Err(crate::error::create_type_error(vm, "Proxy ownKeys trap returned duplicate entries")); + } + trap_keys.push(si); + } + } + // 规范步 11-22:target 自身键按可配置性分两档——不可配置键无论 target + // 是否可扩展都须在 trap 结果中(缺失抛 TypeError);target 可扩展时余键 + // 不受限,不可扩展时 target 全部键须齐全且 trap 结果不得含 target 之外 + // 的键(两侧缺失均抛 TypeError)。 + let mut non_configurable: Vec = Vec::new(); + let mut configurable: Vec = Vec::new(); + for (si, store) in &target_keys { + let non_cfg = storage + .prop_meta_at(*store) + .map(|m| !m.attributes.configurable()) + .unwrap_or(false); + if non_cfg { + non_configurable.push(*si); + } else { + configurable.push(*si); + } + } + for si in &non_configurable { + if !trap_keys.contains(si) { + return Err(crate::error::create_type_error( + vm, + "Proxy ownKeys trap omitted a non-configurable target key", + )); + } + } + if !proxy_is_extensible(vm, target_obj).map_err(|msg| own_keys_trap_exc(vm, msg))? { + for si in &configurable { + if !trap_keys.contains(si) { + return Err(crate::error::create_type_error( + vm, + "Proxy ownKeys trap omitted a key of a non-extensible target", + )); + } + } + for si in &trap_keys { + if !target_keys.iter().any(|(k, _)| k == si) { + return Err(crate::error::create_type_error( + vm, + "Proxy ownKeys trap returned a key not present on a non-extensible target", + )); } } } - // 合并:trap 序优先,target 键补尾;target 上没有的键丢弃。 - let mut keys: Vec<(u32, u32)> = Vec::with_capacity(trap_keys.len() + target_keys.len()); + // 返 trap 结果列表(trap 序):target 上有的键定位存储下标,target 上不 + // 有的键用哨兵下标(无槽位,meta 读恒 None,消费方按键路由描述符读)。 + let mut keys: Vec<(u32, u32)> = Vec::with_capacity(trap_keys.len()); for si in &trap_keys { - if let Some(store) = target_keys.iter().find(|(k, _)| k == si).map(|(_, s)| *s) { - keys.push((*si, store)); - } - } - for (si, store) in target_keys { - if !trap_keys.contains(&si) { - keys.push((si, store)); - } + let store = target_keys.iter().find(|(k, _)| k == si).map(|(_, s)| *s).unwrap_or(u32::MAX); + keys.push((*si, store)); } Ok((keys, storage)) } @@ -377,7 +448,7 @@ pub fn object_get_own_property_symbols(vm: &mut H, args: &[u8]) -> Na // 代理经 ownKeys trap 路由(缺失落穿 target);本入口只取 Symbol 键。 let (keys, _storage) = match own_keys_proxy_aware(vm, obj) { Ok(k) => k, - Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)), + Err(exc) => return NativeResult::Err(exc), }; let symbols: Vec = keys .into_iter() @@ -797,7 +868,7 @@ pub fn object_keys(vm: &mut H, args: &[u8]) -> NativeResult { // Symbol 键在收集器内与字符串键同序,须在此过滤。 let (keys, storage) = match own_keys_proxy_aware(vm, obj) { Ok(k) => k, - Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)), + Err(exc) => return NativeResult::Err(exc), }; let owned_keys: Vec<(u32, u32)> = keys .into_iter() @@ -910,25 +981,29 @@ pub fn object_assign(vm: &mut H, args: &[u8]) -> NativeResult { 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 }; - // 源为代理时经 ownKeys trap 路由(缺失落穿 target);meta 读经存储对象。 - let (keys, storage) = match own_keys_proxy_aware(vm, source) { - Ok(k) => k, - Err(err) => return NativeResult::Err(crate::error::create_from_text(vm, &err)), - }; - keys.into_iter() - // CopyDataProperties:只拷贝可枚举自身属性(无显式 meta 视为可枚举)。 - .filter(|(_si, offset)| { - storage - .prop_meta_at(*offset) - .map(|m| m.attributes.enumerable()) - .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()) - }) - .collect() + let source = unsafe { &*source_ptr }; + // 源为代理时经 ownKeys trap 路由(缺失落穿 target);trap 结果可含 + // target 上不有的键(哨兵下标,无槽位),须按键路由描述符读。 + let (source_keys, _storage) = match own_keys_proxy_aware(vm, source) { + Ok(k) => k, + Err(exc) => return NativeResult::Err(exc), }; + let enum_si = vm.perm_intern("enumerable"); for (si, _offset) in source_keys { - let source = unsafe { &*source_ptr }; + // CopyDataProperties:desc = ? from.[[GetOwnProperty]](nextKey), + // desc 缺失(代理 target 上不有的键)或不可枚举均跳过,不拷贝。 + let desc = match own_descriptor_of(vm, source, si) { + Ok(d) => d, + Err(exc) => return NativeResult::Err(exc), + }; + let Some(desc_val) = desc else { continue }; + let enumerable = match desc_field_bool(vm, desc_val, enum_si) { + Ok(e) => e, + Err(exc) => return NativeResult::Err(exc), + }; + if !enumerable { + continue; + } // ordinary_get 取值(receiver = 源对象):数据属性等价直读,访问器属性 // 触发 getter;getter 抛错按规范中断整个 assign。 let val = match vm.ordinary_get(source, si, source_val) { @@ -1277,10 +1352,7 @@ fn define_all_from_properties( let props = unsafe { &*props_ptr }; // 描述符对象为代理时经 ownKeys trap 路由(缺失落穿 target);meta 读经 // 存储对象。 - let (keys, storage) = match own_keys_proxy_aware(vm, props) { - Ok(k) => k, - Err(msg) => return Err(DefineDescFailure::Engine(msg)), - }; + let (keys, storage) = own_keys_proxy_aware(vm, props)?; // 先物化可枚举自身键(meta 读经存储对象),再逐键经 ordinary_get 取 // 描述符值(触发访问器 getter,异常原值传播)。 let enumerable: Vec = keys @@ -1362,9 +1434,13 @@ pub fn object_define_property(vm: &mut H, args: &[u8]) -> NativeResul fn own_descriptor_of(vm: &mut H, obj: &JsObject, key_si: u32) -> Result, JsValue> { // Proxy [[GetOwnProperty]]:getOwnPropertyDescriptor trap 以(target、P) // 调用;trap 缺失落穿 target 的自身描述符(target 自身为代理时经递归再 - // 路由);trap 返 undefined 表示无属性;返非对象按规范抛 TypeError。 + // 路由);trap 返 undefined 表示无属性(target 自身属性不可配置或 target + // 不可扩展时抛 TypeError);trap 返非对象按规范抛 TypeError;trap 返对象 + // 时与 target 自身描述符做不变量交叉检查(target 不可扩展时)。 if obj.is_proxy_obj() { let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target; + // SAFETY: target 由构造器校验为非空对象指针。 + let target_obj = unsafe { &*target.as_js_object_ptr() }; let key = key_si_to_js_key(vm, key_si); let trap_result = match vm.proxy_call_trap(obj, vm.perm_intern("getOwnPropertyDescriptor"), &[target, key]) { Ok(r) => r, @@ -1377,15 +1453,33 @@ fn own_descriptor_of(vm: &mut H, obj: &JsObject, key_si: u32) -> Resu }; if let Some(r) = trap_result { if r.is_undefined() { + // 规范步 10:targetDesc 缺失直接返 undefined;targetDesc 不可 + // 配置或 target 不可扩展均抛 TypeError。 + let target_desc = own_descriptor_of(vm, target_obj, key_si)?; + if let Some(td) = target_desc { + if !desc_field_bool(vm, td, vm.perm_intern("configurable"))? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap returned undefined for a non-configurable target property", + )); + } + } + if !proxy_is_extensible(vm, target_obj).map_err(|msg| crate::error::create_from_text(vm, &msg))? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap returned undefined for a non-extensible target", + )); + } return Ok(None); } if !r.is_object() { return Err(crate::error::create_type_error(vm, "getOwnPropertyDescriptor trap returned a non-object")); } + proxy_gopd_invariant_check(vm, target_obj, key_si, r)?; return Ok(Some(r)); } // SAFETY: target 由构造器校验为非空对象指针。 - return own_descriptor_of(vm, unsafe { &*target.as_js_object_ptr() }, key_si); + return own_descriptor_of(vm, target_obj, key_si); } // deferred namespace 求值触发([[GetOwnProperty]],symbol-like 键内部 no-op)。 // 触发失败时 uncaught 侧通道已还原为原错误值,原值重抛保身份。 @@ -1469,6 +1563,149 @@ fn own_descriptor_of(vm: &mut H, obj: &JsObject, key_si: u32) -> Resu Ok(Some(JsValue::from_js_object(desc))) } +/// ToPropertyDescriptor 布尔字段读:字段缺失按 false(规范默认值),存在时 +/// 经 ToBoolean 投影。 +fn desc_field_bool(vm: &mut H, desc: JsValue, prop_si: u32) -> Result { + match own_field(vm, desc, prop_si)? { + Some(v) => Ok(oxide_runtime_api::to_boolean(v)), + None => Ok(false), + } +} + +/// [[IsExtensible]](代理感知):代理经 isExtensible trap 路由(实参 target, +/// 结果经 ToBoolean 投影,trap 报告须与 target 实际可扩展状态一致,不一致抛 +/// TypeError),trap 缺失落穿 target;普通对象读自身标志。 +fn proxy_is_extensible(vm: &mut H, obj: &JsObject) -> Result { + if !obj.is_proxy_obj() { + return Ok(obj.is_extensible()); + } + let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target; + // SAFETY: target 由构造器校验为非空对象指针。 + let target_obj = unsafe { &*target.as_js_object_ptr() }; + let trap_result = vm.proxy_call_trap(obj, vm.perm_intern("isExtensible"), &[target])?; + let Some(r) = trap_result else { + return proxy_is_extensible(vm, target_obj); + }; + let result = oxide_runtime_api::to_boolean(r); + if result != proxy_is_extensible(vm, target_obj)? { + return Err(oxide_runtime_api::format_error_message( + "TypeError", + "Proxy isExtensible trap result is inconsistent with target", + )); + } + Ok(result) +} + +/// Proxy [[GetOwnProperty]] trap 返回值的不变量检查(规范 [[GetOwnProperty]] +/// 步 11-16 形态):target 不可扩展时经 `IsCompatiblePropertyDescriptor` 全量 +/// 交叉检查(target 自身属性必须存在、不得报告可配置、enumerable 须一致、 +/// 访问器 get/set 须一致、数据描述符不得由可写放宽为不可写、形态须一致); +/// 结果描述符不可配置时 target 自身属性必须存在且不可配置,且数据描述符不得 +/// 由可写放宽为不可写(与可扩展性无关)。违反抛 TypeError。 +fn proxy_gopd_invariant_check( + vm: &mut H, target: &JsObject, key_si: u32, result_desc: JsValue, +) -> Result<(), JsValue> { + let cfg_si = vm.perm_intern("configurable"); + let enum_si = vm.perm_intern("enumerable"); + let get_si = vm.perm_intern("get"); + let set_si = vm.perm_intern("set"); + let writable_si = vm.perm_intern("writable"); + let extensible = proxy_is_extensible(vm, target).map_err(|msg| crate::error::create_from_text(vm, &msg))?; + let target_desc = own_descriptor_of(vm, target, key_si)?; + // IsCompatiblePropertyDescriptor:仅 target 不可扩展时执行。 + if !extensible { + let Some(td) = target_desc else { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a property that does not exist on a non-extensible target", + )); + }; + if desc_field_bool(vm, result_desc, cfg_si)? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a configurable descriptor for a non-extensible target property", + )); + } + if desc_field_bool(vm, result_desc, enum_si)? != desc_field_bool(vm, td, enum_si)? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a different enumerable flag for a non-configurable target property", + )); + } + let result_get = own_field(vm, result_desc, get_si)?; + let target_get = own_field(vm, td, get_si)?; + if target_get.is_some() { + if result_get.is_none() || result_get != target_get { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a different getter for a non-configurable target accessor", + )); + } + let target_set = own_field(vm, td, set_si)?; + let result_set = own_field(vm, result_desc, set_si)?; + if target_set.is_some() { + if result_set.is_none() || result_set != target_set { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a different setter for a non-configurable target accessor", + )); + } + } else if result_set.is_some() { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a setter for a target accessor without setter", + )); + } + } else { + if result_get.is_some() { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a getter for a non-configurable target data property", + )); + } + if own_field(vm, result_desc, set_si)?.is_some() { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a setter for a non-configurable target data property", + )); + } + if !desc_field_bool(vm, result_desc, writable_si)? && desc_field_bool(vm, td, writable_si)? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a non-writable descriptor for a writable target property", + )); + } + } + } + // 结果描述符不可配置:target 自身属性必须存在且不可配置(与可扩展性无关); + // 数据描述符不得由可写放宽为不可写。 + if !desc_field_bool(vm, result_desc, cfg_si)? { + match target_desc { + None => { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a non-configurable descriptor for a property that does not exist on the target", + )); + } + Some(td) => { + if desc_field_bool(vm, td, cfg_si)? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a non-configurable descriptor for a configurable target property", + )); + } + if !desc_field_bool(vm, result_desc, writable_si)? && desc_field_bool(vm, td, writable_si)? { + return Err(crate::error::create_type_error( + vm, + "getOwnPropertyDescriptor trap reported a non-writable descriptor for a writable target property", + )); + } + } + } + } + Ok(()) +} + /// 分配空描述符对象(proto = %Object.prototype%)。 fn alloc_desc_object(vm: &mut H) -> *mut JsObject { let desc_proto = vm.builtin_proto(ProtoKind::ObjectProto); @@ -1540,7 +1777,7 @@ pub fn object_get_own_property_descriptors(vm: &mut H, args: &[u8]) - // 代理经 ownKeys trap 路由(缺失落穿 target)。 let (keys, _storage) = match own_keys_proxy_aware(vm, obj) { Ok(k) => k, - Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)), + Err(exc) => return NativeResult::Err(exc), }; // 结果对象:proto = %Object.prototype%。 @@ -1667,8 +1904,95 @@ fn seal_own_prop_meta(obj: &mut JsObject, store: u32) { } } +/// 代理 [[PreventExtensions]](规范 ProxyPreventExtensions):preventExtensions +/// trap 以(target)调用,结果经 ToBoolean 投影后须与 target 实际可扩展状态 +/// 一致(不一致抛 TypeError);trap 缺失落穿 target 的普通 preventExtensions +/// (置不可扩展标志,返 true)。 +fn proxy_prevent_extensions(vm: &mut H, obj: &JsObject) -> Result { + let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target; + // SAFETY: target 由构造器校验为非空对象指针。 + let target_obj = unsafe { &*target.as_js_object_ptr() }; + let trap_result = + match vm.proxy_call_trap(obj, vm.perm_intern("preventExtensions"), &[target]) { + Ok(r) => r, + Err(msg) => return Err(own_keys_trap_exc(vm, msg)), + }; + let Some(r) = trap_result else { + // OrdinaryPreventExtensions(target):置不可扩展标志。 + // SAFETY: target 由构造器校验为非空对象指针。 + unsafe { &mut *target.as_js_object_ptr() }.set_extensible(false); + return Ok(true); + }; + let result = oxide_runtime_api::to_boolean(r); + let target_extensible = + proxy_is_extensible(vm, target_obj).map_err(|msg| crate::error::create_from_text(vm, &msg))?; + if result != target_extensible { + return Err(crate::error::create_type_error( + vm, + "Proxy preventExtensions trap result is inconsistent with target", + )); + } + Ok(result) +} + +/// 代理完整性级别(规范 SetIntegrityLevel):[[PreventExtensions]](代理感知) +/// 后 [[OwnPropertyKeys]](代理感知),逐键 DefinePropertyOrThrow(经 +/// defineProperty trap 路由,trap 缺失落穿 target)。seal 仅置 configurable: +/// false;freeze 对数据属性额外置 writable: false(规范步 7b 先读 currentDesc +/// 区分数据/访问器,访问器不写 writable 字段)。 +fn proxy_set_integrity_level( + vm: &mut H, obj_ptr: *mut JsObject, frozen: bool, +) -> Result<(), JsValue> { + // SAFETY: obj_ptr 为存活对象指针。 + let obj = unsafe { &*obj_ptr }; + proxy_prevent_extensions(vm, obj)?; + let (keys, _storage) = own_keys_proxy_aware(vm, obj)?; + let cfg_si = vm.perm_intern("configurable"); + let writable_si = vm.perm_intern("writable"); + let get_si = vm.perm_intern("get"); + let set_si = vm.perm_intern("set"); + for (si, _pos) in keys { + // freeze 先读 currentDesc 区分数据与访问器(规范步 7b);seal 的 desc + // 恒为 {Configurable: false},无需读描述符。 + let is_accessor = if frozen { + match own_descriptor_of(vm, obj, si)? { + Some(desc) => { + own_field(vm, desc, get_si)?.is_some() || own_field(vm, desc, set_si)?.is_some() + } + None => false, + } + } else { + false + }; + let desc = alloc_desc_object(vm); + let sh_ptr = vm.shape_forge_ptr() as *const ShapeForge; + // SAFETY: desc 为本函数刚分配的 epoch 对象;sh 为 kernel 永久引用。 + let d: &mut JsObject = unsafe { &mut *desc }; + let sh = unsafe { &*sh_ptr }; + push_desc_prop(d, sh, cfg_si, JsValue::bool(false)); + if frozen && !is_accessor { + push_desc_prop(d, sh, writable_si, JsValue::bool(false)); + } + match define_from_descriptor(vm, obj_ptr, si, JsValue::from_js_object(desc)) { + Ok(()) => {} + Err(DefineDescFailure::User(exc)) => return Err(exc), + Err(DefineDescFailure::Engine(msg)) => { + // 定义期用户代码(Proxy trap)抛出的原始异常值原值重抛。 + if let Some(exc) = vm.take_uncaught_value() { + return Err(exc); + } + return Err(crate::error::create_define_failure(vm, &msg)); + } + } + } + Ok(()) +} + /// `Object.freeze(obj)`:冻结对象(不可扩展 + 全部属性不可配置/不可写),返回原对象。 /// +/// 代理按规范 SetIntegrityLevel 走通用完整性路径(preventExtensions + ownKeys + +/// defineProperty 均代理感知),不经 freeze trap(freeze trap 仅 Reflect 通道使用)。 +/// /// # 副作用 /// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续 /// 写/define 一律回落 ordinary_set / define 检查(writable/configurable 判定) @@ -1682,6 +2006,12 @@ pub fn object_freeze(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(val); } let obj_ptr = val.as_js_object_ptr(); + if unsafe { &*obj_ptr }.is_proxy_obj() { + return match proxy_set_integrity_level(vm, obj_ptr, true) { + Ok(()) => NativeResult::Ok(val), + Err(exc) => NativeResult::Err(exc), + }; + } // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 if let Err(exc) = ensure_deferred_own_keys(vm, unsafe { &*obj_ptr }) { return NativeResult::Err(exc); @@ -1723,6 +2053,9 @@ pub fn object_freeze(vm: &mut H, args: &[u8]) -> NativeResult { /// `Object.seal(obj)`:密封对象(不可扩展 + 全部属性不可配置),返回原对象。 /// +/// 代理按规范 SetIntegrityLevel 走通用完整性路径(preventExtensions + ownKeys + +/// defineProperty 均代理感知),不经 seal trap(seal trap 仅 Reflect 通道使用)。 +/// /// # 副作用 /// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续 /// define 回落 non-configurable 检查(已有属性写仍可正常进行) @@ -1736,6 +2069,12 @@ pub fn object_seal(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(val); } let obj_ptr = val.as_js_object_ptr(); + if unsafe { &*obj_ptr }.is_proxy_obj() { + return match proxy_set_integrity_level(vm, obj_ptr, false) { + Ok(()) => NativeResult::Ok(val), + Err(exc) => NativeResult::Err(exc), + }; + } // [[OwnPropertyKeys]] 触发(deferred namespace 先求值,cyclic 抛错)。 if let Err(exc) = ensure_deferred_own_keys(vm, unsafe { &*obj_ptr }) { return NativeResult::Err(exc); @@ -1894,7 +2233,7 @@ pub fn object_get_own_property_names(vm: &mut H, args: &[u8]) -> Nati // getOwnPropertySymbols,须在此过滤。 let (keys, _storage) = match own_keys_proxy_aware(vm, obj) { Ok(k) => k, - Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)), + Err(exc) => return NativeResult::Err(exc), }; let keys = keys.into_iter().filter(|(si, _)| !is_symbol_key(*si)).collect::>(); let n = keys.len(); @@ -2366,9 +2705,8 @@ pub fn object_proto_to_string(vm: &mut H, args: &[u8]) -> NativeResul /// # 步骤 /// 1. 先 ToPropertyKey 求键(spec 顺序:键先于 ToObject)。 /// 2. ToObject 装箱 this(null/undefined 抛 TypeError,原始值装箱后查 own 槽)。 -/// -/// # 边界与前提 -/// - TypedArray 界内整数键经统一数值键门预支恒 true,不查形状槽。 +/// 3. 经 `? [[GetOwnProperty]]` 判定(代理感知:trap 路由与落穿、TA 数值键门、 +/// 未初始化命名空间导出抛 ReferenceError)。 pub fn object_proto_has_own_property(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); @@ -2388,25 +2726,13 @@ pub fn object_proto_has_own_property(vm: &mut H, args: &[u8]) -> Nati Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), }; let obj = unsafe { &*obj_val.as_js_object_ptr() }; - // 统一数值键门(exotic [[HasOwnProperty]]):界内整数键恒存在;数字无效 - // 与非数字串键落下方槽位判定(命名空间检查在门臂之后,TA 非命名空间 - // 不触达)。 - if obj.is_typed_array_obj() - && matches!( - crate::typed_array::ta_index_gate(vm, obj, key_si), - crate::typed_array::TaIndexGate::NumericValid(_) - ) - { - return NativeResult::Ok(JsValue::bool(true)); + // HasOwnProperty 经 `? [[GetOwnProperty]]`(代理感知:trap 路由与落穿、 + // TA 数值键门、未初始化命名空间导出抛 ReferenceError)。 + match own_descriptor_of(vm, obj, key_si) { + Ok(Some(_)) => NativeResult::Ok(JsValue::bool(true)), + Ok(None) => NativeResult::Ok(JsValue::bool(false)), + Err(exc) => NativeResult::Err(exc), } - if vm.get_own_property_slot(obj, key_si).is_none() { - return NativeResult::Ok(JsValue::bool(false)); - } - // HasOwnProperty 经 `? [[GetOwnProperty]]`:未初始化导出抛 ReferenceError。 - if let Err(msg) = namespace_export_get(obj, key_si) { - return NativeResult::Err(crate::error::create_reference_error(vm, msg)); - } - NativeResult::Ok(JsValue::bool(true)) } /// `Object.prototype.propertyIsEnumerable(key)`:指定自身属性是否可枚举。 @@ -2414,10 +2740,8 @@ pub fn object_proto_has_own_property(vm: &mut H, args: &[u8]) -> Nati /// # 步骤 /// 1. 先 ToPropertyKey 求键。 /// 2. ToObject 装箱 this(null/undefined 抛 TypeError)。 -/// -/// # 边界与前提 -/// - TypedArray 界内整数键经统一数值键门预支恒 true(enumerable 恒真), -/// 不读 meta。 +/// 3. 先经 `? [[GetOwnProperty]]`(代理感知)取描述符,缺失返 false, +/// 存在时读 enumerable 位(TA 界内整数键经数值键门恒 true)。 pub fn object_proto_property_is_enumerable(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::bool(false)); @@ -2437,28 +2761,19 @@ pub fn object_proto_property_is_enumerable(vm: &mut H, args: &[u8]) - Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)), }; let obj = unsafe { &*obj_val.as_js_object_ptr() }; - // 统一数值键门(exotic PropertyIsEnumerable):界内整数键恒可枚举; - // 数字无效与非数字串键落下方 meta 读取。 - if obj.is_typed_array_obj() - && matches!( - crate::typed_array::ta_index_gate(vm, obj, key_si), - crate::typed_array::TaIndexGate::NumericValid(_) - ) - { - return NativeResult::Ok(JsValue::bool(true)); + // PropertyIsEnumerable 先经 `? [[GetOwnProperty]]`(代理感知)再取 + // enumerable 位。 + match own_descriptor_of(vm, obj, key_si) { + Ok(Some(desc)) => { + let enumerable = match desc_field_bool(vm, desc, vm.perm_intern("enumerable")) { + Ok(b) => b, + Err(exc) => return NativeResult::Err(exc), + }; + NativeResult::Ok(JsValue::bool(enumerable)) + } + Ok(None) => NativeResult::Ok(JsValue::bool(false)), + Err(exc) => NativeResult::Err(exc), } - let Some(pos) = vm.get_own_property_slot(obj, key_si) else { - return NativeResult::Ok(JsValue::bool(false)); - }; - // PropertyIsEnumerable 同样先经 `? [[GetOwnProperty]]` 再取 enumerable 位。 - if let Err(msg) = namespace_export_get(obj, key_si) { - return NativeResult::Err(crate::error::create_reference_error(vm, msg)); - } - let enumerable = obj - .prop_meta_at(pos) - .map(|meta| meta.attributes.enumerable()) - .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); - NativeResult::Ok(JsValue::bool(enumerable)) } /// `Object.prototype.__defineGetter__(key, getter)`:把 key 定义为访问器属性, @@ -2529,7 +2844,7 @@ pub fn object_entries(vm: &mut H, args: &[u8]) -> NativeResult { // (accessor 的 this 指向存储对象)。 let (keys, storage) = match own_keys_proxy_aware(vm, obj) { Ok(k) => k, - Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)), + Err(exc) => return NativeResult::Err(exc), }; let owned_keys: Vec<(u32, u32)> = keys .into_iter() @@ -2719,7 +3034,7 @@ pub fn object_values(vm: &mut H, args: &[u8]) -> NativeResult { // (accessor 的 this 指向存储对象)。 let (keys, storage) = match own_keys_proxy_aware(vm, obj) { Ok(k) => k, - Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)), + Err(exc) => return NativeResult::Err(exc), }; 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 37677d8d..eb421be7 100644 --- a/crates/oxide_builtins/src/reflect.rs +++ b/crates/oxide_builtins/src/reflect.rs @@ -264,7 +264,7 @@ pub fn reflect_own_keys(vm: &mut H, args: &[u8]) -> NativeResult { // 字符串键同序(整数键升序、字符串键插入序、Symbol 键插入序)。 let (mut keys, storage) = match crate::object::own_keys_proxy_aware(vm, target) { Ok(k) => k, - Err(msg) => return NativeResult::Err(crate::error::create_from_text(vm, &msg)), + Err(exc) => return NativeResult::Err(exc), }; // 数组 length 是虚拟自身属性(无 shape 槽、不在形状链):注入字符串组首 // (创建序第一枚字符串键),存储下标哨兵 u32::MAX = 无槽位(消费端只读 diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index a6c21a4d..ced978d1 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -517,14 +517,16 @@ pub fn regexp_constructor(vm: &mut H, args: &[u8]) -> NativeResult { } } - // lastIndex 作实例自身属性写入(缺省描述符,与既有构造器口径一致)。 + // lastIndex 作实例自身属性写入(规范 22.2.7.1:writable:true / + // enumerable:false / configurable:false)。 { let li_si = vm.perm_intern("lastIndex"); let shape_id = vm.make_shape(unsafe { (*this_ptr).shape_id() }, li_si); unsafe { let obj = &mut *this_ptr; obj.set_shape_id(shape_id); - obj.push_prop(JsValue::int(0)); + let li_pos = obj.push_prop(JsValue::int(0)); + obj.set_data_meta(li_pos, oxide_types::object::PropAttributes::new(true, false, false)); obj.type_tag = JsObject::OBJ_TYPE_REGEXP; // source/flags 存实例字段:原型访问器从本字段读取,不作为自身属性枚举。 obj.set_regexp_source(vm.new_string_units_owned(pattern_units)); diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 9e66114d..66295907 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -789,10 +789,18 @@ impl Vm { let key_val = self.regs[b]; let prop_name_si = self.property_key_si(key_val)?; let super_obj = unsafe { &*super_base.as_js_object_ptr() }; + let pc_before = self.pc; let val = self.ordinary_get_with_target(super_obj, prop_name_si, self.regs[a], rd as u8)?; - if self.accessor_frame_target_reg.take().is_none() { - self.regs[rd] = val; + if self.accessor_frame_target_reg.take().is_some() { + // getter 帧已入栈:结果由帧续行交付,此处不写结果寄存器。 + return Ok(true); } + if self.pc != pc_before { + // 读路径在途抛错已展开到 catch/finally 入口:pc 由处理器接管, + // 结果寄存器不得再写(regs[0] 持在途异常)。 + return Ok(true); + } + self.regs[rd] = val; Ok(false) } diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 8bd9c910..f82a76d5 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -370,11 +370,21 @@ impl Vm { // 命中会绕过 get trap 直读代理自身属性槽(恒 miss 守卫的读入口侧)。 if obj.is_proxy_obj() { let prop_name_si = self.property_key_si(self.regs[b])?; + // 先消费扩展字:代理分支不写回 IC(槽恒零),getter 帧的返回地址 + // 与展开后的 catch 入口都以 pc 接管,调用后不得再推进。 + let _ = ic_helper::read_ic_slot0(&self.bytecode, &mut self.pc); + let pc_before = self.pc; let val = self.ordinary_get_with_target(obj, prop_name_si, val, a as u8)?; - if self.accessor_frame_target_reg.take().is_none() { - self.regs[a] = val; + if self.accessor_frame_target_reg.take().is_some() { + // getter 帧已入栈:结果由帧续行交付,此处不写结果寄存器。 + return Ok(()); } - self.pc += IC_EXT_WORDS; + if self.pc != pc_before { + // 读路径在途抛错已展开到 catch/finally 入口:pc 由处理器接管, + // 结果寄存器不得再写(regs[0] 持在途异常)。 + return Ok(()); + } + self.regs[a] = val; return Ok(()); } // 入口快判:键寄存器恒为 perm 串,指针相等即键为 "length"; @@ -390,10 +400,18 @@ impl Vm { if obj.has_prop_meta() { // meta 对象整对象走慢路径,此时才解析键。 let prop_name_si = self.property_key_si(self.regs[b])?; + let pc_before = self.pc; let val = self.ordinary_get_with_target(obj, prop_name_si, val, a as u8)?; - if self.accessor_frame_target_reg.take().is_none() { - self.regs[a] = val; + if self.accessor_frame_target_reg.take().is_some() { + // getter 帧已入栈:结果由帧续行交付,此处不写结果寄存器。 + return Ok(()); } + if self.pc != pc_before { + // 读路径在途抛错已展开到 catch/finally 入口:pc 由处理器接管, + // 结果寄存器不得再写(regs[0] 持在途异常)。 + return Ok(()); + } + self.regs[a] = val; return Ok(()); } @@ -570,10 +588,18 @@ impl Vm { return Ok(()); }; let obj = unsafe { &*obj_ptr }; + let pc_before = self.pc; let val = self.ordinary_get_with_target(obj, prop_name_si, self.regs[rd], a as u8)?; - if self.accessor_frame_target_reg.take().is_none() { - self.regs[a] = val; + if self.accessor_frame_target_reg.take().is_some() { + // getter 帧已入栈:结果由帧续行交付,此处不写结果寄存器。 + return Ok(()); } + if self.pc != pc_before { + // 读路径在途抛错已展开到 catch/finally 入口:pc 由处理器接管, + // 结果寄存器不得再写(regs[0] 持在途异常)。 + return Ok(()); + } + self.regs[a] = val; Ok(()) } @@ -645,10 +671,18 @@ impl Vm { return Ok(()); }; let obj = unsafe { &*obj_ptr }; + let pc_before = self.pc; let val = self.ordinary_get_with_target(obj, prop_name_si, receiver, b as u8)?; - if self.accessor_frame_target_reg.take().is_none() { - self.regs[b] = val; + if self.accessor_frame_target_reg.take().is_some() { + // getter 帧已入栈:结果由帧续行交付,此处不写结果寄存器。 + return Ok(()); } + if self.pc != pc_before { + // 读路径在途抛错已展开到 catch/finally 入口:pc 由处理器接管, + // 结果寄存器不得再写(regs[0] 持在途异常)。 + return Ok(()); + } + self.regs[b] = val; Ok(()) } diff --git a/crates/oxide_vm/src/vm/dynamic.rs b/crates/oxide_vm/src/vm/dynamic.rs index dd4c73cb..065e4c96 100644 --- a/crates/oxide_vm/src/vm/dynamic.rs +++ b/crates/oxide_vm/src/vm/dynamic.rs @@ -98,9 +98,7 @@ impl Vm { table.immutables.resize(table.modules.len(), OnceLock::new()); table.si_tables.resize(table.modules.len(), OnceLock::new()); - // 动态函数对象置 DYNAMIC 旗:`prototype` 属性按规范不可配置 - // (区别于普通函数的可配置面)。 - let func_val = self.create_function_object(base, self.current_gen, FunctionFlags(FunctionFlags::DYNAMIC)); + let func_val = self.create_function_object(base, self.current_gen, FunctionFlags::none()); let func_obj = unsafe { &mut *func_val.as_js_object_ptr() }; let length_si = self.kernel_core.perm_interner().intern("length").0; let name_si = self.kernel_core.perm_interner().intern("name").0; diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 947ee287..497c3dac 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -565,6 +565,36 @@ impl Vm { let mut seen = std::collections::HashSet::new(); let mut current = obj_val; + // 代理 for-in:[[OwnPropertyKeys]] 经 ownKeys trap 路由(缺失落穿 + // target),[[GetOwnProperty]] 可枚举过滤经存储对象(代理本体无原型 + // 链可走,规范枚举面即自身键)。 + if current.is_object() { + let arr = unsafe { &*current.as_js_object_ptr() }; + if arr.is_proxy_obj() { + let (keys, storage) = match oxide_builtins::object::own_keys_proxy_aware(self, arr) { + Ok(k) => k, + Err(exc) => return self.raise_captured(exc), + }; + for (si, store) in keys { + if is_symbol_key(si) { + continue; + } + let enumerable = storage + .prop_meta_at(store) + .map(|m| m.attributes.enumerable()) + .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); + if !enumerable { + continue; + } + let key_val = oxide_builtins::object::key_si_to_js_value(self, si); + keys_vec.push((key_val, si)); + } + let iter = Box::into_raw(Box::new(ForInIter { keys: keys_vec, index: 0 })); + self.iters.push_for_in(iter); + return Ok(()); + } + } + // 数组把整型下标元素存在 prop_vec,而 prop_vec 不属于 shape 链。 // 单独枚举它们(ES:数组下标是可枚举字符串键,按升序排在其它自有键之前)。 // hole(删除标记)跳过——for-in 不枚举数组稀疏空洞。 diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index b8e6bebe..03b5bc94 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -108,34 +108,45 @@ impl Vm { &mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, target_reg: Option, ) -> Result { vm_trace!("ordinary_get_inner: shape={} prop_si={}", obj.shape_id(), prop_name_si); - // Proxy [[Get]]:get trap 权威(target、P、Receiver),trap 定义时其返回值 - // 即读结果;trap 缺失落穿 target 的 [[Get]](receiver 不变)。target 本身 - // 可为代理(嵌套代理),逐层递归应用同一语义直至非代理对象,再落普通读 - // 路径从该对象继续。 - let obj = if obj.is_proxy_obj() { - let mut current = obj; - loop { - let target = oxide_builtins::proxy::proxy_state_ref(current).expect("状态盒").target; - let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si); - match crate::proxy::call_trap(self, current, self.perm_intern("get"), &[target, key, receiver])? { - Some(v) => return Ok(v), - None => { - // SAFETY: target 由构造器校验为非空对象指针。 - let target_obj = unsafe { &*target.as_js_object_ptr() }; - if !target_obj.is_proxy_obj() { - break target_obj; - } - current = target_obj; - } - } - } - } else { - obj - }; let length_si = self.length_si; let mut current = Some(obj); let mut depth = 0usize; while let Some(obj) = current { + // Proxy [[Get]]:链上任意层(入口对象或原型链)为代理时,get trap + // 权威(target、P、Receiver),trap 定义时其返回值即读结果;trap 缺失 + // 落穿 target 的 [[Get]](receiver 不变)。target 本身可为代理(嵌套 + // 代理),逐层递归应用同一语义直至非代理对象,再落普通读路径从该对象 + // 继续。 + if obj.is_proxy_obj() { + let mut chain = obj; + loop { + let target = oxide_builtins::proxy::proxy_state_ref(chain).expect("状态盒").target; + let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si); + match crate::proxy::call_trap(self, chain, self.perm_intern("get"), &[target, key, receiver]) { + Ok(Some(v)) => { + // 不变量检查:target 自身属性不可配置时,不可写数据属性 + // 的 trap 结果须与 target 值 SameValue,无 getter 的访问器 + // 属性要求 trap 结果为 undefined。违反抛 TypeError:字节码 + // 上下文就地展开到外围 catch(pc 已交处理器、regs[0] 持在途 + // 异常),builtin 上下文返回格式化 Err 由调用边界恢复。 + // 检查失败须立即返回,调用方不得再写结果寄存器或推进 pc。 + self.proxy_get_invariant_check(target, prop_name_si, v, self.native_call_depth != 0)?; + return Ok(v); + } + Ok(None) => { + // SAFETY: target 由构造器校验为非空对象指针。 + let target_obj = unsafe { &*target.as_js_object_ptr() }; + if !target_obj.is_proxy_obj() { + current = Some(target_obj); + break; + } + chain = target_obj; + } + Err(e) => return self.proxy_trap_call_failure(e).map(|_| JsValue::undefined()), + } + } + continue; + } // 模块命名空间 exotic [[Get]]:条目表是权威状态,预注册但未初始化的 // 导出读抛 ReferenceError;非 live ns(无表)落普通属性路径零行为变化。 if obj.is_module_namespace() { @@ -875,6 +886,9 @@ impl Vm { ) -> Option<(JsValue, bool, PropAttributes, JsValue, JsValue)> { // SAFETY: target 由构造器校验为非空对象指针。 let target_obj = unsafe { &*target.as_js_object_ptr() }; + // target 自身为代理时其存储恒空:沿代理链落到终极 target 读自身 + // 描述符(不变量检查与属性补全均按终极 target 口径)。 + let target_obj = Self::ultimate_proxy_target(target_obj); let pos = self.get_own_property_slot(target_obj, prop_name_si)?; let meta = target_obj.prop_meta_at(pos)?; Some((target_obj.get_prop_at(pos), meta.is_accessor, meta.attributes, meta.get, meta.set)) @@ -909,6 +923,35 @@ impl Vm { Ok(()) } + /// Proxy [[Get]] trap 返回值的不变量检查(规范 [[Get]] 步 9 形态):target + /// 自身属性不可配置时,不可写数据属性要求 trap 结果与 target 值 + /// SameValue,无 getter 的访问器属性要求 trap 结果为 undefined;违反抛 + /// TypeError(builtin 上下文物化原值入 uncaught 侧通道,调用边界原值 + /// 重抛,字节码上下文就地抛可捕获异常并展开)。 + fn proxy_get_invariant_check( + &mut self, target: JsValue, prop_name_si: u32, trap_result: JsValue, builtin: bool, + ) -> Result<(), String> { + let Some((target_val, is_accessor, attrs, get, _)) = self.proxy_target_desc(target, prop_name_si) else { + return Ok(()); + }; + if !attrs.configurable() { + if is_accessor { + if get.is_undefined() && !trap_result.is_undefined() { + return self.proxy_invariant_failure( + builtin, + "Proxy get trap returned a value for a non-configurable target accessor without getter", + ); + } + } else if !attrs.writable() && !oxide_runtime_api::same_value(trap_result, target_val) { + return self.proxy_invariant_failure( + builtin, + "Proxy get trap returned a different value for a non-writable target property", + ); + } + } + Ok(()) + } + /// Proxy trap 不变量检查违反的两态出口:builtin 上下文物化 TypeError /// 写入 uncaught 侧通道(调用边界 `take_uncaught_value` 原值重抛), /// 字节码上下文就地抛可捕获异常并展开。 @@ -1119,12 +1162,13 @@ impl Vm { } /// Proxy receiver 的 `[[DefineOwnProperty]]`(defineProperty trap):trap - /// 以(target、P、部分描述符 {value: V})调用;报 true 时读 target 自身 - /// 描述符补全部分描述符(数据形态取 target 的 writable/enumerable/ - /// configurable)、做不变量检查后写 target;报 false 或 target 不可扩展 - /// 的新属性定义返 `Ok(false)`(`[[Set]]` 投影为 false)。 + /// 以(target、P、部分描述符 {value: V})调用;报 true 时按 + /// `receiver_configurable` 补全属性(可配置按完整描述符全真、不可配置 + /// 保留 target 既有属性)、做不变量检查后写 target;trap 缺失落穿 target + /// 的普通定义,同口径补全;报 false 或 target 不可扩展的新属性定义返 + /// `Ok(false)`(`[[Set]]` 投影为 false)。 fn proxy_define_on_receiver( - &mut self, receiver_proxy: &JsObject, prop_name_si: u32, val: JsValue, + &mut self, receiver_proxy: &JsObject, prop_name_si: u32, val: JsValue, receiver_configurable: bool, ) -> Result { let target = oxide_builtins::proxy::proxy_state_ref(receiver_proxy).expect("状态盒").target; let key = oxide_builtins::object::key_si_to_js_key(self, prop_name_si); @@ -1140,12 +1184,7 @@ impl Vm { }; if let Some(r) = trap_result { if crate::proxy::trap_bool_result(r, "defineProperty")? { - // 补全部分描述符:数据 targetDesc 取其 writable/enumerable/ - // configurable;targetDesc 缺失按新建数据属性(全真)。 - let completed = self - .proxy_target_desc(target, prop_name_si) - .map(|(_, _, attrs, _, _)| attrs) - .unwrap_or(oxide_types::object::PropAttributes::DEFAULT_DATA); + let completed = self.receiver_set_completed_attrs(target, prop_name_si, receiver_configurable); self.proxy_define_invariant_check( target, prop_name_si, @@ -1157,9 +1196,9 @@ impl Vm { } return Ok(false); } - // trap 缺失:落穿 target 的普通定义(完整数据描述符)。 - let full = oxide_types::object::PropAttributes::DEFAULT_DATA; - self.define_on_target_bool(target, prop_name_si, val, full) + // trap 缺失:落穿 target 的普通定义(同口径补全)。 + let completed = self.receiver_set_completed_attrs(target, prop_name_si, receiver_configurable); + self.define_on_target_bool(target, prop_name_si, val, completed) } /// CreateDataProperty(Receiver, P, V)(Receiver 为代理):defineProperty @@ -1224,11 +1263,39 @@ impl Vm { } return Ok(false); } - self.proxy_define_on_receiver(receiver_proxy, prop_name_si, val) + // receiverDesc 为可写数据属性(规范步 7):可配置按完整描述符 + // 重定义,不可配置按部分描述符 {value: V} 保留既有属性。 + let receiver_configurable = self.read_desc_configurable(desc); + self.proxy_define_on_receiver(receiver_proxy, prop_name_si, val, receiver_configurable) } } } + /// 读描述符对象的 configurable 字段(缺省视为 false:缺字段时无法确认 + /// 可配置,按不可配置分支保留既有属性,与部分描述符语义一致)。 + fn read_desc_configurable(&self, desc: JsValue) -> bool { + // SAFETY: desc 为值携带的非空对象指针。 + let desc_obj = unsafe { &*desc.as_js_object_ptr() }; + self.resolve_property(desc_obj, self.perm_intern("configurable")) + .map(oxide_runtime_api::to_boolean) + .unwrap_or(false) + } + + /// `[[Set]]` receiver 定义补全属性:可配置 receiverDesc 按完整描述符 + /// 重定义(全真,规范步 7.b);不可配置 receiverDesc 保留 target 既有 + /// 属性(规范步 7.c 部分描述符 {value: V}),target 缺该属性时按新建 + /// 数据属性(全真)。 + fn receiver_set_completed_attrs( + &self, target: JsValue, prop_name_si: u32, receiver_configurable: bool, + ) -> PropAttributes { + if receiver_configurable { + return oxide_types::object::PropAttributes::DEFAULT_DATA; + } + self.proxy_target_desc(target, prop_name_si) + .map(|(_, _, attrs, _, _)| attrs) + .unwrap_or(oxide_types::object::PropAttributes::DEFAULT_DATA) + } + /// TypedArray 界内规范键在 `receiver` ≠ TA 时的 [[Set]] 语义: /// /// # 步骤 diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 1d9b2e08..a32f312a 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -40,9 +40,6 @@ impl FunctionFlags { pub(crate) const CLASS_CONSTRUCTOR: u8 = 0b010; /// 派生构造器标志位(bit2)。 pub(crate) const DERIVED_CONSTRUCTOR: u8 = 0b100; - /// 动态函数标志位(bit3):经 Function 构造器创建的函数对象,其 - /// `prototype` 属性不可配置。 - pub(crate) const DYNAMIC: u8 = 0b1000; /// 无标志(普通函数)。 pub(crate) const fn none() -> Self { @@ -78,11 +75,6 @@ impl FunctionFlags { pub(crate) const fn is_derived_constructor(self) -> bool { self.0 & Self::DERIVED_CONSTRUCTOR != 0 } - - /// 是否动态函数(经 Function 构造器创建)。 - pub(crate) const fn is_dynamic(self) -> bool { - self.0 & Self::DYNAMIC != 0 - } } /// 模块级装载缓存条目三件套:已转换常量池、BigInt 自持存储、si 侧表。 @@ -903,21 +895,16 @@ impl Vm { prototype.bump_generation(); } - // 函数自身 `prototype` 属性:生成器与动态函数(Function 构造器产物) - // writable:true / enumerable:false / configurable:false;普通函数与 - // 构造器 writable:true / enumerable:false / configurable:true。显式 - // 落元数据——缺元数据按默认描述符(可枚举)判定,`prototype` 会泄漏 - // 进 keys / for-in / 聚合枚举。 + // 函数自身 `prototype` 属性:writable:true / enumerable:false / + // configurable:false(规范 10.2.1 口径,生成器与动态函数同口径)。 + // 显式落元数据——缺元数据按默认描述符(可枚举)判定,`prototype` + // 会泄漏进 keys / for-in / 聚合枚举。 let prototype_si = self.kernel_core.perm_interner().intern("prototype").0; let func = unsafe { &mut *obj_ptr }; let prototype_shape = self.kernel_core.shape_forge().make_shape(func.shape_id(), prototype_si); func.set_shape_id(prototype_shape); let prototype_pos = func.push_prop(prototype_val); - if is_generator || is_async_generator || flags.is_dynamic() { - func.set_data_meta(prototype_pos, PropAttributes::new(true, false, false)); - } else { - func.set_data_meta(prototype_pos, PropAttributes::new(true, false, true)); - } + func.set_data_meta(prototype_pos, PropAttributes::new(true, false, false)); func.bump_generation(); } diff --git a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs index dc38610f..d02f1947 100644 --- a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs +++ b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs @@ -1273,9 +1273,10 @@ fn sab_new_target_proto_and_fallback() { var d = Reflect.construct(SharedArrayBuffer, [4], D); \ if (Object.getPrototypeOf(d) !== SharedArrayBuffer.prototype) { return false; } \ var E = function () {}; \ - Object.defineProperty(E, 'prototype', { get: function () { throw new RangeError('x'); } }); \ + var e_proxy = new Proxy(E, { get: function (t, k) { \ + if (k === 'prototype') { throw new RangeError('x'); } return t[k]; } }); \ var threw = false; \ - try { Reflect.construct(SharedArrayBuffer, [1], E); } catch (e) { threw = e instanceof RangeError; } \ + try { Reflect.construct(SharedArrayBuffer, [1], e_proxy); } catch (e) { threw = e instanceof RangeError; } \ return threw; })()", ) .unwrap(); diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index da06b9bf..e7655a0f 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -483,9 +483,9 @@ fn function_prototype_new_throws_catchable_type_error() { #[test] fn dynamic_function_prototype_descriptor_not_configurable() { - // 动态函数(Function 构造器产物)的 prototype 属性为 - // {writable:true, enumerable:false, configurable:false},delete 失败且值不变; - // 普通函数的 prototype 属性保持可配置,不回归。 + // 动态函数(Function 构造器产物)与普通函数的 prototype 属性均为 + // {writable:true, enumerable:false, configurable:false}(规范 10.2.1 口径), + // delete 失败且值不变。 let mut vm = Vm::new(); vm.set_compiler_service(Arc::new(DefaultCompilerService)); let result = eval( @@ -505,7 +505,14 @@ fn dynamic_function_prototype_descriptor_not_configurable() { "function g() {} var d = Object.getOwnPropertyDescriptor(g, 'prototype'); d.writable + ',' + d.enumerable + ',' + d.configurable", ) .unwrap(); - assert_eq!(vm.lookup_str(result).unwrap_or_default(), "true,false,true"); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "true,false,false"); + // 普通函数同口径:delete 失败且值不变。 + let result = eval( + &mut vm, + "function h() {} var p = h.prototype; delete h.prototype; h.prototype === p", + ) + .unwrap(); + assert!(result.as_bool()); } #[test] diff --git a/crates/oxide_vm/tests/builtins_regexp_tests.rs b/crates/oxide_vm/tests/builtins_regexp_tests.rs index 7369912c..9fb712df 100644 --- a/crates/oxide_vm/tests/builtins_regexp_tests.rs +++ b/crates/oxide_vm/tests/builtins_regexp_tests.rs @@ -267,11 +267,21 @@ fn regexp_exec_negative_last_index_clamped_to_zero() { #[test] fn regexp_exec_last_index_set_once_on_success() { - // 成功路径 lastIndex 恰好 Set 一次(写回在核内单点):计数 setter 只被观察一次。 - let source = "(() => { var re = /a/g; var n = 0; Object.defineProperty(re, 'lastIndex', { configurable: true, set() { n += 1; }, get() { return 0; } }); re.exec('a'); return String(n); })()"; + // 成功路径 lastIndex 写回匹配末尾(核内单点写回的可观测口径); + // 失败路径置 0(lastIndex 规范不可配置,无法以访问器计数观察)。 let mut vm = Vm::new(); - let result = eval(&mut vm, source).unwrap(); - assert_eq!(to_str(&vm, result), "1"); + let result = eval( + &mut vm, + "(() => { var re = /a/g; var m = re.exec('a'); return m[0] + ':' + re.lastIndex; })()", + ) + .unwrap(); + assert_eq!(to_str(&vm, result), "a:1"); + let result = eval( + &mut vm, + "(() => { var re = /a/g; var r = re.exec('b'); return r === null && re.lastIndex === 0; })()", + ) + .unwrap(); + assert!(result.as_bool()); } // --- test() lastIndex 规范语义(sticky 锚定 / 越界短路 / 不追踪不写回)--- @@ -747,10 +757,10 @@ fn symbol_search_lastindex_init_and_restore() { .unwrap(); assert_eq!(to_str(&vm, result), "0/3"); // 严格 SameValue 口径:-0 与 +0 不同值,初始化 Set 0 与恢复 Set -0 - // 各触发一次写。 + // 各触发一次写(lastIndex 规范不可配置,计数经代理 set trap 观察)。 let result = eval( &mut vm, - "(() => { var r = /b/; var sets = 0; var store = -0; Object.defineProperty(r, 'lastIndex', { configurable: true, set(v) { sets += 1; store = v; }, get() { return store; } }); r.exec = function() { return null; }; r[Symbol.search](''); return sets; })()", + "(() => { var r = /b/; var sets = 0; var store = -0; var p = new Proxy(r, { set(t, k, v) { if (k === 'lastIndex') { sets += 1; store = v; } t[k] = v; return true; }, get(t, k) { if (k === 'lastIndex') { return store; } return t[k]; } }); p.exec = function() { return null; }; p[Symbol.search](''); return sets; })()", ) .unwrap(); assert!(result.as_int() == 2); diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index c7d32393..a192e391 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -2303,14 +2303,14 @@ fn typed_array_ctor_plain_call_all_forms_throw() { #[test] fn typed_array_ctor_gpf_getter_throws_propagates() { - // newTarget.prototype 访问器抛值原值传播(buffer 形 + length 形): - // 构造器体内 GpFC 自重读,不依赖通用构造机制的原型读。 + // newTarget.prototype 读取抛值原值传播(buffer 形 + length 形): + // 构造器体内 GpFC 自重读,不依赖通用构造机制的原型读。普通函数 prototype + // 规范不可配置,抛错形态经代理 get trap 表达。 let mut vm = Vm::new(); let result = eval( &mut vm, - "(function () { var nt = function () {}; \ - Object.defineProperty(nt, 'prototype', { \ - get: function () { throw new RangeError('gpf'); } }); \ + "(function () { var nt = new Proxy(function () {}, { \ + get: function (t, k) { if (k === 'prototype') { throw new RangeError('gpf'); } return t[k]; } }); \ var ab = new ArrayBuffer(4); var t1, t2; \ try { Reflect.construct(Int8Array, [ab], nt); } \ catch (e) { t1 = e instanceof RangeError && e.message === 'gpf'; } \ diff --git a/crates/oxide_vm/tests/new_target_tests.rs b/crates/oxide_vm/tests/new_target_tests.rs index 9665c098..d6cb664b 100644 --- a/crates/oxide_vm/tests/new_target_tests.rs +++ b/crates/oxide_vm/tests/new_target_tests.rs @@ -159,14 +159,15 @@ fn throwing_proto_ctor(source_tail: &str) -> String { #[test] fn gpfc_new_expression_bytecode_ctor_propagates_getter_throw() { - // 字节码构造器 prototype 为访问器且 getter 抛错:new 表达式须原值上抛(getter 触发)。 + // newTarget 为代理且 get trap 读 prototype 抛错:new 表达式须原值上抛 + // (getter 触发;普通函数 prototype 规范不可配置,抛错形态经代理表达)。 let mut vm = Vm::new(); let result = eval( &mut vm, "var F = function(){}; var calls = 0; \ - Object.defineProperty(F, 'prototype', { configurable: true, \ - get: function() { calls++; throw new Error('boom'); } }); \ - try { new F(); } catch (e) { e.message + ':' + calls }", + var p = new Proxy(F, { get: function(t, k) { \ + if (k === 'prototype') { calls++; throw new Error('boom'); } return t[k]; } }); \ + try { new p(); } catch (e) { e.message + ':' + calls }", ) .unwrap(); assert_eq!(vm.lookup_str(result).unwrap(), "boom:1"); @@ -179,9 +180,9 @@ fn gpfc_new_expression_spread_bytecode_ctor_propagates_getter_throw() { let result = eval( &mut vm, "var F = function(){}; var calls = 0; \ - Object.defineProperty(F, 'prototype', { configurable: true, \ - get: function() { calls++; throw new Error('boom'); } }); \ - try { new F(...[]); } catch (e) { e.message + ':' + calls }", + var p = new Proxy(F, { get: function(t, k) { \ + if (k === 'prototype') { calls++; throw new Error('boom'); } return t[k]; } }); \ + try { new p(...[]); } catch (e) { e.message + ':' + calls }", ) .unwrap(); assert_eq!(vm.lookup_str(result).unwrap(), "boom:1");