diff --git a/Cargo.lock b/Cargo.lock index 7d6a9dd0..0f9ea981 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.20.10" +version = "0.20.11" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.20.10" +version = "0.20.11" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.20.10" +version = "0.20.11" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.20.10" +version = "0.20.11" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.20.10" +version = "0.20.11" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.20.10" +version = "0.20.11" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.20.10" +version = "0.20.11" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.20.10" +version = "0.20.11" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.20.10" +version = "0.20.11" [[package]] name = "oxide_parser" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.20.10" +version = "0.20.11" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.20.10" +version = "0.20.11" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.20.10" +version = "0.20.11" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.20.10" +version = "0.20.11" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index f778e1f6..904bab6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.20.10" +version = "0.20.11" 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 4b66464a..632e3202 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -1911,7 +1911,7 @@ fn seal_own_prop_meta(obj: &mut JsObject, store: u32) { /// trap 以(target)调用,结果经 ToBoolean 投影后须与 target 实际可扩展状态 /// 一致(不一致抛 TypeError);trap 缺失落穿 target 的普通 preventExtensions /// (置不可扩展标志,返 true)。 -fn proxy_prevent_extensions(vm: &mut H, obj: &JsObject) -> Result { +pub 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() }; @@ -2326,7 +2326,11 @@ pub fn object_from_entries(vm: &mut H, args: &[u8]) -> NativeResult { /// `Object.getPrototypeOf(obj)`:返回对象的 prototype。 pub fn object_get_prototype_of(vm: &mut H, args: &[u8]) -> NativeResult { let obj_ptr = native_try!(require_obj_arg(vm, args, "getPrototypeOf")); - NativeResult::Ok(unsafe { (*obj_ptr).proto() }) + match vm.get_prototype_of(unsafe { &*obj_ptr }) { + Ok(proto) => NativeResult::Ok(proto), + // 代理 trap 异常原值传播(ReturnIfAbrupt)。 + Err(err) => NativeResult::Err(crate::array::from_engine_error(vm, &err)), + } } /// `Object.setPrototypeOf(obj, proto)`:设置对象的 prototype,返回原值。 @@ -2360,19 +2364,17 @@ pub fn object_set_prototype_of(vm: &mut H, args: &[u8]) -> NativeResu return NativeResult::Ok(target_val); } let target_ptr = target_val.as_js_object_ptr(); - let target = unsafe { &mut *target_ptr }; - // OrdinarySetPrototypeOf 步 2-4:新旧原型 SameValue 时直接成功,可扩展标志 - // 不参与判断;不可扩展且新旧不同才失败。set_proto 是裸写槽操作,不含该判定。 - if !target.is_extensible() && target.proto() != proto { - return NativeResult::Err(crate::error::create_type_error( + // OrdinarySetPrototypeOf 步 2-4 语义在 trait 方法内承载:新旧 SameValue + // 直接成功,不可扩展且新旧不同或成环返回 false(此处按规范抛 TypeError)。 + match vm.set_prototype_of(unsafe { &mut *target_ptr }, proto) { + Ok(true) => NativeResult::Ok(target_val), + Ok(false) => NativeResult::Err(crate::error::create_type_error( vm, "Object.setPrototypeOf: cannot set prototype of non-extensible object", - )); + )), + // 代理 trap 异常原值传播(ReturnIfAbrupt)。 + Err(err) => NativeResult::Err(crate::array::from_engine_error(vm, &err)), } - if target.set_proto(proto).is_err() { - return NativeResult::Err(crate::error::create_type_error(vm, "Object.setPrototypeOf: cannot set prototype")); - } - NativeResult::Ok(target_val) } /// `Object.hasOwn(obj, key)`:对象是否有指定自身属性。 diff --git a/crates/oxide_builtins/src/reflect.rs b/crates/oxide_builtins/src/reflect.rs index eb421be7..e0677d72 100644 --- a/crates/oxide_builtins/src/reflect.rs +++ b/crates/oxide_builtins/src/reflect.rs @@ -202,7 +202,11 @@ pub fn reflect_get_prototype_of(vm: &mut H, args: &[u8]) -> NativeRes let Some(target_ptr) = object_ptr(target_val) else { return type_error(vm, "Reflect.getPrototypeOf target is not an object"); }; - NativeResult::Ok(unsafe { &*target_ptr }.proto()) + match vm.get_prototype_of(unsafe { &*target_ptr }) { + Ok(proto) => NativeResult::Ok(proto), + // 代理 trap 异常原值传播(ReturnIfAbrupt)。 + Err(err) => NativeResult::Err(from_engine_error(vm, &err)), + } } /// `Reflect.has(target, key)`:属性存在性判定(规范 HasProperty,含原型链)。 @@ -475,11 +479,13 @@ pub fn reflect_set_prototype_of(vm: &mut H, args: &[u8]) -> NativeRes return type_error(vm, "Reflect.setPrototypeOf prototype must be an object or null"); } let target = unsafe { &mut *target_ptr }; - // OrdinarySetPrototypeOf 步 2-4:新旧相同先于可扩展判定,直接成功。 - if !target.is_extensible() && target.proto() != proto { - return NativeResult::Ok(JsValue::bool(false)); + // OrdinarySetPrototypeOf 步 2-4 语义在 trait 方法内承载:新旧 SameValue + // 直接成功,不可扩展且新旧不同或成环返回 false。 + match vm.set_prototype_of(target, proto) { + Ok(set) => NativeResult::Ok(JsValue::bool(set)), + // 代理 trap 异常原值传播(ReturnIfAbrupt)。 + Err(err) => NativeResult::Err(from_engine_error(vm, &err)), } - NativeResult::Ok(JsValue::bool(target.set_proto(proto).is_ok())) } fn arg(vm: &H, args: &[u8], idx: usize) -> JsValue { diff --git a/crates/oxide_runtime_api/src/host.rs b/crates/oxide_runtime_api/src/host.rs index 2fd86ec0..7de64e8f 100644 --- a/crates/oxide_runtime_api/src/host.rs +++ b/crates/oxide_runtime_api/src/host.rs @@ -327,6 +327,65 @@ pub trait VmHost { /// Proxy trap 返回值布尔投影:非布尔返回抛 TypeError。 fn proxy_trap_bool_result(&self, result: JsValue, trap_name: &str) -> Result; + // 原型与扩展性面(代理感知路由:代理经 trap 路由、非代理委托固有语义) + /// `[[GetPrototypeOf]]`:返回对象的 prototype(对象或 null);代理经 + /// getPrototypeOf trap 路由——缺失落穿 target 的 `[[GetPrototypeOf]]`, + /// trap 返值须为对象或 null,否则抛 TypeError。 + /// + /// # 边界与前提 + /// - `obj` 须为存活对象;代理的 target 由构造器校验为非空对象指针。 + /// + /// # 副作用 + /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 + fn get_prototype_of(&mut self, obj: &JsObject) -> Result; + /// `[[SetPrototypeOf]]`:设置 prototype(对象或 null)。 + /// + /// # 返回值 + /// - `Ok(true)` 设置成功;`Ok(false)` 未设置(不可扩展且新旧原型不同, + /// 或新原型已在原型链上构成环);`Err` 为 TypeError(trap 报告与 + /// target 实际状态不一致等)。 + /// + /// # 边界与前提 + /// - `proto` 须为对象或 null(调用方入口校验)。 + /// + /// # 副作用 + /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 + fn set_prototype_of(&mut self, obj: &mut JsObject, proto: JsValue) -> Result; + /// `[[IsExtensible]]`:返回可扩展标志;代理经 isExtensible trap 路由 + /// (结果经布尔投影),trap 缺失落穿 target。 + /// + /// # 副作用 + /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 + fn is_extensible(&mut self, obj: &JsObject) -> Result; + /// `[[PreventExtensions]]`:置不可扩展标志;代理经 preventExtensions + /// trap 路由(结果经布尔投影,报告须与 target 实际可扩展状态一致), + /// trap 缺失落穿 target 的普通 preventExtensions。 + /// + /// # 副作用 + /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 + fn prevent_extensions(&mut self, obj: &mut JsObject) -> Result<(), String>; + /// `[[OwnPropertyKeys]]`:返回自身属性键列表(JS 可见键值,整数键升序 + /// 在前、字符串键插入序、Symbol 键插入序);代理经 ownKeys trap 路由 + /// (缺失落穿 target 自身键,重复键与不可配置键做不变量检查)。 + /// + /// # 副作用 + /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 + fn own_keys(&mut self, obj: &JsObject) -> Result, String>; + /// `[[GetOwnProperty]]`:返回自身属性描述符对象(JS 可见形态);缺失 + /// 返回 `Ok(None)`;代理经 getOwnPropertyDescriptor trap 路由(缺失 + /// 落穿 target 自身描述符,undefined 与对象返值做不变量交叉检查)。 + /// + /// # 副作用 + /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 + fn get_own_property_desc(&mut self, obj: &JsObject, key_si: u32) -> Result, String>; + /// `[[Delete]]`:删除自身属性并投影为布尔(成功 true、不可配置 false); + /// 代理经 deleteProperty trap 路由(缺失落穿 target 普通删除,报 true + /// 时 target 自身属性不可配置或 target 不可扩展抛 TypeError)。 + /// + /// # 副作用 + /// - 可能执行用户 trap 代码(含分配与 GC);revoked 抛 TypeError。 + fn delete_own(&mut self, obj: &mut JsObject, key_si: u32) -> Result; + // 属性定义 fn define_data_property( &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes, diff --git a/crates/oxide_vm/src/proxy.rs b/crates/oxide_vm/src/proxy.rs index 7e3b7f7c..c81580d2 100644 --- a/crates/oxide_vm/src/proxy.rs +++ b/crates/oxide_vm/src/proxy.rs @@ -228,7 +228,7 @@ mod tests { use crate::native::NativeFn; use crate::vm::Vm; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; - use oxide_runtime_api::{NativeResult, ProtoKind}; + use oxide_runtime_api::{NativeResult, ProtoKind, VmHost}; use oxide_types::object::NativeFnPtr; /// 建一个带状态盒的 Proxy 对象(测试构造路径,不经 Vm 分配)。 @@ -267,6 +267,48 @@ mod tests { NativeResult::Ok(JsValue::bool(true)) } + /// handler 计数属性加一(receiver 由 trap 调用约定保证为 handler)。 + fn bump_hits(vm: &mut Vm) { + let receiver = vm.reg(253); + // SAFETY: receiver 由 trap 调用约定保证为 handler 对象值。 + let handler = unsafe { &mut *receiver.as_js_object_ptr() }; + let hits_si = vm.perm_intern("hits"); + let cur = vm.resolve_property(handler, hits_si).unwrap_or(JsValue::int(0)); + vm.set_or_create_prop_value(handler, hits_si, JsValue::int(cur.as_int() + 1)); + } + + /// getPrototypeOf trap 原生函数体:计数加一,返回一个新对象。 + fn counting_get_prototype_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult { + bump_hits(vm); + let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + NativeResult::Ok(JsValue::from_js_object(result)) + } + + /// ownKeys trap 原生函数体:计数加一,返回单元素数组 ["k"]。 + fn counting_own_keys_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult { + bump_hits(vm); + let array_proto = vm.builtin_proto(ProtoKind::ArrayProto); + let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), 1)); + unsafe { (*arr).set_prop_at(0, vm.new_string("k")) }; + unsafe { (*arr).set_prop_count(1) }; + NativeResult::Ok(JsValue::from_js_object(arr)) + } + + /// getOwnPropertyDescriptor trap 原生函数体:计数加一,返回数据描述符 + /// 对象 {value: 7, writable/enumerable/configurable 全真}。 + fn counting_gopd_trap(vm: &mut Vm, _args: &[u8]) -> NativeResult { + bump_hits(vm); + let proto = vm.builtin_proto(ProtoKind::ObjectProto); + let desc = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto))); + let attrs = PropAttributes::DEFAULT_DATA; + let d = unsafe { &mut *desc }; + let _ = vm.define_data_property(d, vm.perm_intern("value"), JsValue::int(7), attrs); + let _ = vm.define_data_property(d, vm.perm_intern("writable"), JsValue::bool(true), attrs); + let _ = vm.define_data_property(d, vm.perm_intern("enumerable"), JsValue::bool(true), attrs); + let _ = vm.define_data_property(d, vm.perm_intern("configurable"), JsValue::bool(true), attrs); + NativeResult::Ok(JsValue::from_js_object(desc)) + } + /// 建带原生函数项的函数对象(session 堆分配并登记)。 fn native_fn_obj(vm: &mut Vm, func: NativeFn) -> *mut JsObject { let proto = vm.builtin_proto(ProtoKind::FunctionProto); @@ -540,6 +582,236 @@ mod tests { ); } + /// 7 个 trait 方法:非代理路径零行为变化(返回值与固有方法直读一致)。 + #[test] + fn proto_trait_methods_plain_object_paths() { + let mut vm = Vm::new(); + let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let key_si = vm.perm_intern("k"); + vm.set_or_create_prop_value(unsafe { &mut *obj }, key_si, JsValue::int(1)); + let obj_ref = unsafe { &*obj }; + + // get_prototype_of:与直读一致。 + assert_eq!( + vm.get_prototype_of(obj_ref).expect("get_prototype_of 应成功"), + obj_ref.proto(), + "非代理路径须与直读一致" + ); + + // set_prototype_of:正常设置成功;新旧 SameValue 直接成功。 + let new_proto = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + assert!( + vm.set_prototype_of(unsafe { &mut *obj }, JsValue::from_js_object(new_proto)) + .expect("set_prototype_of 应成功"), + "正常设置须成功" + ); + assert_eq!(unsafe { &*obj }.proto(), JsValue::from_js_object(new_proto)); + assert!( + vm.set_prototype_of(unsafe { &mut *obj }, JsValue::from_js_object(new_proto)) + .expect("set_prototype_of 应成功"), + "新旧 SameValue 须直接成功" + ); + + // is_extensible:与直读一致。 + assert_eq!( + vm.is_extensible(obj_ref).expect("is_extensible 应成功"), + obj_ref.is_extensible(), + "非代理路径须与直读一致" + ); + + // prevent_extensions:置不可扩展。 + vm.prevent_extensions(unsafe { &mut *obj }).expect("prevent_extensions 应成功"); + assert!(!unsafe { &*obj }.is_extensible(), "prevent_extensions 后须不可扩展"); + + // own_keys:与 walk_own_keys 一致(仅字符串键 "k")。 + let keys = vm.own_keys(obj_ref).expect("own_keys 应成功"); + assert_eq!(keys.len(), 1, "自身键须恰有一个"); + assert_eq!(vm.lookup_str(keys[0]).as_deref(), Some("k"), "键值须为字符串 k"); + + // get_own_property_desc:存在属性返描述符,缺失返 None。 + assert!( + vm.get_own_property_desc(obj_ref, key_si) + .expect("get_own_property_desc 应成功") + .is_some(), + "存在属性须有描述符" + ); + assert!( + vm.get_own_property_desc(obj_ref, vm.perm_intern("missing")) + .expect("get_own_property_desc 应成功") + .is_none(), + "缺失属性须返 None" + ); + + // delete_own:删除成功返 true;缺失键删除亦返 true。 + assert!(vm.delete_own(unsafe { &mut *obj }, key_si).expect("delete_own 应成功"), "删除须成功"); + assert!( + vm.delete_own(unsafe { &mut *obj }, key_si).expect("delete_own 应成功"), + "缺失键删除须成功" + ); + } + + /// 7 个 trait 方法:代理路径经 trap 路由(trap 命中、返值原样透传)。 + #[test] + fn proto_trait_methods_proxy_paths_route_traps() { + let mut vm = Vm::new(); + let target = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + // target 原型须为对象或 null(setPrototypeOf trap 报 true 的不变量检查)。 + unsafe { (*target).set_proto(JsValue::null()).expect("set_proto 应成功") }; + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let handler_val = JsValue::from_js_object(handler); + let get_proto_fn = native_fn_obj(&mut vm, counting_get_prototype_trap); + let counting_fn = native_fn_obj(&mut vm, counting_trap); + let own_keys_fn = native_fn_obj(&mut vm, counting_own_keys_trap); + let gopd_fn = native_fn_obj(&mut vm, counting_gopd_trap); + for (name, trap_fn) in [ + ("getPrototypeOf", get_proto_fn), + ("setPrototypeOf", counting_fn), + ("isExtensible", counting_fn), + ("preventExtensions", counting_fn), + ("ownKeys", own_keys_fn), + ("getOwnPropertyDescriptor", gopd_fn), + ("deleteProperty", counting_fn), + ] { + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern(name), + JsValue::from_js_object(trap_fn), + ); + } + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), handler_val); + let proxy_ref = unsafe { &*proxy }; + let handler_ref = unsafe { &*handler }; + let hits_si = vm.perm_intern("hits"); + let key_si = vm.perm_intern("k"); + + // getPrototypeOf:trap 返对象,原样透传。 + let proto = vm.get_prototype_of(proxy_ref).expect("get_prototype_of 应成功"); + assert!(proto.is_object(), "getPrototypeOf trap 返值须为对象"); + assert_eq!( + vm.resolve_property(handler_ref, hits_si).unwrap(), + JsValue::int(1), + "getPrototypeOf trap 须命中" + ); + + // isExtensible:trap 报 true,布尔投影为 true。 + assert!( + vm.is_extensible(proxy_ref).expect("is_extensible 应成功"), + "isExtensible trap 报 true 须投影为 true" + ); + assert_eq!( + vm.resolve_property(handler_ref, hits_si).unwrap(), + JsValue::int(2), + "isExtensible trap 须命中" + ); + + // preventExtensions:trap 报 true(与 target 可扩展状态一致)。 + vm.prevent_extensions(unsafe { &mut *proxy }) + .expect("prevent_extensions 应成功"); + assert_eq!( + vm.resolve_property(handler_ref, hits_si).unwrap(), + JsValue::int(3), + "preventExtensions trap 须命中" + ); + + // setPrototypeOf:trap 报 true,不变量检查通过(target 原型为 null)。 + assert!( + vm.set_prototype_of(unsafe { &mut *proxy }, JsValue::null()) + .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 须命中" + ); + } + + /// 7 个 trait 方法:代理路径不变量检查(getPrototypeOf 返非对象非 null + /// 抛 TypeError、setPrototypeOf 报 false 返 false、ownKeys 返非数组抛 + /// TypeError)。 + #[test] + fn proto_trait_methods_proxy_invariants() { + 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 应成功") }; + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + // getPrototypeOf trap 返非对象非 null 值。 + fn bad_get_prototype_trap(_vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Ok(JsValue::int(42)) + } + // setPrototypeOf trap 报 false。 + fn false_trap(_vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Ok(JsValue::bool(false)) + } + // ownKeys trap 返非数组值。 + fn bad_own_keys_trap(_vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Ok(JsValue::int(42)) + } + let get_proto_fn = native_fn_obj(&mut vm, bad_get_prototype_trap); + let set_proto_fn = native_fn_obj(&mut vm, false_trap); + let own_keys_fn = native_fn_obj(&mut vm, bad_own_keys_trap); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("getPrototypeOf"), + JsValue::from_js_object(get_proto_fn), + ); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("setPrototypeOf"), + JsValue::from_js_object(set_proto_fn), + ); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("ownKeys"), + JsValue::from_js_object(own_keys_fn), + ); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + let proxy_ref = unsafe { &*proxy }; + + // getPrototypeOf trap 返非对象非 null 抛 TypeError。 + let err = vm.get_prototype_of(proxy_ref).expect_err("须抛 TypeError"); + assert!(err.contains("TypeError"), "须抛 TypeError:{err}"); + + // setPrototypeOf trap 报 false 投影为 false(非错误)。 + assert!( + !vm.set_prototype_of(unsafe { &mut *proxy }, JsValue::null()) + .expect("set_prototype_of 应成功"), + "trap 报 false 须投影为 false" + ); + + // ownKeys trap 返非数组抛 TypeError。 + let err = vm.own_keys(proxy_ref).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 4c50b7a1..251bbaf9 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -262,6 +262,130 @@ impl oxide_runtime_api::VmHost for Vm { fn proxy_trap_bool_result(&self, result: JsValue, trap_name: &str) -> Result { crate::proxy::trap_bool_result(result, trap_name) } + fn get_prototype_of(&mut self, obj: &JsObject) -> Result { + if !obj.is_proxy_obj() { + return Ok(obj.proto()); + } + // 代理路径:getPrototypeOf trap(实参 target),缺失落穿 target 的 + // [[GetPrototypeOf]](target 自身为代理时经递归再路由)。 + let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target; + match crate::proxy::call_trap(self, obj, self.perm_intern("getPrototypeOf"), &[target])? { + Some(result) => { + // 返值须为对象或 null(规范 10.5.3.1),否则 TypeError。 + if !result.is_object() && !result.is_null() { + return Err(oxide_runtime_api::format_error_message( + "TypeError", + "a [[GetPrototypeOf]] trap returned a non-object and non-null value", + )); + } + Ok(result) + } + // SAFETY: target 由构造器校验为非空对象指针。 + None => self.get_prototype_of(unsafe { &*target.as_js_object_ptr() }), + } + } + fn set_prototype_of(&mut self, obj: &mut JsObject, proto: JsValue) -> Result { + if !obj.is_proxy_obj() { + // OrdinarySetPrototypeOf 步 2-4:新旧 SameValue 直接成功;不可扩展 + // 且新旧不同返回 false。 + if !obj.is_extensible() && obj.proto() != proto { + return Ok(false); + } + return match obj.set_proto(proto) { + Ok(()) => Ok(true), + // 成环(新原型已在原型链上):规范返回 false,非错误。 + Err("cyclic __proto__ value") => Ok(false), + Err(msg) => Err(oxide_runtime_api::format_error_message("TypeError", msg)), + }; + } + // 代理路径:setPrototypeOf trap(实参 target、newProto),缺失落穿 target + // 的 [[SetPrototypeOf]](target 自身为代理时经递归再路由)。 + let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target; + let trap_result = + match crate::proxy::call_trap(self, obj, self.perm_intern("setPrototypeOf"), &[target, proto])? { + Some(r) => r, + // SAFETY: target 由构造器校验为非空对象指针。 + None => return self.set_prototype_of(unsafe { &mut *target.as_js_object_ptr() }, proto), + }; + 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", + )); + } + Ok(true) + } + fn is_extensible(&mut self, obj: &JsObject) -> Result { + 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() }), + } + } + fn prevent_extensions(&mut self, obj: &mut JsObject) -> Result<(), String> { + if !obj.is_proxy_obj() { + obj.set_extensible(false); + return Ok(()); + } + // 代理路径:preventExtensions trap 路由(含报告与 target 可扩展状态 + // 一致性检查),缺失落穿 target 的普通 preventExtensions。 + match oxide_builtins::object::proxy_prevent_extensions(self, obj) { + Ok(_) => Ok(()), + // 还原原始异常值入 uncaught 侧通道(保身份)。 + Err(exc) => { + self.last_uncaught_value = Some(exc); + Err(self.error_text(exc)) + } + } + } + fn own_keys(&mut self, obj: &JsObject) -> Result, String> { + match oxide_builtins::object::own_keys_proxy_aware(self, obj) { + Ok((keys, _storage)) => { + let mut out = Vec::with_capacity(keys.len()); + for (si, _pos) in keys { + out.push(oxide_builtins::object::key_si_to_js_key(self, si)); + } + Ok(out) + } + // 还原原始异常值入 uncaught 侧通道(保身份)。 + Err(exc) => { + self.last_uncaught_value = Some(exc); + Err(self.error_text(exc)) + } + } + } + fn get_own_property_desc(&mut self, obj: &JsObject, key_si: u32) -> Result, String> { + match oxide_builtins::object::own_descriptor_value(self, obj, key_si) { + Ok(desc) => Ok(desc), + // 还原原始异常值入 uncaught 侧通道(保身份)。 + Err(exc) => { + self.last_uncaught_value = Some(exc); + Err(self.error_text(exc)) + } + } + } + fn delete_own(&mut self, obj: &mut JsObject, key_si: u32) -> Result { + match oxide_builtins::object::delete_own_property(self, obj, key_si) { + Ok(deleted) => Ok(deleted), + // 还原原始异常值入 uncaught 侧通道(保身份)。 + Err(exc) => { + self.last_uncaught_value = Some(exc); + Err(self.error_text(exc)) + } + } + } fn define_data_property( &mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes, ) -> Result<(), String> {