fix: finally 体直落进入时置位 finally_active 防完成穿越计数错乱
catch 体正常结束后指令直落入 finally 体,此前 finally_active 未被置位, finally 内 break/continue/return/throw 触发未进入分支导致 finally 体重复 执行、完成计数错乱。新增无操作数标记指令 TRY_FINALLY_ENTER,在 finally 体入口统一置位栈顶 handler 的 finally_active,覆盖 JMP/直落/unwind/完成 穿越/挂起恢复全部进入路径,并删除 control_flow.rs 中对跳转目标匹配的 脆弱栈顶检测。
This commit is contained in:
@@ -300,7 +300,7 @@ define_opcodes! {
|
|||||||
def = None, uses = [],
|
def = None, uses = [],
|
||||||
pure = false, jump = false, term = false, ic = false,
|
pure = false, jump = false, term = false, ic = false,
|
||||||
|
|
||||||
// ── 异常 (0x2E-0x2F, 0x33-0x35) ──
|
// ── 异常 (0x2E-0x2F, 0x33-0x35;TRY_FINALLY_ENTER 用 0x1B 空槽) ──
|
||||||
THROW = 0x2E => "THROW",
|
THROW = 0x2E => "THROW",
|
||||||
def = None, uses = [SlotSpec::Slot(Slot::Rd)],
|
def = None, uses = [SlotSpec::Slot(Slot::Rd)],
|
||||||
pure = false, jump = false, term = true, ic = false,
|
pure = false, jump = false, term = true, ic = false,
|
||||||
@@ -313,6 +313,9 @@ define_opcodes! {
|
|||||||
TRY_FINALLY_BEGIN = 0x34 => "TRY_FINALLY_BEGIN",
|
TRY_FINALLY_BEGIN = 0x34 => "TRY_FINALLY_BEGIN",
|
||||||
def = None, uses = [],
|
def = None, uses = [],
|
||||||
pure = false, jump = true, term = false, ic = false,
|
pure = false, jump = true, term = false, ic = false,
|
||||||
|
TRY_FINALLY_ENTER = 0x1B => "TRY_FINALLY_ENTER",
|
||||||
|
def = None, uses = [],
|
||||||
|
pure = false, jump = false, term = false, ic = false,
|
||||||
TRY_FINALLY_END = 0x35 => "TRY_FINALLY_END",
|
TRY_FINALLY_END = 0x35 => "TRY_FINALLY_END",
|
||||||
def = None, uses = [],
|
def = None, uses = [],
|
||||||
pure = false, jump = false, term = false, ic = false,
|
pure = false, jump = false, term = false, ic = false,
|
||||||
@@ -787,7 +790,7 @@ mod tests {
|
|||||||
assert_eq!(OpCode::UNSPILL.to_string(), "UNSPILL");
|
assert_eq!(OpCode::UNSPILL.to_string(), "UNSPILL");
|
||||||
assert_eq!(OpCode::SPREAD_OBJECT.to_string(), "SPREAD_OBJECT");
|
assert_eq!(OpCode::SPREAD_OBJECT.to_string(), "SPREAD_OBJECT");
|
||||||
|
|
||||||
assert!(OpCode::try_from(0x1B).is_err());
|
assert!(OpCode::try_from(0xFA).is_err());
|
||||||
assert!(OpCode::try_from(0xFF).is_err());
|
assert!(OpCode::try_from(0xFF).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ impl Emitter {
|
|||||||
}
|
}
|
||||||
if has_finally {
|
if has_finally {
|
||||||
ctx.labels.set_label_pos(finally_label, ctx.insts.len());
|
ctx.labels.set_label_pos(finally_label, ctx.insts.len());
|
||||||
|
// finally 体入口标记:运行时置位栈顶 handler 的 finally_active。所有进入
|
||||||
|
// 路径(try/catch 体 JMP、catch 体直落、unwind、完成穿越、挂起恢复)都
|
||||||
|
// 落到 finally_label 起始的这条标记,统一完成置位;catch 体直落路径此前
|
||||||
|
// 无任何置位,finally 内 break/continue/return/throw 会重入 finally 体。
|
||||||
|
ctx.inst(Inst::try_finally_enter());
|
||||||
let mut last_finally_result: Option<u32> = None;
|
let mut last_finally_result: Option<u32> = None;
|
||||||
// finally block 同为独立块作用域,lexical 声明互不泄漏。
|
// finally block 同为独立块作用域,lexical 声明互不泄漏。
|
||||||
ctx.push_scope();
|
ctx.push_scope();
|
||||||
|
|||||||
@@ -674,6 +674,7 @@ mod tests {
|
|||||||
OpCode::TRY_BEGIN,
|
OpCode::TRY_BEGIN,
|
||||||
OpCode::TRY_END,
|
OpCode::TRY_END,
|
||||||
OpCode::TRY_FINALLY_BEGIN,
|
OpCode::TRY_FINALLY_BEGIN,
|
||||||
|
OpCode::TRY_FINALLY_ENTER,
|
||||||
OpCode::TRY_FINALLY_END,
|
OpCode::TRY_FINALLY_END,
|
||||||
OpCode::FOR_IN_CLEANUP,
|
OpCode::FOR_IN_CLEANUP,
|
||||||
OpCode::FOR_OF_CLOSE,
|
OpCode::FOR_OF_CLOSE,
|
||||||
|
|||||||
@@ -382,6 +382,11 @@ impl Inst {
|
|||||||
pub fn try_finally_begin(label: LabelId) -> Self {
|
pub fn try_finally_begin(label: LabelId) -> Self {
|
||||||
Self::new(OpCode::TRY_FINALLY_BEGIN, Operand::None, Operand::None, Operand::Label(label))
|
Self::new(OpCode::TRY_FINALLY_BEGIN, Operand::None, Operand::None, Operand::Label(label))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// finally 体入口标记:运行时置位对应 try handler 的 `finally_active`。
|
||||||
|
pub fn try_finally_enter() -> Self {
|
||||||
|
Self::new(OpCode::TRY_FINALLY_ENTER, Operand::None, Operand::None, Operand::None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// spread 调用系 ext 构造:首字打包 `nstatic | (nspread << 8)`,后续按源码求值序排列
|
/// spread 调用系 ext 构造:首字打包 `nstatic | (nspread << 8)`,后续按源码求值序排列
|
||||||
|
|||||||
@@ -9,13 +9,6 @@ impl Vm {
|
|||||||
pub(crate) fn dispatch_jmp(&mut self, instr: Instr) {
|
pub(crate) fn dispatch_jmp(&mut self, instr: Instr) {
|
||||||
let offset = opcode::offset16(instr) as isize;
|
let offset = opcode::offset16(instr) as isize;
|
||||||
let target = ((self.pc as isize) + offset - 1) as usize;
|
let target = ((self.pc as isize) + offset - 1) as usize;
|
||||||
// try 体正常完成跳入 finally(`JMP finally_pc`):标记 handler 进入 finally 体,
|
|
||||||
// 供 break/continue/return/异常判定覆盖语义。
|
|
||||||
if let Some(h) = self.try_stack.last_mut() {
|
|
||||||
if h.frame_depth == self.frames.len() && h.finally_pc == Some(target) {
|
|
||||||
h.finally_active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vm_trace!("JMP offset={}", offset);
|
vm_trace!("JMP offset={}", offset);
|
||||||
self.pc = target;
|
self.pc = target;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1811,6 +1811,10 @@ impl Vm {
|
|||||||
self.dispatch_try_finally_begin(instr);
|
self.dispatch_try_finally_begin(instr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpCode::TRY_FINALLY_ENTER => {
|
||||||
|
self.dispatch_try_finally_enter();
|
||||||
|
}
|
||||||
|
|
||||||
OpCode::TRY_FINALLY_END => match self.dispatch_try_finally_end() {
|
OpCode::TRY_FINALLY_END => match self.dispatch_try_finally_end() {
|
||||||
Ok(Some(result)) => return Ok(result),
|
Ok(Some(result)) => return Ok(result),
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
|
|||||||
@@ -495,6 +495,20 @@ impl Vm {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// finally 体入口标记:置位栈顶 try handler 的 `finally_active`。
|
||||||
|
///
|
||||||
|
/// # 注意事项
|
||||||
|
/// - 所有进入 finally 体的路径(try/catch 体 JMP、catch 体直落、异常 unwind、
|
||||||
|
/// 完成穿越、生成器/async 恢复)都先执行本指令,统一完成置位,消除对跳转
|
||||||
|
/// 目标匹配的依赖。栈顶在标记执行时必然是本 finally 的 handler。
|
||||||
|
/// - 幂等:finally 体已置位(unwind/完成穿越路径先行置位)时重复置 true 无害。
|
||||||
|
pub(crate) fn dispatch_try_finally_enter(&mut self) {
|
||||||
|
vm_trace!("TRY_FINALLY_ENTER");
|
||||||
|
if let Some(h) = self.try_stack.last_mut() {
|
||||||
|
h.finally_active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// finally 完成分发点:弹出当前 handler 后统一恢复在途异常或控制流完成。
|
/// finally 完成分发点:弹出当前 handler 后统一恢复在途异常或控制流完成。
|
||||||
///
|
///
|
||||||
/// # 步骤
|
/// # 步骤
|
||||||
|
|||||||
@@ -393,3 +393,74 @@ fn inline_sort_comparator_throw_primitive_kept() {
|
|||||||
"13"
|
"13"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── finally 体直落进入回归(catch 体正常结束)──
|
||||||
|
// catch 体结束后指令直落入 finally 体(无 JMP),此前 finally_active 未被置位,
|
||||||
|
// finally 内 break/continue/return/throw 会触发"未进入"分支重入 finally 体、
|
||||||
|
// 副作用翻倍。专用入口标记统一置位后,以下断言得期望值(修复前为 buggy 值)。
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallthrough_finally_return_runs_once() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var fin=0; function f(){ try { throw 1; } catch(e){} finally { fin=fin+1; return 2; } } var r=f(); r*10+fin"),
|
||||||
|
"21"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallthrough_finally_break_runs_once() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var fin=0; for(var i=0;i<2;i++){ try { throw 1; } catch(e){} finally { fin=fin+1; break; } } fin"),
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallthrough_finally_continue_runs_once_per_iteration() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var fin=0; for(var i=0;i<5;i++){ try { throw 1; } catch(e){} finally { fin=fin+1; continue; } } fin"),
|
||||||
|
"5"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallthrough_finally_return_keeps_value() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var fin=0; function f(){ try { throw 1; } catch(e){} finally { fin++; return 7; } } var r=f(); r*10+fin"),
|
||||||
|
"71"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallthrough_finally_break_crosses_two_layers_once_each() {
|
||||||
|
// 内层 finally 直落进入后 break outer 穿越两层,两层各执行一次不重复。
|
||||||
|
assert_eq!(
|
||||||
|
eval("var log=0; outer: for(var i=0;i<2;i++){ try { try { throw 1; } catch(e){} finally { log=log*10+1; break outer; } } finally { log=log*10+2; } } log"),
|
||||||
|
"12"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallthrough_finally_throw_does_not_reenter_finally() {
|
||||||
|
// finally 内 throw:置位保证 unwind 判定"新异常在 finally 体内"不重入,
|
||||||
|
// 异常交外层 catch,finally 只执行一次。
|
||||||
|
assert_eq!(
|
||||||
|
eval("var fin=0; function f(){ try { throw 1; } catch(e){} finally { fin=fin+1; throw 9; } } try { f(); } catch(e){ fin=fin*10+e; } fin"),
|
||||||
|
"19"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallthrough_finally_nested_finally_return_runs_once_each() {
|
||||||
|
// finally 内嵌套 finally + return:两层各执行一次,return 值不受影响。
|
||||||
|
assert_eq!(
|
||||||
|
eval("var fin=0; function f(){ try { throw 1; } catch(e){} finally { try { } finally { fin++; } return 7; } } var r=f(); r*10+fin"),
|
||||||
|
"71"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallthrough_finally_normal_completion_guard() {
|
||||||
|
// 直落本身非缺陷:无完成语义时 finally 只执行一次(守卫用例)。
|
||||||
|
assert_eq!(eval("var fin=0; try { throw 1; } catch(e){} finally { fin=fin+1; } fin"), "1");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user