mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 含 try 指令的函数跳过调用窗口编码防异常 handler 丢存活寄存器
异常边只从 TRY 标记所在 BB 发出,分支/循环 BB 内调用点的 liveness 不含仅 catch/finally 存活的寄存器;按存活上界截断窗口会使 unwind 恢复后 handler 读到 callee 残留垃圾值(可为悬垂对象指针)。含 TRY_BEGIN/TRY_FINALLY_BEGIN 的函数 整体跳过窗口编码(回退全量窗口),恢复基线保守行为,普通函数不受影响。 补分支内调用 + catch 读 try 前变量的 Red/Green 测试与 finally 回归锚点。
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
//! 物理寄存器最大槽号 + 1(即压帧窗口 `regs[0..上界]`);上界 0 表示未编码
|
||||
//! (运行时回退到调用方 `active_reg_limit` 全量窗口)。`regs[254]/[255]`
|
||||
//! (this/new.target)由帧单独保存不占窗口,计算时排除。生成器 / 异步函数体
|
||||
//! 内部调用点不编码——挂起恢复按全量寄存器快照搬移,保持保守语义。
|
||||
//! 内部调用点不编码——挂起恢复按全量寄存器快照搬移,保持保守语义。含 TRY
|
||||
//! 指令的函数同样不编码——异常 handler 的存活集不经分支/循环内调用点传播,
|
||||
//! 截断窗口会丢仅 handler 存活的槽(见 `encode_call_window` 内 has_try 说明)。
|
||||
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_ir::IRFunction;
|
||||
@@ -21,6 +23,13 @@ pub fn encode_call_window(f: &mut IRFunction, live: &LiveInfo) {
|
||||
if f.insts.is_empty() && f.nested.is_empty() {
|
||||
return;
|
||||
}
|
||||
// 含 try/catch/finally 的函数跳过编码:异常边只从 TRY 标记所在 BB 发出,
|
||||
// 分支/循环 BB 内调用点的 liveness 不含仅 catch/finally 存活的寄存器,
|
||||
// 截断窗口会丢槽(unwind 恢复后 handler 读到 callee 残留)。整体回退全量窗口。
|
||||
let has_try = f
|
||||
.insts
|
||||
.iter()
|
||||
.any(|i| matches!(i.op, OpCode::TRY_BEGIN | OpCode::TRY_FINALLY_BEGIN));
|
||||
// LiveInfo 维度守卫:与 alloc 相同纪律,过期则重算。
|
||||
let live = if live.inst_live_before.len() == f.insts.len() && !f.insts.is_empty() {
|
||||
live.clone()
|
||||
@@ -28,8 +37,9 @@ pub fn encode_call_window(f: &mut IRFunction, live: &LiveInfo) {
|
||||
let cfg = oxide_cfg::build_cfg(f);
|
||||
oxide_liveness::liveness(f, &cfg)
|
||||
};
|
||||
// 生成器 / 异步函数体:调用点保持全量窗口(挂起恢复按全量寄存器快照搬移)。
|
||||
if !f.is_generator && !f.is_async {
|
||||
// 生成器 / 异步函数体 + 含 try 的函数:调用点保持全量窗口(挂起恢复按全量
|
||||
// 寄存器快照搬移;异常 handler 存活集经截断窗口会丢值)。
|
||||
if !has_try && !f.is_generator && !f.is_async {
|
||||
for (i, inst) in f.insts.iter_mut().enumerate() {
|
||||
if matches!(inst.op, OpCode::CALL | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL) {
|
||||
let after = &live.inst_live_after[i];
|
||||
@@ -140,4 +150,24 @@ mod tests {
|
||||
encode_call_window(&mut f, &LiveInfo::new());
|
||||
assert_eq!(f.insts[0].ext[0], 1 | (5 << 8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_skips_function_with_try() {
|
||||
// 含 TRY_BEGIN 的函数整体跳过编码(全量窗口):异常边只从 TRY 所在 BB 出发,
|
||||
// 分支/循环内调用点的 liveness 不含仅 handler 存活的寄存器,截断窗口会丢槽。
|
||||
let mut f = IRFunction::new();
|
||||
f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → L0
|
||||
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1));
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(4), Operand::Reg(4)));
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
|
||||
f.label_pos = vec![Some(2)];
|
||||
f.label_count = 1;
|
||||
let cfg = oxide_cfg::build_cfg(&f);
|
||||
let live = oxide_liveness::liveness(&f, &cfg);
|
||||
encode_call_window(&mut f, &live);
|
||||
// 存活集 {4} 本可编码上界 5,但含 try 保持全量(ext 高 8 位 = 0)
|
||||
assert_eq!(f.insts[1].ext[0], 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,3 +218,67 @@ fn call_window_upper_bound_encoded_into_bytecode() {
|
||||
module.n_registers
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_try_branch_call_preserves_catch_live_vars() {
|
||||
// try 体内分支中的调用点(CALL 与 TRY_BEGIN 不同 BB,异常边不反向传播):
|
||||
// catch 读 try 前的存活变量。截断窗口若不含这些槽,callee 抛错展开时
|
||||
// restore_frame 只回拷窗口,catch 读到 callee 残留垃圾值。
|
||||
// boom 的局部变量值(百位档)与 keep 期望值(1..10)刻意错开,覆盖必现错值。
|
||||
let src = "function boom(){ \
|
||||
var a1=100,a2=200,a3=300,a4=400,a5=500,a6=600,a7=700,a8=800,a9=900,a10=1000, \
|
||||
a11=1100,a12=1200,a13=1300,a14=1400,a15=1500,a16=1600,a17=1700,a18=1800,a19=1900,a20=2000, \
|
||||
a21=2100,a22=2200,a23=2300,a24=2400,a25=2500; \
|
||||
if (a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15+a16+a17+a18+a19+a20+a21+a22+a23+a24+a25) throw 'err'; } \
|
||||
function caller(x){ \
|
||||
var keep1=1,keep2=2,keep3=3,keep4=4,keep5=5,keep6=6,keep7=7,keep8=8,keep9=9,keep10=10; \
|
||||
try { if (x) { boom(); } } \
|
||||
catch(e) { return keep1+keep2+keep3+keep4+keep5+keep6+keep7+keep8+keep9+keep10; } \
|
||||
return -1; } \
|
||||
caller(true)";
|
||||
assert_eq!(eval(src), "55");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_try_branch_call_finally_preserves_live_vars() {
|
||||
// 回归锚点:finally 沿正常边进入,其活集经正常 CFG 传播到分支内调用点,
|
||||
// 窗口机制须保持该场景正确(finally 读 try 前变量不丢值)。
|
||||
let src = "function boom(){ \
|
||||
var a1=100,a2=200,a3=300,a4=400,a5=500,a6=600,a7=700,a8=800,a9=900,a10=1000, \
|
||||
a11=1100,a12=1200,a13=1300,a14=1400,a15=1500,a16=1600,a17=1700,a18=1800,a19=1900,a20=2000, \
|
||||
a21=2100,a22=2200,a23=2300,a24=2400,a25=2500; \
|
||||
return a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15+a16+a17+a18+a19+a20+a21+a22+a23+a24+a25; } \
|
||||
function caller(x){ \
|
||||
var keep = 99; \
|
||||
try { if (x) { boom(); } } \
|
||||
finally { keep = keep + 1; } \
|
||||
return keep; } \
|
||||
caller(true)";
|
||||
assert_eq!(eval(src), "100");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_nested_callee_param_overlap_arguments() {
|
||||
// 回归锚点:嵌套 callee 调用路径(含 arguments 实参区)在压帧拷贝顺序调整后
|
||||
// 值保持正确。真 bug(实参区与形参写入区重叠串值)由 vm 单元测试
|
||||
// push_bytecode_frame_param_overlap_reads_spill_first 直接构造验证。
|
||||
let src = "function outer(){ \
|
||||
var x = 5; \
|
||||
function inner(a, b){ return a*10 + b; } \
|
||||
var r = inner(x, 7); \
|
||||
return r; } \
|
||||
outer()";
|
||||
assert_eq!(eval(src), "57");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_nested_callee_param_overlap_with_arguments_object() {
|
||||
// 同回归锚点,加 arguments 对象:spill 实参区构建的 arguments 对象值正确。
|
||||
let src = "function outer(){ \
|
||||
var x = 5; \
|
||||
function inner(a, b){ return a*10 + b + arguments[1]; } \
|
||||
var r = inner(x, 7); \
|
||||
return r; } \
|
||||
outer()";
|
||||
assert_eq!(eval(src), "64");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user