From dfa85d3eacf255f80ef1064bcb96cf0ad09f689c Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 24 Sep 2026 02:41:28 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=80=9A=E7=94=A8=E6=9E=84=E9=80=A0=20G?= =?UTF-8?q?pFC=20=E8=AF=BB=E4=BD=93=E4=BC=A0=E6=92=AD=E8=AF=BB=E4=B8=8E?= =?UTF-8?q?=E5=86=85=E5=BB=BA=E5=8E=9F=E5=9E=8B=E5=9B=9E=E8=90=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/disposable_stack.rs | 87 +++++---- crates/oxide_vm/src/dispatch/call.rs | 31 +++- crates/oxide_vm/src/promise/constructor.rs | 100 +++++++++-- crates/oxide_vm/src/vm_dispatch_misc.rs | 61 ++++++- crates/oxide_vm/tests/new_target_tests.rs | 169 ++++++++++++++++++ 5 files changed, 394 insertions(+), 54 deletions(-) diff --git a/crates/oxide_builtins/src/disposable_stack.rs b/crates/oxide_builtins/src/disposable_stack.rs index 1871e9cd..90518e14 100644 --- a/crates/oxide_builtins/src/disposable_stack.rs +++ b/crates/oxide_builtins/src/disposable_stack.rs @@ -73,43 +73,68 @@ fn disposed_reference_error(vm: &mut H) -> JsValue { crate::error::create_reference_error(vm, "DisposableStack has been disposed") } -/// 资源栈构造器公共实现:校验 NewTarget 原型链后建带空状态盒的栈对象。 +/// 接收者原型链是否命中对应栈类型内建原型(上限 16 层,普通调用接收者不命中)。 +fn receiver_intrinsic_proto(this_val: JsValue, stack_proto_val: JsValue) -> bool { + if !this_val.is_object() { + return false; + } + let this_ptr = this_val.as_js_object_ptr(); + if this_ptr.is_null() { + return false; + } + let stack_proto = stack_proto_val.as_js_object_ptr(); + // SAFETY: this_ptr 是 is_object 校验过的当前 session 存活对象指针。 + let mut proto = unsafe { &*this_ptr }.proto(); + for _ in 0..16 { + if !proto.is_object() { + return false; + } + let proto_ptr = proto.as_js_object_ptr(); + if proto_ptr.is_null() { + return false; + } + if std::ptr::eq(proto_ptr, stack_proto) { + return true; + } + // SAFETY: 同上,proto 链指针本 session 存活。 + proto = unsafe { &*proto_ptr }.proto(); + } + false +} + +/// 资源栈构造器公共实现:构造形态校验后经 GpFC 推导原型,建带空状态盒的栈对象。 /// /// # 步骤 -/// 1. 校验 NewTarget:`this` 的原型链须命中对应栈的 prototype -/// (`new X()` 直接命中,子类 `super()` 经子类原型链命中;普通调用抛 TypeError)。 -/// 2. 建空状态盒对象,proto 与 type_tag 按栈类型传入。 +/// 1. 非构造形态 → TypeError。构造形态标记由 NEW/SUPER/construct_with 三入口置位、 +/// 普通调用入口清零;标记未置位的直接 native 调用形态以接收者原型链命中 +/// 对应内建原型视同构造形态。 +/// 2. GetPrototypeFromConstructor:传播式读 new.target 的 "prototype"(访问器 +/// 副作用与异常原值传播);非对象结果回落本栈类型内建原型。 +/// 3. 建空状态盒对象,proto 与 type_tag 按栈类型传入。 fn stack_constructor_impl(vm: &mut H, args: &[u8], proto_val: JsValue, type_tag: u8) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - let is_new_call = this_val.is_object() && { - let stack_proto = proto_val.as_js_object_ptr(); - let this_ptr = this_val.as_js_object_ptr(); - if this_ptr.is_null() { - false - } else { - let mut proto = unsafe { &*this_ptr }.proto(); - let mut found = false; - for _ in 0..16 { - if !proto.is_object() { - break; - } - let proto_ptr = proto.as_js_object_ptr(); - if proto_ptr.is_null() { - break; - } - if std::ptr::eq(proto_ptr, stack_proto) { - found = true; - break; - } - proto = unsafe { &*proto_ptr }.proto(); - } - found - } - }; - if !is_new_call { + if !vm.constructing_native() && !receiver_intrinsic_proto(this_val, proto_val) { return NativeResult::Err(crate::error::create_type_error(vm, "DisposableStack must be called with new")); } - let stack = alloc_disposable_stack(vm, proto_val, type_tag); + let new_target = vm.reg(255); + let gpf_proto = if new_target.is_object() { + let nt_ptr = new_target.as_js_object_ptr(); + // SAFETY: is_object 保证指针非空且对象本 session 存活。 + let nt_obj = unsafe { &*nt_ptr }; + let proto_si = vm.kernel_core().perm_interner().intern("prototype").0; + let proto = match vm.ordinary_get(nt_obj, proto_si, new_target) { + Ok(v) => v, + Err(err) => return NativeResult::Err(crate::iterator::engine_error(vm, &err)), + }; + if proto.is_object() { + proto + } else { + proto_val + } + } else { + proto_val + }; + let stack = alloc_disposable_stack(vm, gpf_proto, type_tag); NativeResult::Ok(JsValue::from_js_object(stack)) } diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 462d0d08..3cd2514f 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -985,11 +985,36 @@ impl Vm { 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; - if let Some(proto_val) = self.resolve_property(ctor_obj, proto_si) { - if proto_val.is_object() { + 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 = proto_val.as_js_object_ptr(); + let proto_obj_ptr = v.as_js_object_ptr(); let _ = new_obj_mut.set_proto(JsValue::from_js_object(proto_obj_ptr)); } } diff --git a/crates/oxide_vm/src/promise/constructor.rs b/crates/oxide_vm/src/promise/constructor.rs index 6f5709be..6e95e42f 100644 --- a/crates/oxide_vm/src/promise/constructor.rs +++ b/crates/oxide_vm/src/promise/constructor.rs @@ -87,7 +87,7 @@ impl Vm { JsValue::from_js_object(self.session.builtin_world().data_view_proto.as_ptr() as *mut JsObject); JsValue::from_js_object(self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, data_view_proto_val))) } else { - let this_ptr = self.alloc_ctor_this(nt_obj)?; + let this_ptr = self.alloc_ctor_this(nt_obj, ctor_obj, new_target)?; JsValue::from_js_object(this_ptr) }; // newTarget 经 reg(255) 暴露给 native 构造器(快照/恢复:call 窗口 @@ -111,12 +111,47 @@ impl Vm { self.call_constructor_bytecode_inline(ctor, ctor_obj, nt_obj, new_target, args) } - /// 分配构造 this:proto = ctor.prototype(缺省 Object.prototype)。 - fn alloc_ctor_this(&mut self, ctor_obj: &JsObject) -> Result<*mut JsObject, JsValue> { + /// 分配构造 this:proto = newTarget.prototype(缺省 Object.prototype)。 + /// + /// 按**构造器**(非 newTarget)种类分读形:native 构造器保持裸存储读 + /// (占位 receiver 的原型不可观测,可观测面由各构造体自重读覆盖,native + /// 面的 getter 触发权归构造体的规范步序位);字节码构造器走传播读 + /// (Ordinary [[Construct]] 的 GpFC 先于函数体求值且读体是唯一读点, + /// 访问器 getter 的异常须原值上抛,非对象结果回落 Object.prototype)。 + fn alloc_ctor_this( + &mut self, nt_obj: &JsObject, ctor_obj: &JsObject, nt_val: JsValue, + ) -> Result<*mut JsObject, JsValue> { let proto_si = self.kernel_core.perm_interner().intern("prototype").0; - let proto_val = match self.resolve_property(ctor_obj, proto_si) { - Some(p) if p.is_object() => p, - _ => JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject), + let object_proto = JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject); + let is_native_ctor = ctor_obj.native_fn().is_some() && ctor_obj.type_tag == JsObject::OBJ_TYPE_CONSTRUCTOR; + let proto_val = if is_native_ctor { + match self.resolve_property(nt_obj, proto_si) { + Some(p) if p.is_object() => p, + _ => object_proto, + } + } else { + let pc_before = self.pc; + match self.ordinary_get(nt_obj, proto_si, nt_val) { + Ok(v) if self.pc == pc_before => { + if v.is_object() { + v + } else { + object_proto + } + } + Ok(_) => { + // depth-0 getter 抛已就地展开跳走(构造入口恒在 native 上下文, + // 结构性不可达):停掉构造流,不续行。 + return Err(JsValue::undefined()); + } + Err(err) => { + // 重入路径:恢复原始异常值,按 construct_with 契约携带原值。 + return Err(self + .last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &err))); + } + } }; Ok(self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val))) } @@ -144,7 +179,7 @@ impl Vm { } // 先拷出寄存器数,释放对代际表的借用后再进入可变借用区。 let callee_reg_count = callee_module.n_registers; - let new_obj_ptr = self.alloc_ctor_this(nt_obj)?; + let new_obj_ptr = self.alloc_ctor_this(nt_obj, ctor_obj, new_target)?; let new_obj_val = JsValue::from_js_object(new_obj_ptr); // derived 构造器 super() 前 this 为 undefined,基类 this = 新对象。 let this_value = if ctor_obj.is_derived_constructor() { @@ -303,15 +338,52 @@ impl Vm { } /// `Promise` 构造器:设置状态盒并同步调用 executor(resolve, reject)。 +/// +/// # 步骤 +/// 1. NewTarget 缺失形态(普通调用 / .call 入口)→ TypeError。 +/// 2. executor 可读性检查(步序先于 GpFC 原型读)。 +/// 3. GetPrototypeFromConstructor:传播式读 new.target 的 "prototype" +/// (访问器副作用与异常原值传播);非对象结果回落 %Promise.prototype%。 +/// 4. 物化 receiver(原型重设 + type_tag + 状态盒)后经原型链检放行。 fn promise_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - // `new` 调用时 this 是 proto 链含 %Promise.prototype% 的新对象;裸调用拒绝。 - if !vm.has_promise_proto(this_val) { + // NewTarget 缺失 = 非构造形态:构造入口置位、普通调用入口清零,据此拒绝。 + if !vm.constructing_native { return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Promise must be called with new")); } - // SAFETY: `has_promise_proto` 已校验 this 为存活对象且原型链含 %Promise.prototype%; - // 此处写 type_tag 后即新建状态盒,无别名。 + let executor = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if !oxide_builtins::iterator::is_callable(executor) { + return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Promise executor is not a function")); + } + // GetPrototypeFromConstructor:异常原值上抛;非对象结果回落内建原型。 + let promise_proto_val = JsValue::from_js_object(vm.promise_proto.as_ptr() as *mut JsObject); + let new_target = vm.reg(255); + let proto = if new_target.is_object() { + let nt_ptr = new_target.as_js_object_ptr(); + // SAFETY: is_object 保证指针非空且对象本 session 存活。 + let nt_obj = unsafe { &*nt_ptr }; + let proto_si = vm.kernel_core.perm_interner().intern("prototype").0; + match vm.ordinary_get(nt_obj, proto_si, new_target) { + Ok(v) if v.is_object() => v, + Ok(_) => promise_proto_val, + Err(err) => { + return NativeResult::Err( + vm.last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, &err)), + ); + } + } + } else { + promise_proto_val + }; + // receiver 原型重设为 GpFC 结果(恒等重设是 no-op,非对象回落重设是语义修正), + // 随后物化状态盒。 + // SAFETY: this 为构造帧分配的存活对象;此处写 type_tag 后即新建状态盒,无别名。 let obj = unsafe { &mut *this_val.as_js_object_ptr() }; + if let Err(msg) = obj.set_proto(proto) { + return NativeResult::Err(oxide_builtins::error::create_type_error(vm, msg)); + } obj.type_tag = JsObject::OBJ_TYPE_PROMISE; let resolve = vm.make_resolve_reject_fn(this_val, false, false); let reject = vm.make_resolve_reject_fn(this_val, true, false); @@ -325,9 +397,9 @@ fn promise_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { promoted_clone: std::ptr::null_mut(), }); obj.set_native_data(Box::into_raw(state) as *mut u8); - let executor = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - if !oxide_builtins::iterator::is_callable(executor) { - return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Promise executor is not a function")); + // 物化后的 receiver 原型链须含 %Promise.prototype%(构造面放行兜底)。 + if !vm.has_promise_proto(this_val) { + return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Promise must be called with new")); } match vm.call_function_sync(executor, JsValue::undefined(), &[resolve, reject]) { Ok(_) => NativeResult::Ok(this_val), diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 520a11aa..eb7c071a 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -69,11 +69,38 @@ impl Vm { JsValue::from_js_object(proto_ptr), )); + // GpFC 按构造器种类分读形:native 构造器体(native_fn + CONSTRUCTOR + // tag)保持裸存储读(占位 receiver 的原型不可观测,可观测面由构造体 + // 自重读覆盖);字节码构造器走传播读(Ordinary [[Construct]] 的 GpFC + // 先于函数体求值,访问器 getter 的异常须原值上抛,非对象结果回落 + // Object.prototype)。bound 包装已在上方转发臂返回,不到达此处。 let proto_si = self.kernel_core.perm_interner().intern("prototype").0; - if let Some(proto_val) = self.resolve_property(ctor_obj, proto_si) { - if proto_val.is_object() { + 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 = proto_val.as_js_object_ptr(); + let proto_obj_ptr = v.as_js_object_ptr(); let _ = new_obj_mut.set_proto(JsValue::from_js_object(proto_obj_ptr)); } } @@ -224,11 +251,33 @@ impl Vm { } // 新对象原型取最内层 target 的 prototype(bound 包装自身无 prototype)。 + // 读形与 NEW 读体同构:native target 裸读,字节码 target 传播读 + // (getter 异常原值上抛,非对象回落 Object.prototype)。 let proto_si = self.kernel_core.perm_interner().intern("prototype").0; - if let Some(proto_val) = self.resolve_property(target_obj, proto_si) { - if proto_val.is_object() { + let proto_val = if target_obj.native_fn().is_some() { + self.resolve_property(target_obj, proto_si) + } else { + let pc_before = self.pc; + match self.ordinary_get(target_obj, proto_si, target_val) { + Ok(v) if self.pc == pc_before => Some(v), + Ok(_) => { + 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 = proto_val.as_js_object_ptr(); + let proto_obj_ptr = v.as_js_object_ptr(); let _ = new_obj_mut.set_proto(JsValue::from_js_object(proto_obj_ptr)); } } diff --git a/crates/oxide_vm/tests/new_target_tests.rs b/crates/oxide_vm/tests/new_target_tests.rs index 3aa88ad8..9665c098 100644 --- a/crates/oxide_vm/tests/new_target_tests.rs +++ b/crates/oxide_vm/tests/new_target_tests.rs @@ -145,3 +145,172 @@ fn native_construct_keeps_instance_semantics() { let result = eval(&mut vm, "(function(){ var d = new Date(0); return d instanceof Date; })()").unwrap(); assert_bool(result, true); } + +// ── GpFC 通用构造面(读体按构造器种类分支 + 构造体自重读)回归钉 ── + +fn throwing_proto_ctor(source_tail: &str) -> String { + format!( + "var bound = (function(){{}}).bind(); \ + Object.defineProperty(bound, 'prototype', {{ get: function() {{ \ + calls++; throw new Error('boom'); }} }}); \ + var calls = 0; {source_tail}" + ) +} + +#[test] +fn gpfc_new_expression_bytecode_ctor_propagates_getter_throw() { + // 字节码构造器 prototype 为访问器且 getter 抛错:new 表达式须原值上抛(getter 触发)。 + 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 }", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "boom:1"); +} + +#[test] +fn gpfc_new_expression_spread_bytecode_ctor_propagates_getter_throw() { + // spread 构造变体同形:getter 抛原值上抛且恰触发一次。 + 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 }", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "boom:1"); +} + +#[test] +fn gpfc_reflect_construct_bytecode_ctor_bound_nt_propagates_getter_throw() { + // Reflect.construct(JS 构造器, 实参, bound NT):bound 自身 prototype 访问器 + // getter 抛错须原值上抛(读体对字节码构造器为唯一 GpFC 读点)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + &throwing_proto_ctor("try { Reflect.construct(function(){}, [], bound); } catch (e) { e.message }"), + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "boom"); +} + +#[test] +fn gpfc_promise_executor_check_precedes_proto_read() { + // Promise 体步序:executor 可读性检查先于 GpFC 原型读(非 callable executor + // + 抛 getter 的 NT → TypeError 胜 Test262Error 形)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + &throwing_proto_ctor("try { Reflect.construct(Promise, [], bound); } catch (e) { e.constructor.name }"), + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "TypeError"); +} + +#[test] +fn gpfc_promise_abrupt_proto_read_propagates() { + // Promise 体 GpFC 自重读:executor callable 时 getter 抛原值上抛。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + &throwing_proto_ctor("try { Reflect.construct(Promise, [function(){}], bound); } catch (e) { e.message }"), + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "boom"); +} + +#[test] +fn promise_plain_call_rejected_by_construct_guard() { + // NewTarget 缺失形态:普通调用与 .call 入口一律 TypeError(含 receiver 为 + // 真实 Promise 实例的 .call 面)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var r = []; \ + try { Promise(function(){}); } catch (e) { r.push(e.constructor.name); } \ + try { Promise.call(null, function(){}); } catch (e) { r.push(e.constructor.name); } \ + var p = new Promise(function(){}); \ + try { Promise.call(p, function(){}); } catch (e) { r.push(e.constructor.name); } \ + r.join(',')", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "TypeError,TypeError,TypeError"); +} + +#[test] +fn gpfc_disposable_stack_newtarget_proto_forms() { + // DS 体 GpFC 自重读三形:custom NT → NT.prototype 直用;非对象 NT.prototype + // → 回落 %DisposableStack.prototype%;抛 getter → 原值上抛且恰一次。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var s = Reflect.construct(DisposableStack, [], Object); \ + var a = Object.getPrototypeOf(s) === Object.prototype; \ + function nt() {} nt.prototype = undefined; \ + var s2 = Reflect.construct(DisposableStack, [], nt); \ + var b = Object.getPrototypeOf(s2) === DisposableStack.prototype; \ + a + ':' + b", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "true:true"); + + let mut vm = Vm::new(); + let result = eval( + &mut vm, + &throwing_proto_ctor( + "try { Reflect.construct(DisposableStack, [], bound); } catch (e) { e.message + ':' + calls }", + ), + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "boom:1"); +} + +#[test] +fn gpfc_async_disposable_stack_newtarget_proto_forms() { + // ADS 同形三钉:custom NT 直用 / 非对象回落内建原型 / 抛 getter 原值一次。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var s = Reflect.construct(AsyncDisposableStack, [], Object); \ + var a = Object.getPrototypeOf(s) === Object.prototype; \ + function nt() {} nt.prototype = null; \ + var s2 = Reflect.construct(AsyncDisposableStack, [], nt); \ + var b = Object.getPrototypeOf(s2) === AsyncDisposableStack.prototype; \ + a + ':' + b", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "true:true"); + + let mut vm = Vm::new(); + let result = eval( + &mut vm, + &throwing_proto_ctor( + "try { Reflect.construct(AsyncDisposableStack, [], bound); } catch (e) { e.message + ':' + calls }", + ), + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "boom:1"); +} + +#[test] +fn gpfc_native_ctor_raw_read_unchanged() { + // native 构造器臂回归:new Map()/new Set() 实例原型不变;Reflect.construct + // 以数据属性 prototype 的 NT 构造 Map 时原型直用(native 臂保持裸读)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var a = Object.getPrototypeOf(new Map()) === Map.prototype; \ + var b = Object.getPrototypeOf(new Set()) === Set.prototype; \ + function nt() {} nt.prototype = Map.prototype; \ + var c = Object.getPrototypeOf(Reflect.construct(Map, [], nt)) === Map.prototype; \ + a + ':' + b + ':' + c", + ) + .unwrap(); + assert_eq!(vm.lookup_str(result).unwrap(), "true:true:true"); +}