diff --git a/Cargo.lock b/Cargo.lock index 3a310062..ee7eea6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.20.6" +version = "0.20.7" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.20.6" +version = "0.20.7" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.20.6" +version = "0.20.7" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.20.6" +version = "0.20.7" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.20.6" +version = "0.20.7" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.20.6" +version = "0.20.7" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.20.6" +version = "0.20.7" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.20.6" +version = "0.20.7" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.20.6" +version = "0.20.7" [[package]] name = "oxide_parser" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.20.6" +version = "0.20.7" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.20.6" +version = "0.20.7" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.20.6" +version = "0.20.7" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.20.6" +version = "0.20.7" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 8a7374e5..41b04b6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.20.6" +version = "0.20.7" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/array/from.rs b/crates/oxide_builtins/src/array/from.rs index 5155a80f..3fca532a 100644 --- a/crates/oxide_builtins/src/array/from.rs +++ b/crates/oxide_builtins/src/array/from.rs @@ -17,6 +17,12 @@ use super::common::{ pub fn array_constructor(vm: &mut H, args: &[u8]) -> NativeResult { let proto = vm.builtin_proto(ProtoKind::ArrayProto); let proto_val = JsValue::from_js_object(proto); + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + // 构造形态(newTarget 在场):产物须为 Array exotic 对象(Array.isArray 认) + // 且原型取 newTarget.prototype(派生类 / 代理 newTarget 由此拿到子类实例), + // 原型已由构造入口预写入 receiver,直接读取复用;普通调用形态自建 + // Array.prototype 原型的新数组。 + let in_construct = vm.constructing_native() && this_val.is_object(); if args.len() == 2 { let val = vm.reg(args[1]); @@ -25,7 +31,13 @@ pub fn array_constructor(vm: &mut H, args: &[u8]) -> NativeResult { Ok(n) => n, Err(err) => return NativeResult::Err(err), }; - let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n)); + let arr_proto = if in_construct { + // SAFETY: receiver 是构造帧分配的 this(本 session 存活),只读原型。 + unsafe { &*this_val.as_js_object_ptr() }.proto() + } else { + proto_val + }; + let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, arr_proto, n)); // 数值长度分支建 n 个空洞而非 present-undefined:`in`、for-in、 // Object.keys 按缺失处理,元素 Get 落原型链。物理区以稠密上限 // 钳位(越界索引无物理槽,逻辑长度已覆盖)。 @@ -39,7 +51,13 @@ pub fn array_constructor(vm: &mut H, args: &[u8]) -> NativeResult { } let n_elems = if args.len() > 1 { args.len() - 1 } else { 0 }; - let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n_elems)); + let arr_proto = if in_construct { + // SAFETY: receiver 是构造帧分配的 this(本 session 存活),只读原型。 + unsafe { &*this_val.as_js_object_ptr() }.proto() + } else { + proto_val + }; + let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, arr_proto, n_elems)); for i in 0..n_elems { unsafe { (*arr).set_prop_at(i, vm.reg(args[1 + i])); diff --git a/crates/oxide_builtins/src/proxy.rs b/crates/oxide_builtins/src/proxy.rs index 0e9cc112..070db8d4 100644 --- a/crates/oxide_builtins/src/proxy.rs +++ b/crates/oxide_builtins/src/proxy.rs @@ -70,15 +70,23 @@ fn check_target_and_handler(target: JsValue, handler: JsValue) -> Result<(), &'s Ok(()) } -/// 不变量检查(规范 10.2.4 步骤 4-5):handler 的 isExtensible 与 setPrototypeOf -/// trap 若存在,其布尔值须与 target 实际状态一致,否则抛 TypeError。 +/// 不变量检查(规范 10.2.4 步骤 1、4-5):target 可调用时须可扩展,handler +/// 的 isExtensible 与 setPrototypeOf trap 若存在,其布尔值须与 target 实际 +/// 状态一致,否则抛 TypeError。 /// /// # 边界与前提 /// - 仅当 handler 自有对应 trap 属性时检查(继承属性不触发)。 /// - trap 读为传播读,getter 抛错原值上抛。 fn proxy_create_invariants( - vm: &mut H, target_obj: &JsObject, handler_obj: &JsObject, handler: JsValue, + vm: &mut H, target: JsValue, target_obj: &JsObject, handler_obj: &JsObject, handler: JsValue, ) -> 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() { @@ -160,7 +168,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_obj, handler_obj, handler) { + if let Err(exc) = proxy_create_invariants(vm, target, target_obj, handler_obj, handler) { return NativeResult::Err(exc); } NativeResult::Ok(create_proxy_object(vm, target, handler)) @@ -187,7 +195,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_obj, handler_obj, handler) { + if let Err(exc) = proxy_create_invariants(vm, target, target_obj, handler_obj, handler) { return NativeResult::Err(exc); } let proxy_val = create_proxy_object(vm, target, handler); diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index eb96bc18..357db420 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -108,12 +108,13 @@ impl Vm { } // 无本体函数对象(Function.prototype):[[Call]] 接受任意实参 // 返回 undefined,不执行函数体。 - if obj.native_fn().is_none() && obj.sub_module_index() == 0 { + if obj.native_fn().is_none() && obj.sub_module_index() == 0 && !obj.is_proxy_obj() { self.regs[0] = JsValue::undefined(); return Ok(()); } - if obj.native_fn().is_some() { - // 尾调用转发为普通调用形态。 + if obj.is_proxy_obj() || obj.native_fn().is_some() { + // 尾调用转发为普通调用形态(代理经 call_function_sync 的 + // apply trap 路由,缺失落穿 target)。 self.constructing_native = false; match self.call_function_sync(callee, this, &args) { Ok(val) => { @@ -1094,103 +1095,9 @@ impl Vm { return self.dispatch_new_bound(rd, constructor, new_obj, args, 0); } - let proto_ptr = self.realm.object_prototype.borrow().as_ptr() as *mut JsObject; - let new_obj = self.alloc_object(JsObject::new_empty( - oxide_kernel::shape_forge::EMPTY_SHAPE_ID, - JsValue::from_js_object(proto_ptr), - )); - // GpFC 按构造器种类分读形:native 构造器保持裸存储读(占位 receiver - // 不可观测,构造体自重读覆盖可观测面);字节码构造器走传播读(getter - // 异常原值上抛,非对象结果回落 Object.prototype)。 - let proto_si = self.kernel_core.perm_interner().intern("prototype").0; - let proto_val = if ctor_obj.native_fn().is_some() { - self.resolve_property(ctor_obj, proto_si) - } else { - let pc_before = self.pc; - match self.ordinary_get(ctor_obj, proto_si, constructor) { - Ok(v) if self.pc == pc_before => Some(v), - Ok(_) => { - // getter 抛且已有 catch/finally 接手:展开已跳到落点,停构造流。 - return Ok(true); - } - Err(err) => { - // 无处理器(或重入在途异常):恢复原值走异常通道。 - let exc = self - .last_uncaught_value - .take() - .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &err)); - self.exception_value = Some(exc); - self.pending_error_kind = Some(self.thrown_error_kind(exc)); - return self.unwind().map(|_| true); - } - } - }; - if let Some(v) = proto_val { - if v.is_object() { - let new_obj_mut = unsafe { &mut *new_obj }; - let proto_obj_ptr = v.as_js_object_ptr(); - let _ = new_obj_mut.set_proto(JsValue::from_js_object(proto_obj_ptr)); - } - } - let new_obj_val = JsValue::object(new_obj as *mut u8); - - if ctor_obj.native_fn().is_some() { - // native 构造器:receiver 为新对象,值传递调用。newTarget 快照后置入 - // reg(255) 暴露给 native 构造器(调用后恢复原值);构造形态标记同窗 - // 夹持(调用返回即恢复,先于结果分派,异常结局不残留)。 - let saved_new_target = self.regs[255]; - let saved_constructing = self.constructing_native; - self.regs[255] = constructor; - self.constructing_native = true; - let result = self.call_function_sync(constructor, new_obj_val, &args); - self.constructing_native = saved_constructing; - self.regs[255] = saved_new_target; - match result { - Ok(v) => { - self.regs[rd] = if v.is_object() { v } else { new_obj_val }; - Ok(false) - } - Err(_) => { - let exc = self - .last_uncaught_value - .take() - .unwrap_or_else(|| oxide_builtins::error::create_error(self, "constructor call failed")); - self.exception_value = Some(exc); - self.pending_error_kind = Some(self.thrown_error_kind(exc)); - self.unwind().map(|_| true) - } - } - } else if ctor_obj.sub_module_index() > 0 { - // 按构造器自身记录的表代际解析。 - let sub = self.callee_module(ctor_obj); - // 生成器函数不是构造器:`new g()` 抛 TypeError。 - if matches!(sub, Some(m) if m.is_generator) { - return self.raise_type_error("g is not a constructor").map(|_| true); - } - // 异步函数不是构造器:`new f()` 抛 TypeError。 - if matches!(sub, Some(m) if m.is_async) { - return self.raise_type_error("g is not a constructor").map(|_| true); - } - let this_value = if ctor_obj.is_derived_constructor() { - JsValue::undefined() - } else { - new_obj_val - }; - self.push_bytecode_frame( - constructor, - this_value, - FrameArgs::Slice(&args), - ConstructArgs::new(rd as u8, new_obj_val, constructor), - FrameContinuation::None, - 0, - )?; - Ok(true) - } else { - // 无函数体的函数对象(如 Function.prototype):IsConstructor 为假, - // 按规范抛 TypeError(与非 spread 的 NEW_EXPRESSION 臂同形)。 - self.raise_type_error("NEW_EXPRESSION: constructor is not a constructor") - .map(|_| true) - } + // Proxy [[Construct]] 路由、新对象分配、原型读与 native / 字节码分派 + // 收敛到统一入口(spread 路径无调用点存活上界,窗口取 0)。 + self.dispatch_new_target(ctor_obj, constructor, constructor, rd, &args, 0) } /// SUPER_CALL_SPREAD:物化实参后按父构造器目标分派(native 走值传递,字节码走帧)。 diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index ace0cf61..1d4411ca 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -366,6 +366,16 @@ impl Vm { } let obj = unsafe { &*obj_ptr }; + // 代理对象恒走慢路径:IC 键不含 type_tag,shape_id 与同形普通对象碰撞时 + // 命中会绕过 get trap 直读代理自身属性槽(恒 miss 守卫的读入口侧)。 + if obj.is_proxy_obj() { + let prop_name_si = self.property_key_si(self.regs[b])?; + 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; + } + return Ok(()); + } // 入口快判:键寄存器恒为 perm 串,指针相等即键为 "length"; // 数组接收者直返逻辑长度,免 IC 读与键解析(与虚拟长度槽口径一致)。 let key_val = self.regs[b]; diff --git a/crates/oxide_vm/src/promise/constructor.rs b/crates/oxide_vm/src/promise/constructor.rs index 55f5eef9..9e35c805 100644 --- a/crates/oxide_vm/src/promise/constructor.rs +++ b/crates/oxide_vm/src/promise/constructor.rs @@ -75,6 +75,23 @@ impl Vm { // SAFETY: 调用方入口已对 new_target 做过同谓词 IsConstructor 校验, // 指针非空且指向存活对象。 let nt_obj = unsafe { &*new_target.as_js_object_ptr() }; + // Proxy [[Construct]]:construct trap 权威(target, argumentsList, + // newTarget);trap 缺失落穿 target 的普通 [[Construct]](newTarget 不变)。 + if ctor_obj.is_proxy_obj() { + match crate::proxy::proxy_construct_trap(self, ctor_obj, args, new_target) { + Ok(Some(v)) => return Ok(v), + Ok(None) => { + let target = oxide_builtins::proxy::proxy_state_ref(ctor_obj).expect("状态盒").target; + return self.construct_with(target, new_target, args); + } + Err(e) => { + return Err(self + .last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &e))); + } + } + } // bound 包装:解包链(绑定实参前置、newTarget 逐层指针相等替换), // 最内层非构造器判定后递归进入既有臂。 if ctor_obj.type_tag == JsObject::OBJ_TYPE_BOUND { diff --git a/crates/oxide_vm/src/proxy.rs b/crates/oxide_vm/src/proxy.rs index f58ae2ec..7ebaca96 100644 --- a/crates/oxide_vm/src/proxy.rs +++ b/crates/oxide_vm/src/proxy.rs @@ -55,7 +55,7 @@ pub(crate) fn drop_proxy_native(obj: &JsObject) -> u64 { /// 1. 取状态盒并检 revoked 位:位真抛 TypeError("Proxy is revoked")。 /// 2. 经全 get 路径从 handler 读 trap 值(receiver 为 handler——handler 自身 /// 是代理时其 get trap 依规范触发)。 -/// 3. trap 值 undefined 返 None(落穿普通行为)。 +/// 3. trap 值 undefined 或 null 返 None(GetMethod 形态:落穿普通行为)。 /// 4. 经 `call_function_sync` 同步调用 trap 值(this 为 handler)。 /// /// # 边界与前提 @@ -79,9 +79,23 @@ pub(crate) fn call_trap( // SAFETY: handler 由构造器校验为非空对象指针。 let handler_obj = unsafe { &*handler.as_js_object_ptr() }; let trap_val = vm.ordinary_get(handler_obj, trap_name_si, handler)?; - if trap_val.is_undefined() { + if trap_val.is_undefined() || trap_val.is_null() { return Ok(None); } + // trap 值须可调用(规范 Call 语义):非可调用抛 TypeError 而非落入 + // call_function_sync 的通用「accessor is not callable」文本(kind 丢失)。 + if !trap_val.is_object() { + return Err(oxide_runtime_api::format_error_message( + "TypeError", + "Proxy trap is not callable", + )); + } + if !unsafe { &*trap_val.as_js_object_ptr() }.is_function() { + return Err(oxide_runtime_api::format_error_message( + "TypeError", + "Proxy trap is not callable", + )); + } let result = vm.call_function_sync(trap_val, handler, args)?; Ok(Some(result)) } @@ -100,6 +114,77 @@ pub(crate) fn trap_bool_result(result: JsValue, trap_name: &str) -> Result JsValue { + let array_proto = vm.builtin_proto(oxide_runtime_api::ProtoKind::ArrayProto); + let arr = vm.alloc_object(JsObject::new_array( + oxide_kernel::shape_forge::EMPTY_SHAPE_ID, + JsValue::from_js_object(array_proto), + args.len().min(oxide_types::object::MAX_DENSE_PROPS), + )); + for (i, item) in args.iter().enumerate() { + // SAFETY: arr 是刚经统一入口分配的数组对象,元素区已就绪。 + unsafe { (*arr).set_prop_at(i, *item) }; + } + // SAFETY: 与 new_array 同 epoch,写入后按实际元素数设 prop_count。 + unsafe { (*arr).set_prop_count(args.len().min(oxide_types::object::MAX_DENSE_PROPS)) }; + JsValue::from_js_object(arr) +} + +/// apply trap 调用([[Call]] 路由):trap 权威,缺失落穿 target。 +/// +/// # 步骤 +/// 1. 取状态盒(revoked 检查在 `call_trap` 内)。 +/// 2. 从 handler 读 apply trap 值(GetMethod 形态:undefined 与 null 均视为缺失)。 +/// 3. trap 缺失:落穿 target 的 [[Call]](同 this 同实参的同步调用)。 +/// 4. trap 存在:以 (target, thisArgument, argumentsList) 调用,返回值原样透传。 +/// +/// # 边界与前提 +/// - `proxy` 须为带状态盒的 Proxy 对象(守卫以 `is_proxy_obj` 保证入口)。 +/// - 函数代理的 target 可调用(构造期置 is_function 位的前提),落穿 +/// `call_function_sync` 依赖该前提。 +pub(crate) fn proxy_apply_call( + vm: &mut Vm, proxy: &JsObject, this_value: JsValue, args: &[JsValue], +) -> Result { + let state = oxide_builtins::proxy::proxy_state_ref(proxy).expect("状态盒"); + let target = state.target; + let args_array = materialize_args_array(vm, args); + match call_trap(vm, proxy, vm.perm_intern("apply"), &[target, this_value, args_array])? { + Some(result) => Ok(result), + None => vm.call_function_sync(target, this_value, args), + } +} + +/// construct trap 调用([[Construct]] 路由):trap 权威,缺失落穿 target。 +/// +/// # 步骤 +/// 1. 取状态盒(revoked 检查在 `call_trap` 内)。 +/// 2. 从 handler 读 construct trap 值(GetMethod 形态)。 +/// 3. trap 缺失:返 None,调用方落穿 target 的普通 [[Construct]]。 +/// 4. trap 存在:以 (target, argumentsList, newTarget) 调用;返回值须为对象, +/// 否则抛 TypeError。 +/// +/// # 边界与前提 +/// - `proxy` 须为带状态盒的 Proxy 对象(守卫以 `is_proxy_obj` 保证入口)。 +/// - `new_target` 为调用点 newTarget(代理自身),落穿时原样透传给 target。 +pub(crate) fn proxy_construct_trap( + vm: &mut Vm, proxy: &JsObject, args: &[JsValue], new_target: JsValue, +) -> Result, String> { + let state = oxide_builtins::proxy::proxy_state_ref(proxy).expect("状态盒"); + let target = state.target; + let args_array = materialize_args_array(vm, args); + let result = call_trap(vm, proxy, vm.perm_intern("construct"), &[target, args_array, new_target])?; + match result { + Some(v) if v.is_object() => Ok(Some(v)), + Some(_) => Err(oxide_runtime_api::format_error_message( + "TypeError", + "a [[Construct]] trap returned a non-object", + )), + None => Ok(None), + } +} + /// 构造 defineProperty trap 的属性描述符对象(value 加三标志位)。 /// /// 描述符属性均为不可写、不可枚举、不可配置(与内建对象属性口径一致); @@ -243,6 +328,158 @@ mod tests { assert!(err.contains("Proxy is revoked"), "错误文本须带撤销标记:{err}"); } + /// 函数代理不变量:target 可调用且不可扩展时构造器抛 TypeError。 + #[test] + fn proxy_constructor_rejects_callable_non_extensible_target() { + let mut vm = Vm::new(); + let target = native_fn_obj(&mut vm, gc_forcing_get_trap); + unsafe { (*target).set_extensible(false) }; + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + // native 调用协议:253/254 为 receiver/callee 槽,实参经寄存器下标传递。 + vm.regs[1] = JsValue::from_js_object(target); + vm.regs[2] = JsValue::from_js_object(handler); + vm.constructing_native = true; + let result = oxide_builtins::proxy::proxy_constructor(&mut vm, &[253, 254, 1, 2]); + let exc = match result { + NativeResult::Err(exc) => exc, + _ => panic!("可调用且不可扩展的 target 须抛 TypeError"), + }; + let exc_ref = unsafe { &*exc.as_js_object_ptr() }; + let name_val = vm.resolve_property(exc_ref, vm.perm_intern("name")).unwrap(); + // SAFETY: name_val 为字符串值(异常对象 name 属性)。 + let name = unsafe { (*name_val.as_string_ptr()).as_lossy_str() }; + assert_eq!(name, "TypeError", "须抛 TypeError"); + } + + /// apply trap 体:把三实参(target、thisArg、argumentsList)身份记入 handler, + /// 返回固定值。 + fn apply_trap_capture(vm: &mut Vm, args: &[u8]) -> NativeResult { + let handler = vm.reg(253); + let handler_ref = unsafe { &mut *handler.as_js_object_ptr() }; + vm.set_or_create_prop_value(handler_ref, vm.perm_intern("target"), vm.reg(args[1])); + vm.set_or_create_prop_value(handler_ref, vm.perm_intern("this"), vm.reg(args[2])); + vm.set_or_create_prop_value(handler_ref, vm.perm_intern("list"), vm.reg(args[3])); + NativeResult::Ok(JsValue::int(99)) + } + + /// apply trap 路由:trap 以 (target, thisArg, argumentsList) 调用,返回值原样透传。 + #[test] + fn proxy_apply_call_routes_apply_trap_with_args() { + let mut vm = Vm::new(); + let target = native_fn_obj(&mut vm, counting_trap); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + let trap_fn = native_fn_obj(&mut vm, apply_trap_capture); + let this_value = plain_obj_value(); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("apply"), + JsValue::from_js_object(trap_fn), + ); + let result = proxy_apply_call(&mut vm, unsafe { &*proxy }, this_value, &[JsValue::int(1), JsValue::int(2)]) + .expect("apply 应成功"); + assert_eq!(result, JsValue::int(99), "trap 返回值须原样透传"); + let handler_ref = unsafe { &*handler }; + assert_eq!( + vm.resolve_property(handler_ref, vm.perm_intern("target")).unwrap(), + JsValue::from_js_object(target), + "第一实参须为 target" + ); + assert_eq!( + vm.resolve_property(handler_ref, vm.perm_intern("this")).unwrap(), + this_value, + "第二实参须为 thisArg" + ); + let list = vm.resolve_property(handler_ref, vm.perm_intern("list")).unwrap(); + assert!(list.is_object(), "第三实参须为数组对象"); + let list_ref = unsafe { &*list.as_js_object_ptr() }; + assert!(list_ref.is_array(), "argumentsList 须为数组"); + } + + /// apply trap 缺失:落穿 target 的普通调用路径。 + #[test] + fn proxy_apply_call_falls_through_to_target() { + let mut vm = Vm::new(); + // target 不读 receiver(落穿时 this 为 undefined),仅返回固定值。 + fn target_return_7(_vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Ok(JsValue::int(7)) + } + let target = native_fn_obj(&mut vm, target_return_7); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + let result = + proxy_apply_call(&mut vm, unsafe { &*proxy }, JsValue::undefined(), &[JsValue::int(1)]) + .expect("落穿 target 应成功"); + assert_eq!(result, JsValue::int(7), "target 调用结果须原样返回"); + } + + /// construct trap 返回值须为对象,非对象返回抛 TypeError。 + #[test] + fn proxy_construct_trap_non_object_result_throws() { + let mut vm = Vm::new(); + let target = native_fn_obj(&mut vm, counting_trap); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + // construct trap 体:返回非对象值。 + fn construct_trap_non_object(_vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Ok(JsValue::int(42)) + } + let trap_fn = native_fn_obj(&mut vm, construct_trap_non_object); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("construct"), + JsValue::from_js_object(trap_fn), + ); + let err = proxy_construct_trap(&mut vm, unsafe { &*proxy }, &[], JsValue::from_js_object(proxy)) + .expect_err("非对象返回值须抛 TypeError"); + assert!(err.contains("TypeError"), "须抛 TypeError:{err}"); + assert!(err.contains("non-object"), "错误文本须带非对象标记:{err}"); + } + + /// construct trap 路由:trap 以 (target, argumentsList, newTarget) 调用, + /// 对象返回值原样透传。 + #[test] + fn proxy_construct_trap_routes_with_new_target() { + let mut vm = Vm::new(); + let target = native_fn_obj(&mut vm, counting_trap); + let handler = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + let proxy = proxy_with_state(&mut vm, JsValue::from_js_object(target), JsValue::from_js_object(handler)); + // construct trap 体:记录三实参身份,返回固定对象。 + fn construct_trap_capture(vm: &mut Vm, args: &[u8]) -> NativeResult { + let handler = vm.reg(253); + let handler_ref = unsafe { &mut *handler.as_js_object_ptr() }; + vm.set_or_create_prop_value(handler_ref, vm.perm_intern("target"), vm.reg(args[1])); + vm.set_or_create_prop_value(handler_ref, vm.perm_intern("list"), vm.reg(args[2])); + vm.set_or_create_prop_value(handler_ref, vm.perm_intern("newtarget"), vm.reg(args[3])); + let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())); + NativeResult::Ok(JsValue::from_js_object(result)) + } + let trap_fn = native_fn_obj(&mut vm, construct_trap_capture); + vm.set_or_create_prop_value( + unsafe { &mut *handler }, + vm.perm_intern("construct"), + JsValue::from_js_object(trap_fn), + ); + let new_target = plain_obj_value(); + let result = proxy_construct_trap(&mut vm, unsafe { &*proxy }, &[JsValue::int(3)], new_target) + .expect("construct 应成功") + .expect("trap 存在须有返回值"); + assert!(result.is_object(), "trap 对象返回值须原样透传"); + let handler_ref = unsafe { &*handler }; + assert_eq!( + vm.resolve_property(handler_ref, vm.perm_intern("target")).unwrap(), + JsValue::from_js_object(target), + "第一实参须为 target" + ); + let list = vm.resolve_property(handler_ref, vm.perm_intern("list")).unwrap(); + assert!(list.is_object() && unsafe { &*list.as_js_object_ptr() }.is_array(), "第二实参须为数组"); + assert_eq!( + vm.resolve_property(handler_ref, vm.perm_intern("newtarget")).unwrap(), + new_target, + "第三实参须为 newTarget" + ); + } + /// 守卫路由:get / set / has trap 对代理命中,resolve_property 对代理保守返 None。 #[test] fn guard_routing_for_proxy_get_set_has_and_resolve() { diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index 4ccc63c0..79b767d9 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -125,6 +125,11 @@ impl Vm { if !callee_obj.is_function() { return Err(self.error_message_text("TypeError", "accessor is not callable")); } + // Proxy [[Call]]:apply trap 权威(target, thisArgument, argumentsList); + // trap 缺失落穿 target 的普通调用路径(嵌套代理经递归自然消解)。 + if callee_obj.is_proxy_obj() { + return crate::proxy::proxy_apply_call(self, callee_obj, receiver, args); + } if let Some(native_fn) = callee_obj.native_fn() { if self.native_call_depth >= self.kernel_core.config.max_call_depth { diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 5bcabb28..8d72dff6 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -41,6 +41,26 @@ impl Vm { let call_window = (ext >> 8) as u8; crate::vm_debug!("CALL rd={} this={} args={} depth={}", rd, this_reg, arg_count, self.frames.len()); + // Proxy [[Call]]:apply trap 权威(target, thisArgument, + // argumentsList);trap 缺失落穿 target 的普通调用路径。 + if obj.is_proxy_obj() { + let this_value = self.regs[this_reg as usize]; + let mut args = Vec::with_capacity(arg_count); + for i in 0..arg_count.min(256) { + args.push(self.regs[first_arg_reg.wrapping_add(i as u8) as usize]); + } + match crate::proxy::proxy_apply_call(self, obj, this_value, &args) { + Ok(v) => { + self.regs[0] = v; + return Ok(false); + } + Err(msg) => { + self.raise_call_error(&msg)?; + return Ok(true); + } + } + } + // 无本体函数对象(Function.prototype):[[Call]] 接受任意实参 // 返回 undefined,不执行函数体。 if obj.native_fn().is_none() && obj.sub_module_index() == 0 { diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index c525c4a1..c4247464 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -87,6 +87,59 @@ impl Vm { return self.dispatch_new_bound(rd, constructor, new_obj, args, call_window); } + let mut args = Vec::with_capacity(arg_count); + for i in 0..arg_count.min(256) { + args.push(self.regs[first_arg_reg.wrapping_add(i as u8) as usize]); + } + self.dispatch_new_target(ctor_obj, constructor, constructor, rd, &args, call_window) + } + + /// 构造器普通 [[Construct]]:Proxy construct trap 路由、新对象分配、原型读 + /// (newTarget)、native / 字节码分派。 + /// + /// # 步骤 + /// 1. Proxy [[Construct]]:construct trap 权威(target、argumentsList、 + /// newTarget);trap 缺失落穿 target 的普通 [[Construct]](newTarget 不变, + /// 嵌套代理经递归自然消解)。 + /// 2. 分配占位 this(原型默认 Object.prototype)。 + /// 3. GpFC 自 newTarget 读原型(native 形裸读、字节码形传播读)。 + /// 4. native 构造器:值传递调用(receiver = 新对象,new.target 槽 = newTarget); + /// 字节码构造器:压构造帧(this = 新对象,派生构造器为 undefined)。 + /// + /// # 边界与前提 + /// - `ctor_obj` 为待分派的构造器(native / 字节码臂);`new_target` 为 + /// newTarget(原型读源与 new.target 槽),非代理路径两者恒同一值。 + pub(crate) fn dispatch_new_target( + &mut self, + ctor_obj: &JsObject, + constructor: JsValue, + new_target: JsValue, + rd: usize, + args: &[JsValue], + call_window: u8, + ) -> Result { + // Proxy [[Construct]]:construct trap 权威(target, argumentsList, + // newTarget);trap 缺失落穿 target 的普通 [[Construct]] 路径。 + if ctor_obj.is_proxy_obj() { + match crate::proxy::proxy_construct_trap(self, ctor_obj, args, new_target) { + Ok(Some(v)) => { + self.regs[rd] = v; + return Ok(false); + } + Ok(None) => { + let target = oxide_builtins::proxy::proxy_state_ref(ctor_obj).expect("状态盒").target; + let target_obj = unsafe { &*target.as_js_object_ptr() }; + return self.dispatch_new_target(target_obj, target, new_target, rd, args, call_window); + } + Err(msg) => { + // trap 抛错(含 revoked / 非可调用 / 非对象返回)须恢复为可捕获 + // JS 异常:原值经 last_uncaught_value 保留,深度 0 就地展开。 + self.raise_call_error(&msg)?; + return Ok(true); + } + } + } + let proto_ptr = self.realm.object_prototype.borrow().as_ptr() as *mut JsObject; let new_obj = self.alloc_object(JsObject::new_empty( oxide_kernel::shape_forge::EMPTY_SHAPE_ID, @@ -99,11 +152,13 @@ impl Vm { // 先于函数体求值,访问器 getter 的异常须原值上抛,非对象结果回落 // Object.prototype)。bound 包装已在上方转发臂返回,不到达此处。 let proto_si = self.kernel_core.perm_interner().intern("prototype").0; + // SAFETY: new_target 为构造器值(非空对象指针)。 + let new_target_obj = unsafe { &*new_target.as_js_object_ptr() }; let proto_val = if ctor_obj.native_fn().is_some() { - self.resolve_property(ctor_obj, proto_si) + self.resolve_property(new_target_obj, proto_si) } else { let pc_before = self.pc; - match self.ordinary_get(ctor_obj, proto_si, constructor) { + match self.ordinary_get(new_target_obj, proto_si, new_target) { Ok(v) if self.pc == pc_before => Some(v), Ok(_) => { // getter 抛且已有 catch/finally 接手:展开已跳到落点,停构造流。 @@ -131,10 +186,6 @@ impl Vm { if ctor_obj.native_fn().is_some() { let new_obj_val = JsValue::object(new_obj as *mut u8); - let mut args = Vec::with_capacity(arg_count); - for i in 0..arg_count.min(256) { - args.push(self.regs[first_arg_reg.wrapping_add(i as u8) as usize]); - } // native 构造器:收口到 call_function_sync(与 spread / bound 变体一致)。 // 该入口统一保存/恢复调用窗口与 253/254 槽且不触碰 255,调用方 // new.target 与 this 跨 native 构造不被污染;receiver 经 arg0 打包, @@ -143,9 +194,9 @@ impl Vm { // 夹持(调用后恢复)。 let saved_new_target = self.regs[255]; let saved_constructing = self.constructing_native; - self.regs[255] = constructor; + self.regs[255] = new_target; self.constructing_native = true; - let result = self.call_function_sync(constructor, new_obj_val, &args); + let result = self.call_function_sync(constructor, new_obj_val, args); self.constructing_native = saved_constructing; self.regs[255] = saved_new_target; match result { @@ -187,7 +238,7 @@ impl Vm { let new_obj_val = JsValue::object(new_obj as *mut u8); // 收敛到统一压帧入口:derived 构造器在 super() 前 this 为 undefined, - // 基类构造器 this = 新对象;new.target = 构造器本身。 + // 基类构造器 this = 新对象;new.target = newTarget。 let this_value = if ctor_obj.is_derived_constructor() { JsValue::undefined() } else { @@ -196,11 +247,8 @@ impl Vm { self.push_bytecode_frame( constructor, this_value, - FrameArgs::RegRange { - first: first_arg_reg, - count: arg_count, - }, - ConstructArgs::new(rd as u8, new_obj_val, constructor), + FrameArgs::Slice(args), + ConstructArgs::new(rd as u8, new_obj_val, new_target), FrameContinuation::None, call_window, )?; diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 876b66e1..535960bf 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -109,14 +109,29 @@ impl Vm { ) -> Result { vm_trace!("ordinary_get_inner: shape={} prop_si={}", obj.shape_id(), prop_name_si); // Proxy [[Get]]:get trap 权威(target、P、Receiver),trap 定义时其返回值 - // 即读结果;trap 未定义落穿普通属性路径。 - if obj.is_proxy_obj() { - let target = oxide_builtins::proxy::proxy_state_ref(obj).expect("状态盒").target; - let key = oxide_builtins::object::key_si_to_js_value(self, prop_name_si); - if let Some(v) = crate::proxy::call_trap(self, obj, self.perm_intern("get"), &[target, key, receiver])? { - return Ok(v); + // 即读结果;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;