diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index e819db5..c3bd8ed 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -300,7 +300,7 @@ define_opcodes! { def = None, uses = [], pure = false, jump = false, term = false, ic = false, - // ── 异常 (0x2E-0x2F, 0x33-0x35) ── + // ── 异常 (0x2E-0x2F, 0x33-0x35;TRY_FINALLY_ENTER 用 0x1B 空槽) ── THROW = 0x2E => "THROW", def = None, uses = [SlotSpec::Slot(Slot::Rd)], pure = false, jump = false, term = true, ic = false, @@ -313,6 +313,9 @@ define_opcodes! { TRY_FINALLY_BEGIN = 0x34 => "TRY_FINALLY_BEGIN", def = None, uses = [], 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", def = None, uses = [], pure = false, jump = false, term = false, ic = false, @@ -787,7 +790,7 @@ mod tests { assert_eq!(OpCode::UNSPILL.to_string(), "UNSPILL"); 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()); } diff --git a/crates/oxide_emit/src/stmt/exception/try_catch.rs b/crates/oxide_emit/src/stmt/exception/try_catch.rs index 96f4aa9..8cb4df3 100644 --- a/crates/oxide_emit/src/stmt/exception/try_catch.rs +++ b/crates/oxide_emit/src/stmt/exception/try_catch.rs @@ -80,6 +80,11 @@ impl Emitter { } if has_finally { 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 = None; // finally block 同为独立块作用域,lexical 声明互不泄漏。 ctx.push_scope(); diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index de91a39..6b35018 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -674,6 +674,7 @@ mod tests { OpCode::TRY_BEGIN, OpCode::TRY_END, OpCode::TRY_FINALLY_BEGIN, + OpCode::TRY_FINALLY_ENTER, OpCode::TRY_FINALLY_END, OpCode::FOR_IN_CLEANUP, OpCode::FOR_OF_CLOSE, diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 1df75fc..05a43a7 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -382,6 +382,11 @@ impl Inst { pub fn try_finally_begin(label: LabelId) -> Self { 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)`,后续按源码求值序排列 diff --git a/crates/oxide_vm/src/dispatch/control_flow.rs b/crates/oxide_vm/src/dispatch/control_flow.rs index cfbfd53..13942a1 100644 --- a/crates/oxide_vm/src/dispatch/control_flow.rs +++ b/crates/oxide_vm/src/dispatch/control_flow.rs @@ -9,13 +9,6 @@ impl Vm { pub(crate) fn dispatch_jmp(&mut self, instr: Instr) { let offset = opcode::offset16(instr) as isize; 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); self.pc = target; } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 8ef4bbf..22a62c8 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1811,6 +1811,10 @@ impl Vm { 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() { Ok(Some(result)) => return Ok(result), Ok(None) => {} diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 25a5078..8f4ba52 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -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 后统一恢复在途异常或控制流完成。 /// /// # 步骤 diff --git a/crates/oxide_vm/tests/try_finally_control_flow_tests.rs b/crates/oxide_vm/tests/try_finally_control_flow_tests.rs index 55d5143..4fb752a 100644 --- a/crates/oxide_vm/tests/try_finally_control_flow_tests.rs +++ b/crates/oxide_vm/tests/try_finally_control_flow_tests.rs @@ -393,3 +393,74 @@ fn inline_sort_comparator_throw_primitive_kept() { "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"); +}