fix: try 内 return 泄漏 TryHandler,异常跳回死函数 catch 死循环
This commit is contained in:
@@ -178,6 +178,10 @@ pub struct CompileCtx {
|
||||
/// with 语句作用域栈:元素为 (with 对象寄存器, 打开时的作用域深度)。
|
||||
/// 非空时 with 体内的自由标识符需动态解析(先查对象属性,回退外层)。
|
||||
pub(crate) with_stack: Vec<(u32, usize)>,
|
||||
/// 打开中(尚未 emit 对应 END)的 try handler 栈,自底向上镜像运行时
|
||||
/// try_stack 组成。每项标记是否为纯 catch handler(TRY_BEGIN,由 TRY_END
|
||||
/// 弹出):return 逃出 try 域时据此弹出栈顶连续纯 catch,防 handler 泄漏。
|
||||
pub(crate) open_try_handlers: Vec<bool>,
|
||||
}
|
||||
|
||||
/// 函数体编译上下文:决定 `this`/`super` 绑定与参数前导(prologue)形态。
|
||||
@@ -259,6 +263,7 @@ impl CompileCtx {
|
||||
captured_bindings: BTreeMap::new(),
|
||||
const_overflow: false,
|
||||
with_stack: Vec::new(),
|
||||
open_try_handlers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,6 +421,29 @@ impl CompileCtx {
|
||||
self.labels.finally_depth -= 1;
|
||||
}
|
||||
|
||||
/// 记录一个纯 catch handler 打开(对应 emit try_begin 后的运行时 TRY_BEGIN)。
|
||||
pub(crate) fn push_open_catch_handler(&mut self) {
|
||||
self.open_try_handlers.push(true);
|
||||
}
|
||||
|
||||
/// 记录一个 finally handler 打开(对应 emit try_finally_begin 后的运行时
|
||||
/// TRY_FINALLY_BEGIN)。
|
||||
pub(crate) fn push_open_finally_handler(&mut self) {
|
||||
self.open_try_handlers.push(false);
|
||||
}
|
||||
|
||||
/// 弹出最近打开的 handler(对应 emit 的 TRY_END / TRY_FINALLY_END)。
|
||||
pub(crate) fn pop_open_try_handler(&mut self) {
|
||||
self.open_try_handlers.pop();
|
||||
}
|
||||
|
||||
/// 栈顶连续打开的纯 catch handler 数(遇 finally handler 即停)。
|
||||
/// return 逃出本函数时,这些 handler 可由 TRY_END 直接从栈顶弹出;
|
||||
/// finally 之下的 catch 无法经 TRY_END 弹出,交给运行时统一清理。
|
||||
pub(crate) fn top_open_catch_handlers(&self) -> usize {
|
||||
self.open_try_handlers.iter().rev().take_while(|&&is_catch| is_catch).count()
|
||||
}
|
||||
|
||||
pub(crate) fn push_label_scope(
|
||||
&mut self, name: &str, break_label: LabelId, continue_label: Option<LabelId>,
|
||||
) -> Result<(), String> {
|
||||
|
||||
@@ -14,15 +14,34 @@ impl Emitter {
|
||||
match &ret.argument {
|
||||
Some(expr) => {
|
||||
let r = self.emit_expression(expr, ctx)?;
|
||||
// return 逃出本函数全部 try 域:值求值后、发 RETURN 前,先从栈顶
|
||||
// 弹出连续纯 catch handler(finally handler 走运行时完成穿越逐个
|
||||
// 执行)。否则 return 跳过 TRY_END 会让 handler 残留在 try_stack,
|
||||
// 后续异常 unwind 会跳回已返回函数的 catch 形成死循环。
|
||||
self.emit_return_try_cleanup(ctx);
|
||||
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(r), Operand::None, Operand::None));
|
||||
}
|
||||
None => {
|
||||
self.emit_return_try_cleanup(ctx);
|
||||
ctx.inst(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// 在 RETURN 前弹出栈顶连续打开的纯 catch handler。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 只处理位于栈顶的连续纯 catch handler;finally handler 及其下方的 catch
|
||||
/// handler 不由 TRY_END 弹出(LIFO 无法跨 finally 弹出),由运行时
|
||||
/// dispatch_return 扫描兜底清理。
|
||||
fn emit_return_try_cleanup(&self, ctx: &mut CompileCtx) {
|
||||
let open_catches = ctx.top_open_catch_handlers();
|
||||
for _ in 0..open_catches {
|
||||
ctx.inst(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn emit_basic_return(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
|
||||
self.emit_return_statement(stmt, ctx)
|
||||
}
|
||||
|
||||
@@ -22,9 +22,11 @@ impl Emitter {
|
||||
// 逃出语句时须穿越本 finally,词法上记录域深度供跨越计数。
|
||||
ctx.push_finally_domain();
|
||||
ctx.inst(Inst::try_finally_begin(finally_label));
|
||||
ctx.push_open_finally_handler();
|
||||
}
|
||||
if has_catch {
|
||||
ctx.inst(Inst::try_begin(catch_label));
|
||||
ctx.push_open_catch_handler();
|
||||
}
|
||||
let mut last_try_result: Option<u32> = None;
|
||||
for s in &ts.block.body {
|
||||
@@ -40,6 +42,7 @@ impl Emitter {
|
||||
));
|
||||
if has_catch {
|
||||
ctx.inst(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None));
|
||||
ctx.pop_open_try_handler();
|
||||
}
|
||||
let jmp_needed = has_catch || has_finally;
|
||||
if jmp_needed {
|
||||
@@ -84,6 +87,7 @@ impl Emitter {
|
||||
Operand::None,
|
||||
));
|
||||
ctx.inst(Inst::new(OpCode::TRY_FINALLY_END, Operand::None, Operand::None, Operand::None));
|
||||
ctx.pop_open_try_handler();
|
||||
ctx.pop_finally_domain();
|
||||
}
|
||||
ctx.labels.set_label_pos(try_end_label, ctx.insts.len());
|
||||
|
||||
Reference in New Issue
Block a user