mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: CREATE_CLOSURE 跨 run 帧按执行帧表代际解析——嵌套闭包错代盖写闭合,挂起恢复重设活跃代际,OOB 消息上界对齐实际查表代际
This commit is contained in:
@@ -105,11 +105,13 @@ impl Vm {
|
||||
pub(crate) fn dispatch_create_closure(&mut self, rd: usize, instr: u32) -> Result<(), String> {
|
||||
let sub_idx = opcode::imm16(instr) as u32;
|
||||
vm_trace!("CREATE_CLOSURE rd={} sub_idx={}", rd, sub_idx);
|
||||
// 闭包按当前 run 的平表解析(CREATE_CLOSURE 只在本 run 装载的模块内发射)。
|
||||
let table = self.current_table();
|
||||
// 指令操作数是发射期 flat_id,口径为执行帧字节码所属代际的平表:跨 run
|
||||
// 调用在当前代际上下文执行旧代字节码,须按执行帧的表代际解析,按当前
|
||||
// 代际会悬空(表更短)或命中他代异模块(表足够长)。
|
||||
let table = self.active_table();
|
||||
if sub_idx == 0 || (sub_idx as usize) >= table.modules.len() {
|
||||
// 逃逸闭包(函数对象在定义模块之外被创建)时 sub_idx 相对定义模块,
|
||||
// 超出当前平表上下文——按运行时错误处理而非索引越界 panic。
|
||||
// sub_idx 超出执行帧代际平表:定义模块缺失或发射期编号错位——按
|
||||
// 运行时错误处理而非索引越界 panic。
|
||||
return Err(format!(
|
||||
"CREATE_CLOSURE: sub_module_index {} out of bounds (max {})",
|
||||
sub_idx,
|
||||
@@ -124,8 +126,11 @@ impl Vm {
|
||||
let upvalue_captures = sub.upvalue_captures.clone();
|
||||
let function_name = sub.function_name.clone();
|
||||
let function_length = sub.function_length;
|
||||
// 新闭包盖执行帧代际:sub_idx 与 gen 同域,跨 run 调用时新闭包仍解析
|
||||
// 回定义模块所在的原表。
|
||||
let result = self.create_function_object(
|
||||
sub_idx,
|
||||
self.active_table_gen,
|
||||
is_arrow,
|
||||
is_class_constructor,
|
||||
is_derived_constructor,
|
||||
@@ -507,10 +512,11 @@ impl Vm {
|
||||
} else if super_obj.sub_module_index() > 0 {
|
||||
let sub_idx = super_obj.sub_module_index() as usize;
|
||||
if self.callee_module(super_obj).is_none() {
|
||||
// 上界取 super 目标自身代际平表长度(跨 run 调用时可异于当前代际)。
|
||||
return Err(format!(
|
||||
"SUPER_CALL: sub_module_index {} out of bounds (max {})",
|
||||
sub_idx,
|
||||
self.current_table().modules.len()
|
||||
self.tables.get(&super_obj.table_gen()).map(|t| t.modules.len()).unwrap_or(0)
|
||||
));
|
||||
}
|
||||
// 收敛到统一压帧入口:this = derived_this(super() 把实例交予父构造器),
|
||||
|
||||
@@ -107,13 +107,19 @@ impl SuspendedFrame {
|
||||
}
|
||||
|
||||
/// 恢复进 VM:写回各栈段、在途异常/完成,按 callee 记录的表代际重激活
|
||||
/// immutables。
|
||||
/// immutables,并把执行键维度(表代际 / flat_id)还原到被恢复模块。
|
||||
///
|
||||
/// # 边界
|
||||
/// - `callee` 须为挂起函数对象值(状态盒 `callee` 槽):代际按对象创建期
|
||||
/// 记录解析,存活函数对象按代际保活其表,跨 run 恢复命中同一张表。
|
||||
/// - callee 非对象(gen 0 哨兵)、代际表已被回收或下标越界返回 Err,
|
||||
/// 调用方须按各自路径回滚并报错(保持现有三处行为)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - `active_table_gen` / `active_flat_id` 还原为被恢复模块的 (代际,
|
||||
/// flat_id):恢复体后续发射的标签模板须按本模块 (代际, flat_id) 命中
|
||||
/// 模板缓存——不还原则同顶层连续恢复两代挂起体撞调用方侧同键,静默
|
||||
/// 取回他侧模板对象。
|
||||
pub fn restore_into(&mut self, vm: &mut Vm, callee: JsValue) -> Result<(), String> {
|
||||
vm.regs = *self.regs;
|
||||
vm.pc = self.pc;
|
||||
@@ -131,6 +137,9 @@ impl SuspendedFrame {
|
||||
Some(c) => vm.activate_immutables(gen, self.sub_idx as usize, &c),
|
||||
None => return Err("suspended state module table is no longer available".into()),
|
||||
}
|
||||
// 模板缓存键维度随模块恢复:与 activate_immutables 同位还原。
|
||||
vm.active_table_gen = gen;
|
||||
vm.active_flat_id = self.sub_idx;
|
||||
vm.active_reg_limit = self.active_reg_limit;
|
||||
vm.root_reg_limit = self.root_reg_limit;
|
||||
vm.frames.clear();
|
||||
|
||||
+24
-12
@@ -800,16 +800,23 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前代际的子模块平表(动态扩表与测试注入的目标)。
|
||||
pub(crate) fn current_table(&self) -> &TableGen {
|
||||
self.tables.get(&self.current_gen).expect("当前代际表构造期已预登记")
|
||||
}
|
||||
|
||||
/// 当前代际的子模块平表(可变)。
|
||||
/// 当前代际的子模块平表(可变,动态扩表与测试注入的目标)。
|
||||
pub(crate) fn current_table_mut(&mut self) -> &mut TableGen {
|
||||
self.tables.get_mut(&self.current_gen).expect("当前代际表构造期已预登记")
|
||||
}
|
||||
|
||||
/// 当前执行帧所属代际的子模块平表:按帧切换时记录的 `active_table_gen`
|
||||
/// 解析(压帧 / inline 入口 / run 顶层 / 挂起恢复置位,弹帧还原),跨 run
|
||||
/// 调用时可异于 `current_gen`——帧内指令操作数是帧字节码所属代际平表里的
|
||||
/// flat_id,须按该代际解析。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 执行帧代际表恒在注册表中:顶层即当前代际(恒保留),帧 / inline 的
|
||||
/// 代际由存活 callee 函数对象保活,挂起恢复已按 callee 对象验证在位。
|
||||
pub(crate) fn active_table(&self) -> &TableGen {
|
||||
self.tables.get(&self.active_table_gen).expect("执行帧的代际表须在注册表中")
|
||||
}
|
||||
|
||||
/// 函数对象 `sub_module_index` 指向的子模块条目:按对象自身记录的表代际
|
||||
/// 解析平表后按下标定位。
|
||||
///
|
||||
@@ -2672,7 +2679,7 @@ impl Vm {
|
||||
// 平表变长后同步扩容常量缓存,否则激活新模块常量时越界 panic。
|
||||
table.immutables.resize(table.modules.len(), OnceLock::new());
|
||||
|
||||
let func_val = self.create_function_object(base, false, false, false, false);
|
||||
let func_val = self.create_function_object(base, self.current_gen, false, false, false, false);
|
||||
let func_obj = unsafe { &mut *func_val.as_js_object_ptr() };
|
||||
let length_si = self.kernel_core.perm_interner().intern("length").0;
|
||||
let name_si = self.kernel_core.perm_interner().intern("name").0;
|
||||
@@ -2719,7 +2726,7 @@ impl Vm {
|
||||
Arc::make_mut(&mut table.modules).extend(added);
|
||||
// 平表变长后同步扩容常量缓存,否则激活新模块常量时越界 panic。
|
||||
table.immutables.resize(table.modules.len(), OnceLock::new());
|
||||
Ok(self.create_function_object(base, false, false, false, false))
|
||||
Ok(self.create_function_object(base, self.current_gen, false, false, false, false))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2752,6 +2759,11 @@ fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec<Arc<Compiled
|
||||
|
||||
#[cfg(test)]
|
||||
impl Vm {
|
||||
/// 测试用:当前代际的子模块平表只读视图(消费点全在测试钉)。
|
||||
pub(crate) fn current_table(&self) -> &TableGen {
|
||||
self.tables.get(&self.current_gen).expect("当前代际表构造期已预登记")
|
||||
}
|
||||
|
||||
/// 测试用:整表替换当前代际的子模块平表并同步常量缓存槽位(绕过 run() 装载,
|
||||
/// 直接注入模块表后压帧/内联调用)。
|
||||
pub(crate) fn install_module_table_for_test(&mut self, modules: Arc<Vec<Arc<CompiledModule>>>) {
|
||||
@@ -3205,7 +3217,7 @@ mod tests {
|
||||
vm.active_reg_limit = 254;
|
||||
vm.regs[253] = JsValue::int(42);
|
||||
|
||||
let callee = vm.create_function_object(1, false, false, false, false);
|
||||
let callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
||||
let callee_obj = unsafe { &*callee.as_js_object_ptr() };
|
||||
let result = vm
|
||||
.call_bytecode_function_inline(callee, callee_obj, JsValue::undefined(), &[])
|
||||
@@ -3229,7 +3241,7 @@ mod tests {
|
||||
vm.regs[253] = JsValue::int(7);
|
||||
vm.regs[254] = JsValue::int(8);
|
||||
|
||||
let inner_callee = vm.create_function_object(1, false, false, false, false);
|
||||
let inner_callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
||||
let outer_native = native_function(&mut vm, native_nested_inline_254);
|
||||
let result = vm
|
||||
.call_function_sync(outer_native, JsValue::int(99), &[inner_callee])
|
||||
@@ -3258,7 +3270,7 @@ mod tests {
|
||||
vm.regs[1] = JsValue::int(10);
|
||||
vm.regs[2] = JsValue::int(20);
|
||||
|
||||
let callee = vm.create_function_object(1, false, false, false, false);
|
||||
let callee = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
||||
vm.push_bytecode_frame(
|
||||
callee,
|
||||
JsValue::undefined(),
|
||||
@@ -3300,7 +3312,7 @@ mod tests {
|
||||
// 调用方实参区 regs[1..3):arg0=10, arg1=20;regs[2] 同时是 callee 形参槽(param_base=2)
|
||||
vm.regs[1] = JsValue::int(10);
|
||||
vm.regs[2] = JsValue::int(20);
|
||||
vm.regs[5] = vm.create_function_object(1, false, false, false, false);
|
||||
vm.regs[5] = vm.create_function_object(1, vm.current_gen, false, false, false, false);
|
||||
|
||||
vm.dispatch_new_expression(0, 5, 1).expect("NEW 压帧成功");
|
||||
|
||||
|
||||
@@ -105,11 +105,12 @@ impl Vm {
|
||||
let sub = match self.callee_module(ctor_obj) {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
// 上界取构造器自身代际平表长度(跨 run 调用时可异于当前代际)。
|
||||
return Err(format!(
|
||||
"NEW_EXPRESSION: sub_module_index {} out of bounds (max {})",
|
||||
sub_idx,
|
||||
self.current_table().modules.len()
|
||||
))
|
||||
self.tables.get(&ctor_obj.table_gen()).map(|t| t.modules.len()).unwrap_or(0)
|
||||
));
|
||||
}
|
||||
};
|
||||
// 生成器函数不是构造器:`new g()` 抛 TypeError。
|
||||
|
||||
@@ -689,15 +689,20 @@ impl Vm {
|
||||
|
||||
/// 为 BytecodeFunc 常量创建函数 JsObject。
|
||||
/// 当 `is_arrow` 为 true 时,捕获当前 `this`(regs[254]),供调用时词法 this 绑定。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `gen` 为函数对象所属表代际:`sub_idx` 的口径与对象头的 `table_gen`
|
||||
/// 盖写均以它为准(闭包创建传执行帧代际,动态构造传当前代际——动态扩表
|
||||
/// 追加进当前代际平表)。
|
||||
pub(crate) fn create_function_object(
|
||||
&mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool,
|
||||
&mut self, sub_idx: u32, gen: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool,
|
||||
needs_home_object: bool,
|
||||
) -> JsValue {
|
||||
// 生成器函数对象:原型为 %GeneratorFunction.prototype%(constructor 链解析到
|
||||
// "GeneratorFunction"),且不像普通函数那样拥有 `prototype` 属性。
|
||||
// 按当前代际平表解析:函数对象恒在本次 run 内创建(CREATE_CLOSURE /
|
||||
// 动态构造),sub_idx 口径即当前代际表。
|
||||
let table = self.current_table();
|
||||
// 标志按函数对象所属代际平表解析:跨 run 调用时执行帧代际可异于当前代际,
|
||||
// sub_idx 口径与 gen 同域。
|
||||
let table = self.tables.get(&gen).expect("函数对象的代际表须在注册表中");
|
||||
let is_generator = table.modules.get(sub_idx as usize).map(|m| m.is_generator).unwrap_or(false);
|
||||
let is_async = table.modules.get(sub_idx as usize).map(|m| m.is_async).unwrap_or(false);
|
||||
// 异步生成器(`async function*`)函数对象:原型为 %AsyncGeneratorFunction.prototype%。
|
||||
@@ -716,7 +721,7 @@ impl Vm {
|
||||
obj.set_sub_module_index(sub_idx);
|
||||
// 记录创建期表代际:调用点按 (table_gen, sub_module_index) 解析子模块
|
||||
// 平表,跨 run 换表后存活函数仍命中原表(注册表按代际保活)。
|
||||
obj.set_table_gen(self.current_gen);
|
||||
obj.set_table_gen(gen);
|
||||
obj.set_class_constructor(is_class_constructor);
|
||||
obj.set_derived_constructor(is_derived_constructor);
|
||||
let _ = needs_home_object;
|
||||
|
||||
@@ -278,6 +278,26 @@ fn dynamic_function_survives_reset_and_calls() {
|
||||
));
|
||||
}
|
||||
|
||||
/// 跨 run 嵌套闭包:run1 的 f 体内定义嵌套函数 g(f 帧内 CREATE_CLOSURE 按
|
||||
/// run1 代际平表发射 flat_id);reset 后 run2 调用 f——嵌套闭包须按执行帧
|
||||
/// 代际解析 flat 表,返回闭包的 name/调用值须命中 f 模块的字节码。
|
||||
#[test]
|
||||
fn cross_run_call_nested_closure_resolves_frame_generation() {
|
||||
// 当前代际平表长度 ≥ 旧代索引:按当前代际解析会静默命中他模块
|
||||
// (错 name / 错字节码),值断言与 name 断言同时捕获。
|
||||
assert!(eval_two_phases(
|
||||
"function f(){ function g(){ return 41 + 1; } return g; } globalThis.f = f; 0",
|
||||
"var filler_a = function(){}; var filler_b = function(){}; \
|
||||
var h = globalThis.f(); h() === 42 && h.name === 'g'",
|
||||
));
|
||||
// 当前代际平表更短(run2 脚本无顶层函数):旧代索引越界,合法代码不得
|
||||
// 转显式 Err。
|
||||
assert!(eval_two_phases(
|
||||
"function f(){ function g(){ return 41 + 1; } return g; } globalThis.f = f; 0",
|
||||
"var h = globalThis.f(); h() === 42 && h.name === 'g'",
|
||||
));
|
||||
}
|
||||
|
||||
/// Map 原生盒按键值直插不经写屏障:epoch 值跨 reset 由边界晋升克隆进
|
||||
/// session。run2 先分配新 epoch 对象覆写旧内存再读盒内值——未晋升则读到
|
||||
/// 覆写后的垃圾。
|
||||
|
||||
Reference in New Issue
Block a user