From 71c7aed8338f73fa61512938caa47d38923f12e6 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 18 Oct 2026 12:28:43 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8E=9F=E5=9E=8B=E9=9D=A2=E5=9B=9B?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E8=A1=A5=E4=B8=8D=E5=8F=98=E9=87=8F=E5=B9=B6?= =?UTF-8?q?=E6=94=B9=E5=9B=9B=E5=85=A5=E5=8F=A3=E8=B7=AF=E7=94=B1?= 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 | 77 ++++++++---- crates/oxide_builtins/src/proxy.rs | 55 ++------- crates/oxide_builtins/src/reflect.rs | 24 +++- crates/oxide_runtime_api/src/host.rs | 19 ++- crates/oxide_vm/src/proxy.rs | 177 ++++++++++++++++++++++----- crates/oxide_vm/src/vm/vmhost.rs | 49 +++++--- 8 files changed, 293 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5753d93..e0b68980 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.20.14" +version = "0.20.15" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.20.14" +version = "0.20.15" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.20.14" +version = "0.20.15" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.20.14" +version = "0.20.15" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.20.14" +version = "0.20.15" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.20.14" +version = "0.20.15" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.20.14" +version = "0.20.15" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.20.14" +version = "0.20.15" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.20.14" +version = "0.20.15" [[package]] name = "oxide_parser" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.20.14" +version = "0.20.15" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.20.14" +version = "0.20.15" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.20.14" +version = "0.20.15" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.20.14" +version = "0.20.15" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 79d4bc0f..18e3ffd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.20.14" +version = "0.20.15" 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 c7db05a6..12be6993 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1579,7 +1579,7 @@ fn desc_field_bool(vm: &mut H, desc: JsValue, prop_si: u32) -> Result /// [[IsExtensible]](代理感知):代理经 isExtensible trap 路由(实参 target, /// 结果经 ToBoolean 投影,trap 报告须与 target 实际可扩展状态一致,不一致抛 /// TypeError),trap 缺失落穿 target;普通对象读自身标志。 -fn proxy_is_extensible(vm: &mut H, obj: &JsObject) -> Result { +pub fn proxy_is_extensible(vm: &mut H, obj: &JsObject) -> Result { if !obj.is_proxy_obj() { return Ok(obj.is_extensible()); } @@ -1943,9 +1943,10 @@ fn seal_own_prop_meta(obj: &mut JsObject, store: u32) { } /// 代理 [[PreventExtensions]](规范 ProxyPreventExtensions):preventExtensions -/// trap 以(target)调用,结果经 ToBoolean 投影后须与 target 实际可扩展状态 -/// 一致(不一致抛 TypeError);trap 缺失落穿 target 的普通 preventExtensions -/// (置不可扩展标志,返 true)。 +/// trap 以(target)调用,结果经 ToBoolean 投影;trap 报 true 而 target 可扩展 +/// 时抛 TypeError(报 false 无论 target 状态均原样返回);trap 缺失落穿 +/// target 的 [[PreventExtensions]](target 自身为代理时递归再路由,普通对象 +/// 置不可扩展标志)。 pub fn proxy_prevent_extensions(vm: &mut H, obj: &JsObject) -> Result { let target = crate::proxy::proxy_state_ref(obj).expect("状态盒").target; // SAFETY: target 由构造器校验为非空对象指针。 @@ -1955,32 +1956,45 @@ pub fn proxy_prevent_extensions(vm: &mut H, obj: &JsObject) -> Result Err(msg) => return Err(own_keys_trap_exc(vm, msg)), }; let Some(r) = trap_result else { - // OrdinaryPreventExtensions(target):置不可扩展标志。 + // 落穿 target 的 [[PreventExtensions]]:target 自身为代理时递归再路由 + // (其 trap 与落穿语义对 target 生效);普通对象置不可扩展标志。 + if target_obj.is_proxy_obj() { + return proxy_prevent_extensions(vm, target_obj); + } // 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", - )); + if result { + let target_extensible = + proxy_is_extensible(vm, target_obj).map_err(|msg| crate::error::create_from_text(vm, &msg))?; + if target_extensible { + return Err(crate::error::create_type_error( + vm, + "Proxy preventExtensions trap returned true for an extensible target", + )); + } } Ok(result) } -/// 代理完整性级别(规范 SetIntegrityLevel):[[PreventExtensions]](代理感知) -/// 后 [[OwnPropertyKeys]](代理感知),逐键 DefinePropertyOrThrow(经 -/// defineProperty trap 路由,trap 缺失落穿 target)。seal 仅置 configurable: -/// false;freeze 对数据属性额外置 writable: false(规范步 7b 先读 currentDesc -/// 区分数据/访问器,访问器不写 writable 字段)。 +/// 代理完整性级别(规范 SetIntegrityLevel):[[PreventExtensions]](代理感知, +/// 报 false 时按规范抛 TypeError)后 [[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)?; + // SetIntegrityLevel 步 3:[[PreventExtensions]] 报 false 时完整性级别 + // 返回 false,Object.freeze/seal 按规范抛 TypeError。 + if !proxy_prevent_extensions(vm, obj)? { + return Err(crate::error::create_type_error( + vm, + "SetIntegrityLevel: [[PreventExtensions]] returned false", + )); + } let (keys, _storage) = own_keys_proxy_aware(vm, obj)?; let cfg_si = vm.perm_intern("configurable"); let writable_si = vm.perm_intern("writable"); @@ -2138,6 +2152,10 @@ pub fn object_seal(vm: &mut H, args: &[u8]) -> NativeResult { } /// `Object.preventExtensions(obj)`:禁止添加新属性,返回原对象。 +/// +/// # 边界与前提 +/// - 代理经 preventExtensions trap 路由(缺失落穿 target);`[[PreventExtensions]]` +/// 报 false 时按规范抛 TypeError。 pub fn object_prevent_extensions(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Err(crate::error::create_type_error(vm, "Object.preventExtensions called on non-object")); @@ -2147,10 +2165,16 @@ pub fn object_prevent_extensions(vm: &mut H, args: &[u8]) -> NativeRe return NativeResult::Ok(val); } let obj_ptr = val.as_js_object_ptr(); - unsafe { - (*obj_ptr).set_extensible(false); + match vm.prevent_extensions(unsafe { &mut *obj_ptr }) { + Ok(true) => NativeResult::Ok(val), + // [[PreventExtensions]] 报 false:规范 20.1.2.20 步 3 抛 TypeError。 + Ok(false) => NativeResult::Err(crate::error::create_type_error( + vm, + "Object.preventExtensions: [[PreventExtensions]] returned false", + )), + // 代理 trap 异常原值传播(ReturnIfAbrupt)。 + Err(err) => NativeResult::Err(crate::array::from_engine_error(vm, &err)), } - NativeResult::Ok(val) } /// `Object.isFrozen(obj)`:对象是否冻结(检查 extensible 及全部属性 writable/configurable)。 @@ -2236,6 +2260,10 @@ pub fn object_is_sealed(vm: &mut H, args: &[u8]) -> NativeResult { } /// `Object.isExtensible(obj)`:对象是否可扩展。 +/// +/// # 边界与前提 +/// - 代理经 isExtensible trap 路由(缺失落穿 target,报告与 target 状态 +/// 不一致抛 TypeError);普通对象读自身标志。 pub fn object_is_extensible(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Err(crate::error::create_type_error(vm, "Object.isExtensible called on non-object")); @@ -2248,8 +2276,11 @@ pub fn object_is_extensible(vm: &mut H, args: &[u8]) -> NativeResult if obj_ptr.is_null() { return NativeResult::Ok(JsValue::bool(false)); } - let obj = unsafe { &*obj_ptr }; - NativeResult::Ok(JsValue::bool(obj.is_extensible())) + match vm.is_extensible(unsafe { &*obj_ptr }) { + Ok(extensible) => NativeResult::Ok(JsValue::bool(extensible)), + // 代理 trap 异常原值传播(ReturnIfAbrupt)。 + Err(err) => NativeResult::Err(crate::array::from_engine_error(vm, &err)), + } } /// `Object.getOwnPropertyNames(obj)`:返回全部自身属性名(含不可枚举)。 diff --git a/crates/oxide_builtins/src/proxy.rs b/crates/oxide_builtins/src/proxy.rs index 566c5425..28aa543b 100644 --- a/crates/oxide_builtins/src/proxy.rs +++ b/crates/oxide_builtins/src/proxy.rs @@ -70,54 +70,17 @@ fn check_target_and_handler(target: JsValue, handler: JsValue) -> Result<(), &'s Ok(()) } -/// 不变量检查(规范 10.2.4 步骤 1、4-5):target 可调用时须可扩展,handler -/// 的 isExtensible 与 setPrototypeOf trap 若存在,其布尔值须与 target 实际 -/// 状态一致,否则抛 TypeError。 +/// 不变量检查(规范 10.5.15 ProxyCreate 步 1-2):target 可调用且不可扩展时 +/// 抛 TypeError。构造器不读 handler 的 trap 属性(现代规范无构造期 trap 一致性 +/// 检查,不变量在内部方法调用期执行,getter 抛错不得在构造期触发)。 /// /// # 边界与前提 -/// - 仅当 handler 自有对应 trap 属性时检查(继承属性不触发)。 -/// - trap 读为传播读,getter 抛错原值上抛。 -fn proxy_create_invariants( - vm: &mut H, target: JsValue, target_obj: &JsObject, handler_obj: &JsObject, handler: JsValue, -) -> Result<(), JsValue> { +/// - 仅当 target 可调用时检查可扩展性。 +fn proxy_create_invariants(vm: &mut H, target: JsValue, target_obj: &JsObject) -> Result<(), JsValue> { // target 可调用且不可扩展:函数代理的不变量无法满足,抛 TypeError。 if crate::iterator::is_callable(target) && !target_obj.is_extensible() { return Err(crate::error::create_type_error(vm, "Proxy target must be extensible if it is callable")); } - // isExtensible trap:handler 报 true 时 target 须可扩展,报 false 时须不可扩展。 - let si_is_extensible = vm.perm_intern("isExtensible"); - if vm.get_own_property_slot(handler_obj, si_is_extensible).is_some() { - let trap_val = vm - .ordinary_get(handler_obj, si_is_extensible, handler) - .map_err(|e| crate::iterator::engine_error(vm, &e))?; - let target_extensible = target_obj.is_extensible(); - let trap_is_true = trap_val.is_bool() && trap_val.as_bool(); - let trap_is_false = trap_val.is_bool() && !trap_val.as_bool(); - if (trap_is_true && !target_extensible) || (trap_is_false && target_extensible) { - return Err(crate::error::create_type_error( - vm, - "Proxy invariant: isExtensible trap inconsistent with target", - )); - } - } - // setPrototypeOf trap:handler 报 true 时 target 原型须为对象或 null, - // 报 false 时须为其它类型。 - let si_set_proto = vm.perm_intern("setPrototypeOf"); - if vm.get_own_property_slot(handler_obj, si_set_proto).is_some() { - let trap_val = vm - .ordinary_get(handler_obj, si_set_proto, handler) - .map_err(|e| crate::iterator::engine_error(vm, &e))?; - let target_proto = target_obj.proto(); - let target_proto_is_obj_or_null = target_proto.is_object() || target_proto.is_null(); - let trap_is_true = trap_val.is_bool() && trap_val.as_bool(); - let trap_is_false = trap_val.is_bool() && !trap_val.as_bool(); - if (trap_is_true && !target_proto_is_obj_or_null) || (trap_is_false && target_proto_is_obj_or_null) { - return Err(crate::error::create_type_error( - vm, - "Proxy invariant: setPrototypeOf trap inconsistent with target", - )); - } - } Ok(()) } @@ -146,7 +109,7 @@ fn create_proxy_object(vm: &mut H, target: JsValue, handler: JsValue) /// # 步骤 /// 1. NewTarget 校验:非构造形态调用抛 TypeError。 /// 2. 参数校验:target 与 handler 均须对象。 -/// 3. 不变量检查:handler 的 isExtensible / setPrototypeOf trap 与 target 一致性。 +/// 3. 不变量检查:target 可调用时须可扩展(构造器不读 handler trap 属性)。 /// 4. 创建代理对象(tag 39 + 状态盒 + Object.prototype 原型链)。 /// /// # 边界与前提 @@ -164,8 +127,7 @@ pub fn proxy_constructor(vm: &mut H, args: &[u8]) -> NativeResult { } // SAFETY: check_target_and_handler 已校验非空对象指针。 let target_obj = unsafe { &*target.as_js_object_ptr() }; - let handler_obj = unsafe { &*handler.as_js_object_ptr() }; - if let Err(exc) = proxy_create_invariants(vm, target, target_obj, handler_obj, handler) { + if let Err(exc) = proxy_create_invariants(vm, target, target_obj) { return NativeResult::Err(exc); } NativeResult::Ok(create_proxy_object(vm, target, handler)) @@ -191,8 +153,7 @@ pub fn proxy_revocable(vm: &mut H, args: &[u8]) -> NativeResult { } // SAFETY: check_target_and_handler 已校验非空对象指针。 let target_obj = unsafe { &*target.as_js_object_ptr() }; - let handler_obj = unsafe { &*handler.as_js_object_ptr() }; - if let Err(exc) = proxy_create_invariants(vm, target, target_obj, handler_obj, handler) { + if let Err(exc) = proxy_create_invariants(vm, target, target_obj) { return NativeResult::Err(exc); } let proxy_val = create_proxy_object(vm, target, handler); diff --git a/crates/oxide_builtins/src/reflect.rs b/crates/oxide_builtins/src/reflect.rs index e0677d72..5c39f69e 100644 --- a/crates/oxide_builtins/src/reflect.rs +++ b/crates/oxide_builtins/src/reflect.rs @@ -233,12 +233,20 @@ pub fn reflect_has(vm: &mut H, args: &[u8]) -> NativeResult { } /// `Reflect.isExtensible(target)`:对象是否可扩展。 +/// +/// # 边界与前提 +/// - 代理经 isExtensible trap 路由(缺失落穿 target,报告与 target 状态 +/// 不一致抛 TypeError);普通对象读自身标志。 pub fn reflect_is_extensible(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.isExtensible target is not an object"); }; - NativeResult::Ok(JsValue::bool(unsafe { &*target_ptr }.is_extensible())) + match vm.is_extensible(unsafe { &*target_ptr }) { + Ok(extensible) => NativeResult::Ok(JsValue::bool(extensible)), + // 代理 trap 异常原值传播(ReturnIfAbrupt)。 + Err(msg) => NativeResult::Err(from_engine_error(vm, &msg)), + } } /// `Reflect.ownKeys(target)`:返回对象全部自身属性键,字符串键在前、Symbol 键在后。 @@ -297,14 +305,22 @@ pub fn reflect_own_keys(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(arr)) } -/// `Reflect.preventExtensions(target)`:阻止扩展,返回 true。 +/// `Reflect.preventExtensions(target)`:阻止扩展,返回 `[[PreventExtensions]]` +/// 的布尔结果。 +/// +/// # 边界与前提 +/// - 代理经 preventExtensions trap 路由(缺失落穿 target);结果按规范 +/// 原样返回(非恒 true)。 pub fn reflect_prevent_extensions(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = arg(vm, args, 1); let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.preventExtensions target is not an object"); }; - unsafe { &mut *target_ptr }.set_extensible(false); - NativeResult::Ok(JsValue::bool(true)) + match vm.prevent_extensions(unsafe { &mut *target_ptr }) { + Ok(status) => NativeResult::Ok(JsValue::bool(status)), + // 代理 trap 异常原值传播(ReturnIfAbrupt)。 + Err(msg) => NativeResult::Err(from_engine_error(vm, &msg)), + } } /// `Reflect.set(target, key, value, receiver)`:按 OrdinarySetWithReceiver 语义写入属性。 diff --git a/crates/oxide_runtime_api/src/host.rs b/crates/oxide_runtime_api/src/host.rs index 709b204e..8ff63470 100644 --- a/crates/oxide_runtime_api/src/host.rs +++ b/crates/oxide_runtime_api/src/host.rs @@ -330,7 +330,8 @@ pub trait VmHost { // 原型与扩展性面(代理感知路由:代理经 trap 路由、非代理委托固有语义) /// `[[GetPrototypeOf]]`:返回对象的 prototype(对象或 null);代理经 /// getPrototypeOf trap 路由——缺失落穿 target 的 `[[GetPrototypeOf]]`, - /// trap 返值须为对象或 null,否则抛 TypeError。 + /// trap 返值须为对象或 null,否则抛 TypeError;target 不可扩展时 trap + /// 结果须与 target 原型 SameValue,否则抛 TypeError。 /// /// # 边界与前提 /// - `obj` 须为存活对象;代理的 target 由构造器校验为非空对象指针。 @@ -352,18 +353,24 @@ pub trait VmHost { /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 fn set_prototype_of(&mut self, obj: &mut JsObject, proto: JsValue) -> Result; /// `[[IsExtensible]]`:返回可扩展标志;代理经 isExtensible trap 路由 - /// (结果经布尔投影),trap 缺失落穿 target。 + /// (结果经布尔投影,报告须与 target 实际可扩展状态一致,不一致抛 + /// TypeError),trap 缺失落穿 target。 /// /// # 副作用 /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 fn is_extensible(&mut self, obj: &JsObject) -> Result; - /// `[[PreventExtensions]]`:置不可扩展标志;代理经 preventExtensions - /// trap 路由(结果经布尔投影,报告须与 target 实际可扩展状态一致), - /// trap 缺失落穿 target 的普通 preventExtensions。 + /// `[[PreventExtensions]]`:置不可扩展标志并返回状态(普通对象恒 true; + /// 代理经 preventExtensions trap 路由,结果经布尔投影,trap 报 true 而 + /// target 可扩展时抛 TypeError),trap 缺失落穿 target 的普通 + /// preventExtensions。 + /// + /// # 返回值 + /// - `Ok(true)` 成功置不可扩展;`Ok(false)` 代理 trap 报 false(未置位); + /// `Err` 为 TypeError 或 trap 异常。 /// /// # 副作用 /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 - fn prevent_extensions(&mut self, obj: &mut JsObject) -> Result<(), String>; + fn prevent_extensions(&mut self, obj: &mut JsObject) -> Result; /// `[[OwnPropertyKeys]]`:返回自身属性键列表(JS 可见键值,整数键升序 /// 在前、字符串键插入序、Symbol 键插入序);代理经 ownKeys trap 路由 /// (缺失落穿 target 自身键,重复键与不可配置键做不变量检查)。 diff --git a/crates/oxide_vm/src/proxy.rs b/crates/oxide_vm/src/proxy.rs index c81580d2..c084b8ad 100644 --- a/crates/oxide_vm/src/proxy.rs +++ b/crates/oxide_vm/src/proxy.rs @@ -704,12 +704,41 @@ mod tests { "isExtensible trap 须命中" ); - // preventExtensions:trap 报 true(与 target 可扩展状态一致)。 + // ownKeys:trap 返数组,原样透传。 + let keys = vm.own_keys(proxy_ref).expect("own_keys 应成功"); + assert_eq!(keys.len(), 1, "ownKeys trap 返值须为单元素数组"); + assert_eq!(vm.lookup_str(keys[0]).as_deref(), Some("k"), "ownKeys trap 返值须原样透传"); + assert_eq!(vm.resolve_property(handler_ref, hits_si).unwrap(), JsValue::int(3), "ownKeys trap 须命中"); + + // getOwnPropertyDescriptor:trap 返描述符对象,原样透传。 + let desc = vm + .get_own_property_desc(proxy_ref, key_si) + .expect("get_own_property_desc 应成功"); + assert!(desc.is_some(), "getOwnPropertyDescriptor trap 须返描述符"); + assert_eq!( + vm.resolve_property(handler_ref, hits_si).unwrap(), + JsValue::int(4), + "getOwnPropertyDescriptor trap 须命中" + ); + + // deleteProperty:trap 报 true,布尔投影为 true。 + assert!( + vm.delete_own(unsafe { &mut *proxy }, key_si).expect("delete_own 应成功"), + "deleteProperty trap 报 true 须投影为 true" + ); + assert_eq!( + vm.resolve_property(handler_ref, hits_si).unwrap(), + JsValue::int(5), + "deleteProperty trap 须命中" + ); + + // preventExtensions:trap 报 true(与 target 不可扩展状态一致)。 + unsafe { (*target).set_extensible(false) }; vm.prevent_extensions(unsafe { &mut *proxy }) .expect("prevent_extensions 应成功"); assert_eq!( vm.resolve_property(handler_ref, hits_si).unwrap(), - JsValue::int(3), + JsValue::int(6), "preventExtensions trap 须命中" ); @@ -719,38 +748,10 @@ mod tests { .expect("set_prototype_of 应成功"), "setPrototypeOf trap 报 true 须投影为 true" ); - assert_eq!( - vm.resolve_property(handler_ref, hits_si).unwrap(), - JsValue::int(4), - "setPrototypeOf trap 须命中" - ); - - // ownKeys:trap 返数组,原样透传。 - let keys = vm.own_keys(proxy_ref).expect("own_keys 应成功"); - assert_eq!(keys.len(), 1, "ownKeys trap 返值须为单元素数组"); - assert_eq!(vm.lookup_str(keys[0]).as_deref(), Some("k"), "ownKeys trap 返值须原样透传"); - assert_eq!(vm.resolve_property(handler_ref, hits_si).unwrap(), JsValue::int(5), "ownKeys trap 须命中"); - - // getOwnPropertyDescriptor:trap 返描述符对象,原样透传。 - let desc = vm - .get_own_property_desc(proxy_ref, key_si) - .expect("get_own_property_desc 应成功"); - assert!(desc.is_some(), "getOwnPropertyDescriptor trap 须返描述符"); - assert_eq!( - vm.resolve_property(handler_ref, hits_si).unwrap(), - JsValue::int(6), - "getOwnPropertyDescriptor trap 须命中" - ); - - // deleteProperty:trap 报 true,布尔投影为 true。 - assert!( - vm.delete_own(unsafe { &mut *proxy }, key_si).expect("delete_own 应成功"), - "deleteProperty trap 报 true 须投影为 true" - ); assert_eq!( vm.resolve_property(handler_ref, hits_si).unwrap(), JsValue::int(7), - "deleteProperty trap 须命中" + "setPrototypeOf trap 须命中" ); } @@ -812,6 +813,120 @@ mod tests { assert!(err.contains("TypeError"), "须抛 TypeError:{err}"); } + /// setPrototypeOf 不变量(规范 10.5.2 步 10-12):trap 报 true 且 target + /// 不可扩展时,新 proto 与 target 原型非 SameValue 抛 TypeError;target + /// 可扩展时直接成功(不读 target 原型)。 + #[test] + fn set_prototype_of_invariant_by_extensibility() { + let mut vm = Vm::new(); + let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + unsafe { (*target).set_proto(JsValue::null()).expect("set_proto 应成功") }; + unsafe { (*target).set_extensible(false) }; + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let trap_fn = native_fn_obj(&mut vm, counting_trap); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("setPrototypeOf"), + JsValue::from_js_object(trap_fn), + ); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + // 异 proto:不可扩展 target 下 trap 报 true 须抛 TypeError。 + let other = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let err = vm + .set_prototype_of(unsafe { &mut *proxy }, JsValue::from_js_object(other)) + .expect_err("须抛 TypeError"); + assert!(err.contains("TypeError"), "须抛 TypeError:{err}"); + // 同 proto(null):不变量通过,返 true。 + assert!( + vm.set_prototype_of(unsafe { &mut *proxy }, JsValue::null()) + .expect("set_prototype_of 应成功"), + "同 proto 须返 true" + ); + } + + /// setPrototypeOf 可扩展早返(规范 10.5.2 步 10-11):target 可扩展时 + /// trap 报 true 直接成功,不触 target 的 [[GetPrototypeOf]](其 trap 抛 + /// 异常值验证未调用)。 + #[test] + fn set_prototype_of_extensible_target_skips_target_proto_read() { + let mut vm = Vm::new(); + // target 为代理且可扩展:其 getPrototypeOf trap 抛错。 + fn throwing_get_prototype_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult { + let exc = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + NativeResult::Err(JsValue::from_js_object(exc)) + } + let inner_target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let inner_handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let throw_fn = native_fn_obj(&mut vm, throwing_get_prototype_trap); + vm.set_or_create_prop_value( + unsafe { &mut *inner_handler }, + vm.perm_intern("getPrototypeOf"), + JsValue::from_js_object(throw_fn), + ); + let target = + proxy_with_state(&mut vm, JsValue::from_js_object(inner_target), JsValue::from_js_object(inner_handler)); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let trap_fn = native_fn_obj(&mut vm, counting_trap); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("setPrototypeOf"), + JsValue::from_js_object(trap_fn), + ); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + let new_proto = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + assert!( + vm.set_prototype_of(unsafe { &mut *proxy }, JsValue::from_js_object(new_proto)) + .expect("可扩展 target 下 trap 报 true 须成功"), + "须返 true" + ); + } + + /// preventExtensions 不变量(规范 10.5.4 步 9):trap 报 true 而 target + /// 可扩展抛 TypeError;target 不可扩展时返 true。 + #[test] + fn prevent_extensions_invariant_by_extensibility() { + let mut vm = Vm::new(); + let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let trap_fn = native_fn_obj(&mut vm, counting_trap); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("preventExtensions"), + JsValue::from_js_object(trap_fn), + ); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + // 可扩展 target + trap 报 true:抛 TypeError。 + let err = vm.prevent_extensions(unsafe { &mut *proxy }).expect_err("须抛 TypeError"); + assert!(err.contains("TypeError"), "须抛 TypeError:{err}"); + // 不可扩展 target + trap 报 true:返 true。 + unsafe { (*target).set_extensible(false) }; + assert!( + vm.prevent_extensions(unsafe { &mut *proxy }) + .expect("prevent_extensions 应成功"), + "target 不可扩展时 trap 报 true 须返 true" + ); + } + + /// getPrototypeOf 不变量(规范 10.5.1 步 9-12):target 不可扩展且 trap + /// 结果与 target 原型非 SameValue 抛 TypeError。 + #[test] + fn get_prototype_of_invariant_on_non_extensible_target() { + let mut vm = Vm::new(); + let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + unsafe { (*target).set_proto(JsValue::null()).expect("set_proto 应成功") }; + unsafe { (*target).set_extensible(false) }; + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let trap_fn = native_fn_obj(&mut vm, counting_get_prototype_trap); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("getPrototypeOf"), + JsValue::from_js_object(trap_fn), + ); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + let err = vm.get_prototype_of(unsafe { &*proxy }).expect_err("须抛 TypeError"); + assert!(err.contains("TypeError"), "须抛 TypeError:{err}"); + } + #[test] fn proxy_native_edges_return_target_and_handler() { let target = plain_obj_value(); diff --git a/crates/oxide_vm/src/vm/vmhost.rs b/crates/oxide_vm/src/vm/vmhost.rs index 6e2b0b92..8fd0d9ce 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -278,6 +278,18 @@ impl oxide_runtime_api::VmHost for Vm { "a [[GetPrototypeOf]] trap returned a non-object and non-null value", )); } + // 不变量:target 不可扩展时,trap 结果须与 target 的 + // [[GetPrototypeOf]] 结果 SameValue(规范 10.5.1 步 9-12)。 + if !self.is_extensible(unsafe { &*target.as_js_object_ptr() })? { + // SAFETY: target 由构造器校验为非空对象指针。 + let target_proto = self.get_prototype_of(unsafe { &*target.as_js_object_ptr() })?; + if !oxide_runtime_api::same_value(result, target_proto) { + return Err(oxide_runtime_api::format_error_message( + "TypeError", + "a [[GetPrototypeOf]] trap returned a value different from the non-extensible target's prototype", + )); + } + } Ok(result) } // SAFETY: target 由构造器校验为非空对象指针。 @@ -310,14 +322,18 @@ impl oxide_runtime_api::VmHost for Vm { if !crate::proxy::trap_bool_result(trap_result, "setPrototypeOf")? { return Ok(false); } - // 不变量检查:trap 报 true 时 target 的 prototype 须为对象或 null。 - // SAFETY: target 由构造器校验为非空对象指针。 - let target_proto = self.get_prototype_of(unsafe { &*target.as_js_object_ptr() })?; - if !target_proto.is_object() && !target_proto.is_null() { - return Err(oxide_runtime_api::format_error_message( - "TypeError", - "a [[SetPrototypeOf]] trap returned true for a non-object and non-null target prototype", - )); + // 不变量:trap 报 true 且 target 不可扩展时,proto 须与 target 的 + // [[GetPrototypeOf]] 结果 SameValue(规范 10.5.2 步 10-12);target + // 可扩展时直接成功,不读 target 原型。 + if !self.is_extensible(unsafe { &*target.as_js_object_ptr() })? { + // SAFETY: target 由构造器校验为非空对象指针。 + let target_proto = self.get_prototype_of(unsafe { &*target.as_js_object_ptr() })?; + if !oxide_runtime_api::same_value(proto, target_proto) { + return Err(oxide_runtime_api::format_error_message( + "TypeError", + "a [[SetPrototypeOf]] trap returned true for a non-extensible target with a different prototype", + )); + } } Ok(true) } @@ -325,24 +341,19 @@ impl oxide_runtime_api::VmHost for Vm { if !obj.is_proxy_obj() { return Ok(obj.is_extensible()); } - // 代理路径:isExtensible trap(实参 target),结果经布尔投影,缺失 - // 落穿 target(target 自身为代理时经递归再路由)。 - let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target; - match crate::proxy::call_trap(self, obj, self.perm_intern("isExtensible"), &[target])? { - Some(result) => Ok(oxide_runtime_api::to_boolean(result)), - // SAFETY: target 由构造器校验为非空对象指针。 - None => self.is_extensible(unsafe { &*target.as_js_object_ptr() }), - } + // 代理路径:isExtensible trap 路由(实参 target,结果经布尔投影, + // 报告须与 target 实际可扩展状态一致),缺失落穿 target。 + oxide_builtins::object::proxy_is_extensible(self, obj) } - fn prevent_extensions(&mut self, obj: &mut JsObject) -> Result<(), String> { + fn prevent_extensions(&mut self, obj: &mut JsObject) -> Result { if !obj.is_proxy_obj() { obj.set_extensible(false); - return Ok(()); + return Ok(true); } // 代理路径:preventExtensions trap 路由(含报告与 target 可扩展状态 // 一致性检查),缺失落穿 target 的普通 preventExtensions。 match oxide_builtins::object::proxy_prevent_extensions(self, obj) { - Ok(_) => Ok(()), + Ok(status) => Ok(status), // 还原原始异常值入 uncaught 侧通道(保身份)。 Err(exc) => { self.last_uncaught_value = Some(exc);