fix: TA 构造形态改读构造入口标记替代寄存器推断

This commit is contained in:
2026-09-22 19:41:55 +00:00
parent b5b456346c
commit dce132acec
10 changed files with 94 additions and 12 deletions
+8 -5
View File
@@ -554,11 +554,14 @@ fn to_collect_len(n: f64) -> usize {
fn typed_array_new<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
// reg 255(new.target)为对象即构造调用(NEW/SUPER_CALL/construct 三路径同形):
// 视图数据物化到 receiver(其原型 = new.target.prototype,派生类 super() 与
// species 构造由此拿到子类实例);普通调用(new.target 为 undefined)按规范
// TypedArrayCreate 忽略 this、自建新对象。
let in_construct = vm.reg(255).is_object() && this_val.is_object();
// 调用形态取构造入口标记(NEW/SUPER native 臂与 construct_with 三入口调用前
// 置位、普通调用入口调用前清零),不推断 new.target 寄存器:native 调用与
// 调用方共享寄存器文件,类构造器帧内 new.target 槽残留类构造器对象,按
// 寄存器推断会把成员式普通调用误判为构造(双物化 receiver、旧数据盒泄漏)。
// 构造形态下视图数据物化到 receiver(其原型 = new.target.prototype,派生类
// super() 与 species 构造由此拿到子类实例);普通形态按规范 TypedArrayCreate
// 忽略 this、自建新对象。
let in_construct = vm.constructing_native() && this_val.is_object();
let first = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::int(0) };
let bpe = kind.bytes_per_element();
+8
View File
@@ -45,6 +45,14 @@ pub trait VmHost {
self.native_overflow_at(idx - window)
}
}
/// 当前 native 调用是否以构造形态发起(NEW/SUPER native 臂与 `construct_with`
/// native 臂置 true,普通调用入口置 false,调用结束即恢复)。
///
/// # 边界与前提
/// - 仅在 native 实现内部读取;构造器 builtin 据此判定向 receiver 物化,
/// 不得用寄存器推断(native 调用与调用方共享寄存器文件,new.target
/// 槽在类构造器帧内残留类构造器对象)。
fn constructing_native(&self) -> bool;
// 对象分配 / 字符串创建
fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject;
+28 -4
View File
@@ -46,7 +46,8 @@ impl Vm {
/// - `Err` 保留异常原值:用户回调 throw 的原始值经 catch 原样收到,不包装成 Error。
///
/// # 副作用
/// - 修改 `native_call_depth`、`regs[0]`、`regs[254]`,可能压帧或触发异常展开。
/// - 修改 `native_call_depth`、`regs[0]`、`regs[254]`,普通调用形态清零
/// `constructing_native`,可能压帧或触发异常展开。
///
/// # 注意事项
/// - `native_fn_ptr_to_fn` 是 NativeFnPtr → NativeFn 的唯一强制转换点,SAFETY 前提
@@ -72,6 +73,9 @@ impl Vm {
// `this` 寄存器里。
let saved_this = self.regs[254];
self.regs[254] = callee;
// 普通调用形态:清零构造形态标记,防止外层构造窗口残留误判
// (成员式普通调用落在构造器帧内时 new.target 槽仍为类构造器)。
self.constructing_native = false;
self.native_call_depth += 1;
let result = func(self, args_slice);
self.native_call_depth -= 1;
@@ -101,6 +105,8 @@ impl Vm {
return self.raise_type_error("CALL target is not callable");
}
if obj.native_fn().is_some() {
// 尾调用转发为普通调用形态。
self.constructing_native = false;
match self.call_function_sync(callee, this, &args) {
Ok(val) => {
self.regs[0] = val;
@@ -596,7 +602,13 @@ impl Vm {
let args: Vec<JsValue> = (0..arg_count)
.map(|i| self.regs[first_arg_reg.wrapping_add(i as u8) as usize])
.collect();
match self.call_function_sync(super_ctor, derived_this, &args) {
// 构造形态标记夹持(SUPER native 臂不写 reg255,new.target 继承
// 外层类构造器帧,形态判定只能靠本标记)。
let saved_constructing = self.constructing_native;
self.constructing_native = true;
let result = self.call_function_sync(super_ctor, derived_this, &args);
self.constructing_native = saved_constructing;
match result {
Ok(val) => {
// super() 返回实例的 [[Prototype]] 须设为 new.target.prototype
//(native 构造器不知道 new.target,由调用方设置)。
@@ -852,6 +864,8 @@ impl Vm {
.map(|_| true);
}
if obj.native_fn().is_some() {
// spread 普通调用形态:清零构造形态标记。
self.constructing_native = false;
match self.call_function_sync(callee, this_value, &args) {
Ok(v) => {
self.regs[0] = v;
@@ -983,10 +997,14 @@ impl Vm {
if ctor_obj.native_fn().is_some() {
// native 构造器:receiver 为新对象,值传递调用。newTarget 快照后置入
// reg(255) 暴露给 native 构造器(调用后恢复原值)。
// 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) => {
@@ -1091,7 +1109,13 @@ impl Vm {
};
if super_obj.native_fn().is_some() {
match self.call_function_sync(super_ctor, derived_this, &args) {
// 构造形态标记夹持(SUPER native 臂不写 reg255,new.target 继承
// 外层类构造器帧,形态判定只能靠本标记)。
let saved_constructing = self.constructing_native;
self.constructing_native = true;
let result = self.call_function_sync(super_ctor, derived_this, &args);
self.constructing_native = saved_constructing;
match result {
Ok(val) => {
// super() 返回实例的 [[Prototype]] 须设为 new.target.prototype
let instance = if val.is_object() { val } else { derived_this };
+5 -1
View File
@@ -90,10 +90,14 @@ impl Vm {
let this_ptr = self.alloc_ctor_this(nt_obj)?;
JsValue::from_js_object(this_ptr)
};
// newTarget 经 reg(255) 暴露给 native 构造器(快照/恢复:call 窗口不触及 255)。
// newTarget 经 reg(255) 暴露给 native 构造器(快照/恢复:call 窗口
// 不触及 255);构造形态标记同窗夹持(调用后恢复)。
let saved_nt = self.regs[255];
let saved_constructing = self.constructing_native;
self.regs[255] = new_target;
self.constructing_native = true;
let result = self.call_function_sync(ctor, this_val, args);
self.constructing_native = saved_constructing;
self.regs[255] = saved_nt;
return match result {
Ok(ret) if ret.is_object() => Ok(ret),
+3
View File
@@ -106,6 +106,9 @@ impl Vm {
self.native_call_depth += 1;
let result = func(self, &arg_regs);
self.native_call_depth -= 1;
// 构造形态标记不跨 native 调用存活:本次调用内的置位(构造臂)
// 与清零(普通调用臂)在此收口,异常展开亦不残留。
self.constructing_native = false;
// 溢出区随本次调用结束截断回收,并还原外层的溢出区描述(嵌套调用安全)。
if args.len() > Self::SYNC_NATIVE_ARG_LIMIT {
self.spill_stack.truncate(overflow_base);
+7
View File
@@ -304,6 +304,13 @@ pub struct Vm {
/// 已做非对象回退 this)交付给恢复方(与 generator_dispatch 同语义,
/// 同样经 InlineSyncState 限定作用域于属主 dispatch)。
pub(crate) construct_dispatch: bool,
/// 当前 native 调用是否以构造形态发起(NEW/SUPER native 臂与
/// `construct_with` native 臂在调用前置 true,普通调用入口前置 false):
/// builtin 构造器(`typed_array_new`)据此判定向 receiver 物化,替代
/// 寄存器推断——native 调用与调用方共享寄存器文件,类构造器帧内的
/// new.target 槽会残留类构造器对象,按寄存器判定会把成员式普通调用
/// 误判为构造。调用结束即恢复/清零,不跨 native 调用存活。
pub(crate) constructing_native: bool,
/// 当前正在执行的异步生成器上下文对象(`OBJ_TYPE_ASYNC_GENERATOR`,持有
/// `AsyncGeneratorState` 快照)。AWAIT dispatch 据此登记异步生成器恢复反应;
/// 跨嵌套 async 调用保存/恢复(与 `async_context` 同生命周期,二者互斥占用)。
+3
View File
@@ -22,6 +22,9 @@ impl oxide_runtime_api::VmHost for Vm {
fn native_overflow_at(&self, i: usize) -> JsValue {
self.spill_stack[self.native_overflow_base + i]
}
fn constructing_native(&self) -> bool {
self.constructing_native
}
fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject {
self.alloc_object(obj)
}
+10 -2
View File
@@ -88,10 +88,14 @@ impl Vm {
// 该入口统一保存/恢复调用窗口与 253/254 槽且不触碰 255,调用方
// new.target 与 this 跨 native 构造不被污染;receiver 经 arg0 打包,
// native 侧经 reg(args[0]) 读取,构造语义不变。newTarget 快照后置入
// reg(255) 暴露给 native 构造器(调用后恢复原值)。
// 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) => {
@@ -230,10 +234,14 @@ impl Vm {
if target_obj.native_fn().is_some() {
// native 构造器:receiver = 新对象,值传递调用(错误原值恢复后展开)。
// new.target 按 bound [[Construct]] 语义取最外层 bound 包装(wrapper_val),
// 快照后置入 reg(255) 暴露给 native 构造器(调用后恢复原值)。
// 快照后置入 reg(255) 暴露给 native 构造器(调用后恢复原值);构造
// 形态标记同窗夹持(调用后恢复)。
let saved_new_target = self.regs[255];
let saved_constructing = self.constructing_native;
self.regs[255] = wrapper_val;
self.constructing_native = true;
let result = self.call_function_sync(target_val, new_obj_val, &call_args);
self.constructing_native = saved_constructing;
self.regs[255] = saved_new_target;
match result {
Ok(v) => {
+2
View File
@@ -114,6 +114,7 @@ impl Vm {
async_suspended: false,
async_dispatch: false,
construct_dispatch: false,
constructing_native: false,
async_gen_context: None,
async_gen_dispatch: false,
async_gen_suspended: false,
@@ -245,6 +246,7 @@ impl Vm {
async_suspended: false,
async_dispatch: false,
construct_dispatch: false,
constructing_native: false,
async_gen_context: None,
async_gen_dispatch: false,
async_gen_suspended: false,
@@ -691,3 +691,23 @@ fn typed_array_plain_call_and_construct_zero_drift() {
.unwrap();
assert!(result.as_bool());
}
#[test]
fn typed_array_plain_member_call_in_class_ctor_not_construct() {
// 类构造器帧内对 TA 构造器的成员式普通调用是普通形态:返回全新 TA,
// receiver 不物化(长度与 buffer 不动),receiver 为普通对象时亦不改建。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { class Sub extends Uint8Array { constructor() { \
super(4); this.f = Uint8Array; var r = this.f(2); this.r = r; this.len = this.length; \
var o = {}; o.f = Uint8Array; var p = o.f(2); this.p = p; this.o = o; } } \
var s = new Sub(); \
return s.r !== s && s.r instanceof Uint8Array && \
Object.getPrototypeOf(s.r) === Uint8Array.prototype && s.r.length === 2 && \
s.len === 4 && s.length === 4 && s.buffer.byteLength === 4 && \
s.p !== s.o && s.p.length === 2 && typeof s.o.length === 'undefined'; })()",
)
.unwrap();
assert!(result.as_bool());
}