mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: try/finally 支持 break/continue/return 穿越 finally(completion 机制)
This commit is contained in:
@@ -8,8 +8,16 @@ impl Vm {
|
||||
#[inline(always)]
|
||||
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 = ((self.pc as isize) + offset - 1) as usize;
|
||||
self.pc = target;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
@@ -139,11 +139,55 @@ pub struct ForInIter<'bump> {
|
||||
pub struct TryHandler {
|
||||
pub catch_pc: Option<usize>,
|
||||
pub finally_pc: Option<usize>,
|
||||
/// finally 体是否已进入(normal JMP / 异常展开 / 完成穿越都会置位)。
|
||||
/// 用标志而非 pc 范围判定"当前是否执行 finally 体":try 体末指令可能因 DCE
|
||||
/// 紧贴 finally 入口,pc 边界会误判。
|
||||
pub finally_active: bool,
|
||||
pub frame_depth: usize,
|
||||
/// try 入口时 for_of_iters 的长度,界定异常展开时哪些迭代器需要 IteratorClose。
|
||||
pub for_of_depth: usize,
|
||||
}
|
||||
|
||||
/// 控制流完成:break/continue/return 逃出 finally 域时暂存的完成目标。
|
||||
///
|
||||
/// 仿 `pending_exception` 的侧通道:finally 执行期间悬挂在此,由 TRY_FINALLY_END
|
||||
/// 逐个恢复(`remaining_finally` 为仍需穿越的 finally 体数,进入一个递减一个)。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Completion {
|
||||
Break {
|
||||
target_pc: usize,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
Continue {
|
||||
target_pc: usize,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
Return {
|
||||
value: JsValue,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Completion {
|
||||
/// 仍需穿越的 finally 体数。
|
||||
pub fn remaining_finally(&self) -> usize {
|
||||
match *self {
|
||||
Completion::Break { remaining_finally, .. }
|
||||
| Completion::Continue { remaining_finally, .. }
|
||||
| Completion::Return { remaining_finally, .. } => remaining_finally,
|
||||
}
|
||||
}
|
||||
|
||||
/// 复制并改写剩余 finally 计数(进入一个 finally 后递减)。
|
||||
pub fn with_remaining(&self, remaining: usize) -> Completion {
|
||||
match *self {
|
||||
Completion::Break { target_pc, .. } => Completion::Break { target_pc, remaining_finally: remaining },
|
||||
Completion::Continue { target_pc, .. } => Completion::Continue { target_pc, remaining_finally: remaining },
|
||||
Completion::Return { value, .. } => Completion::Return { value, remaining_finally: remaining },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `call_bytecode_function_inline` 使用的堆分配快照。
|
||||
/// 放在堆上避免 JS 代码链式同步字节码调用(如 sort 比较器、accessor)时
|
||||
/// 耗尽 Rust 栈。
|
||||
@@ -159,6 +203,7 @@ pub(crate) struct InlineSyncState {
|
||||
pub(crate) exception_value: Option<JsValue>,
|
||||
pub(crate) pending_exception: Option<JsValue>,
|
||||
pub(crate) pending_error_kind: Option<&'static str>,
|
||||
pub(crate) pending_completion: Option<Completion>,
|
||||
pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>,
|
||||
pub(crate) for_of_iters: Vec<JsValue>,
|
||||
pub(crate) last_for_of_result: JsValue,
|
||||
@@ -214,6 +259,8 @@ pub struct Vm {
|
||||
pub(crate) last_uncaught_value: Option<JsValue>,
|
||||
pub(crate) pending_exception: Option<JsValue>,
|
||||
pub(crate) pending_error_kind: Option<&'static str>,
|
||||
/// 控制流完成(break/continue/return)暂存,finally 执行后由 TRY_FINALLY_END 恢复。
|
||||
pub(crate) pending_completion: Option<Completion>,
|
||||
pub(crate) root_reg_limit: u8,
|
||||
pub(crate) active_reg_limit: u8,
|
||||
pub(crate) native_call_depth: usize,
|
||||
@@ -424,6 +471,12 @@ impl Vm {
|
||||
f(JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject));
|
||||
f(self.exception_value.unwrap_or(JsValue::undefined()));
|
||||
f(self.pending_exception.unwrap_or(JsValue::undefined()));
|
||||
// 悬挂的 return 完成持有返回值,是 GC 根。
|
||||
if let Some(Completion::Return { value, .. }) = self.pending_completion {
|
||||
if value.is_object() || value.is_string() {
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
for &v in &self.iters.for_of_iters {
|
||||
f(v);
|
||||
}
|
||||
@@ -1063,6 +1116,14 @@ impl Vm {
|
||||
self.dispatch_jmp(instr);
|
||||
}
|
||||
|
||||
OpCode::BREAK => {
|
||||
self.dispatch_break(instr);
|
||||
}
|
||||
|
||||
OpCode::CONTINUE => {
|
||||
self.dispatch_continue(instr);
|
||||
}
|
||||
|
||||
OpCode::JMP_IF_FALSE => {
|
||||
self.dispatch_jmp_if_false(rd, instr);
|
||||
}
|
||||
@@ -1374,8 +1435,8 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::TRY_FINALLY_END => match self.dispatch_try_finally_end() {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
Ok(Some(result)) => return Ok(result),
|
||||
Ok(None) => {}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
@@ -1706,6 +1767,7 @@ mod tests {
|
||||
vm.try_stack.push(TryHandler {
|
||||
catch_pc: Some(1),
|
||||
finally_pc: None,
|
||||
finally_active: false,
|
||||
frame_depth: 0,
|
||||
for_of_depth: 0,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::vm::{FrameContinuation, TryHandler, Vm};
|
||||
use crate::vm::{Completion, FrameContinuation, TryHandler, Vm};
|
||||
use crate::vm_trace;
|
||||
use oxide_bytecode::opcode;
|
||||
use oxide_types::object::PropAttributes;
|
||||
@@ -124,6 +124,83 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// break 完成:`crossed`(rd 槽)为 emit 词法算出的逃出 finally 域数。
|
||||
/// 逐个穿越 finally 后跳转到目标;crossed 为 0 时直接跳转。
|
||||
pub(crate) fn dispatch_break(&mut self, instr: u32) {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
let crossed = opcode::rd(instr) as usize;
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Break { target_pc, remaining_finally: crossed }) {
|
||||
self.pc = finally_pc;
|
||||
} else {
|
||||
self.pc = target_pc;
|
||||
}
|
||||
}
|
||||
|
||||
/// continue 完成:同 break,目标为循环继续位置。
|
||||
pub(crate) fn dispatch_continue(&mut self, instr: u32) {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
let crossed = opcode::rd(instr) as usize;
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Continue { target_pc, remaining_finally: crossed }) {
|
||||
self.pc = finally_pc;
|
||||
} else {
|
||||
self.pc = target_pc;
|
||||
}
|
||||
}
|
||||
|
||||
/// 记录一次控制流完成:穿越 `remaining_finally` 个 finally 体后执行完成本身。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 新完成覆盖在途异常/完成(break/continue/return 是新的突然完成)。
|
||||
/// 2. 从栈顶向下扫描:逃出的 catch-only handler 弹出;正在执行的 finally 体
|
||||
/// (`finally_active`)被覆盖弹出并计数;未进入的包裹 finally 计数后进入。
|
||||
/// 3. 计数耗尽(全部跨越的 finally 已进入或已覆盖)→ 返回 None(快速路径)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 只扫描当前帧深度(`frames.len()`)的 handler;调用者的 handler 不参与——
|
||||
/// break/continue 词法不跨函数,return 也只逃出本函数 finally。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 清空 `pending_exception`/`pending_completion`,弹出被逃出的 handler。
|
||||
fn record_completion(&mut self, c: Completion) -> Option<usize> {
|
||||
self.pending_exception = None;
|
||||
self.pending_error_kind = None;
|
||||
self.pending_completion = None;
|
||||
let depth = self.frames.len();
|
||||
let mut remaining = c.remaining_finally();
|
||||
loop {
|
||||
if remaining == 0 {
|
||||
return None;
|
||||
}
|
||||
let (frame_depth, finally_pc, finally_active) = {
|
||||
let Some(h) = self.try_stack.last() else {
|
||||
return None;
|
||||
};
|
||||
(h.frame_depth, h.finally_pc, h.finally_active)
|
||||
};
|
||||
if frame_depth != depth {
|
||||
return None;
|
||||
}
|
||||
let Some(fp) = finally_pc else {
|
||||
// 逃出的 catch-only handler:丢弃防泄漏。
|
||||
self.try_stack.pop();
|
||||
continue;
|
||||
};
|
||||
remaining -= 1;
|
||||
if finally_active {
|
||||
// 正在执行本 finally 体:本完成覆盖它,弹出。
|
||||
self.try_stack.pop();
|
||||
continue;
|
||||
}
|
||||
self.try_stack.last_mut().unwrap().finally_active = true;
|
||||
self.pending_completion = Some(c.with_remaining(remaining));
|
||||
return Some(fp);
|
||||
}
|
||||
}
|
||||
|
||||
/// return 完成:`crossed` = 当前帧深度内全部 finally handler 数(return 逃出整个
|
||||
/// 函数,途中被覆盖的 finally 体也在内),穿越后实际返回。
|
||||
pub(crate) fn dispatch_return(&mut self, rd: usize) -> Result<Option<JsValue>, String> {
|
||||
let result = self.regs[rd];
|
||||
crate::vm_debug!(
|
||||
@@ -131,6 +208,23 @@ impl Vm {
|
||||
self.frames.len(),
|
||||
self.frames.last().map(|f| f.return_addr).unwrap_or(0)
|
||||
);
|
||||
let crossed = self
|
||||
.try_stack
|
||||
.iter()
|
||||
.filter(|h| h.frame_depth == self.frames.len() && h.finally_pc.is_some())
|
||||
.count();
|
||||
if let Some(finally_pc) =
|
||||
self.record_completion(Completion::Return { value: result, remaining_finally: crossed })
|
||||
{
|
||||
self.pc = finally_pc;
|
||||
return Ok(None);
|
||||
}
|
||||
self.do_return(result)
|
||||
}
|
||||
|
||||
/// 实际执行返回:弹出当前帧并交付返回值(供 dispatch_return 与 finally 完成
|
||||
/// 恢复共用)。
|
||||
fn do_return(&mut self, result: JsValue) -> Result<Option<JsValue>, String> {
|
||||
if let Some(frame) = self.frames.pop() {
|
||||
self.cell_stack.pop();
|
||||
let construct_result_reg = frame.construct_result_reg;
|
||||
@@ -139,10 +233,9 @@ impl Vm {
|
||||
let continuation = frame.continuation;
|
||||
let callee_this = self.regs[254];
|
||||
vm_trace!(
|
||||
"RETURN frame: continuation={:?}, derived={}, result_reg={}",
|
||||
"RETURN frame: continuation={:?}, derived={}",
|
||||
continuation,
|
||||
is_derived_constructor,
|
||||
rd
|
||||
is_derived_constructor
|
||||
);
|
||||
self.restore_frame(frame);
|
||||
if let (Some(target_reg), Some(constructed_this)) = (construct_result_reg, constructed_this) {
|
||||
@@ -192,6 +285,7 @@ impl Vm {
|
||||
self.try_stack.push(TryHandler {
|
||||
catch_pc,
|
||||
finally_pc: None,
|
||||
finally_active: false,
|
||||
frame_depth: self.frames.len(),
|
||||
for_of_depth: self.iters.for_of_iters.len(),
|
||||
});
|
||||
@@ -209,20 +303,75 @@ impl Vm {
|
||||
self.try_stack.push(TryHandler {
|
||||
catch_pc: None,
|
||||
finally_pc: Some(finally_pc),
|
||||
finally_active: false,
|
||||
frame_depth: self.frames.len(),
|
||||
for_of_depth: self.iters.for_of_iters.len(),
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_try_finally_end(&mut self) -> Result<bool, String> {
|
||||
/// finally 完成分发点:弹出当前 handler 后统一恢复在途异常或控制流完成。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 弹出刚执行完的 finally handler。
|
||||
/// 2. 优先恢复在途异常(unwind 继续向外展开)。
|
||||
/// 3. 否则查 `pending_completion`:仍有剩余 finally 体则进入下一个(递减计数);
|
||||
/// 耗尽则执行完成本身(break/continue 跳转目标,return 交付返回值)。
|
||||
///
|
||||
/// # 返回值
|
||||
/// `Ok(Some(value))` = 函数返回;`Ok(None)` = 正常继续 dispatch。
|
||||
pub(crate) fn dispatch_try_finally_end(&mut self) -> Result<Option<JsValue>, String> {
|
||||
vm_trace!("TRY_FINALLY_END has_pending_exc={}", self.pending_exception.is_some());
|
||||
self.try_stack.pop();
|
||||
if self.pending_exception.is_some() && self.exception_value.is_none() {
|
||||
self.exception_value = self.pending_exception.take();
|
||||
self.unwind().map(|_| true)
|
||||
} else {
|
||||
self.pending_exception = None;
|
||||
Ok(false)
|
||||
self.unwind()?;
|
||||
return Ok(None);
|
||||
}
|
||||
self.pending_exception = None;
|
||||
let Some(c) = self.pending_completion.take() else {
|
||||
return Ok(None);
|
||||
};
|
||||
if c.remaining_finally() > 0 {
|
||||
let next = c.with_remaining(c.remaining_finally() - 1);
|
||||
if let Some(finally_pc) = self.find_next_finally() {
|
||||
self.pending_completion = Some(next);
|
||||
self.pc = finally_pc;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
match c {
|
||||
Completion::Break { target_pc, .. } | Completion::Continue { target_pc, .. } => {
|
||||
self.pc = target_pc;
|
||||
Ok(None)
|
||||
}
|
||||
Completion::Return { value, .. } => self.do_return(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// 找下一个未被覆盖的包裹 finally 并进入(resume 路径用):弹出途中被逃出的
|
||||
/// catch-only 与防御性的 active handler。无更多返回 None。
|
||||
fn find_next_finally(&mut self) -> Option<usize> {
|
||||
let depth = self.frames.len();
|
||||
loop {
|
||||
let (frame_depth, finally_pc, finally_active) = {
|
||||
let Some(h) = self.try_stack.last() else {
|
||||
return None;
|
||||
};
|
||||
(h.frame_depth, h.finally_pc, h.finally_active)
|
||||
};
|
||||
if frame_depth != depth {
|
||||
return None;
|
||||
}
|
||||
let Some(fp) = finally_pc else {
|
||||
self.try_stack.pop();
|
||||
continue;
|
||||
};
|
||||
if finally_active {
|
||||
self.try_stack.pop();
|
||||
continue;
|
||||
}
|
||||
self.try_stack.last_mut().unwrap().finally_active = true;
|
||||
return Some(fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ impl Vm {
|
||||
exception_value: self.exception_value.take(),
|
||||
pending_exception: self.pending_exception.take(),
|
||||
pending_error_kind: self.pending_error_kind.take(),
|
||||
pending_completion: self.pending_completion,
|
||||
for_in_iters: std::mem::take(&mut self.iters.for_in_iters),
|
||||
for_of_iters: std::mem::take(&mut self.iters.for_of_iters),
|
||||
last_for_of_result: self.iters.last_for_of_result,
|
||||
@@ -133,6 +134,7 @@ impl Vm {
|
||||
self.exception_value = saved.exception_value;
|
||||
self.pending_exception = saved.pending_exception;
|
||||
self.pending_error_kind = saved.pending_error_kind;
|
||||
self.pending_completion = saved.pending_completion;
|
||||
self.iters.for_in_iters = saved.for_in_iters;
|
||||
self.iters.for_of_iters = saved.for_of_iters;
|
||||
self.iters.last_for_of_result = saved.last_for_of_result;
|
||||
@@ -222,7 +224,7 @@ impl Vm {
|
||||
|
||||
pub(crate) fn unwind(&mut self) -> Result<(), String> {
|
||||
vm_debug!("unwind: {} try handlers on stack", self.try_stack.len());
|
||||
while let Some(handler) = self.try_stack.pop() {
|
||||
while let Some(mut handler) = self.try_stack.pop() {
|
||||
while self.frames.len() > handler.frame_depth {
|
||||
if let Some(frame) = self.frames.pop() {
|
||||
self.cell_stack.pop();
|
||||
@@ -232,10 +234,17 @@ impl Vm {
|
||||
// 对该处理器作用域内被中断的 for-of 循环执行 IteratorClose。
|
||||
self.close_for_of_above(handler.for_of_depth);
|
||||
if let Some(finally_pc) = handler.finally_pc {
|
||||
vm_trace!("unwind: entering finally at pc={}", finally_pc);
|
||||
if self.pending_exception.is_none() {
|
||||
self.pending_exception = self.exception_value.take();
|
||||
if handler.finally_active {
|
||||
// 新异常在 finally 体内抛出:覆盖在途异常与完成,继续向外展开,
|
||||
// 不再重入本 finally(否则已执行的 finally 会重复运行)。
|
||||
self.pending_exception = None;
|
||||
self.pending_error_kind = None;
|
||||
self.pending_completion = None;
|
||||
continue;
|
||||
}
|
||||
vm_trace!("unwind: entering finally at pc={}", finally_pc);
|
||||
self.pending_exception = Some(self.exception_value.take().unwrap_or(JsValue::undefined()));
|
||||
handler.finally_active = true;
|
||||
self.try_stack.push(handler);
|
||||
self.pc = finally_pc;
|
||||
return Ok(());
|
||||
|
||||
@@ -43,6 +43,7 @@ impl Vm {
|
||||
last_uncaught_value: None,
|
||||
pending_exception: None,
|
||||
pending_error_kind: None,
|
||||
pending_completion: None,
|
||||
root_reg_limit: 0,
|
||||
active_reg_limit: 0,
|
||||
native_call_depth: 0,
|
||||
@@ -108,6 +109,7 @@ impl Vm {
|
||||
last_uncaught_value: None,
|
||||
pending_exception: None,
|
||||
pending_error_kind: None,
|
||||
pending_completion: None,
|
||||
root_reg_limit: 0,
|
||||
active_reg_limit: 0,
|
||||
native_call_depth: 0,
|
||||
@@ -223,6 +225,7 @@ impl Vm {
|
||||
self.exception_value = None;
|
||||
self.pending_exception = None;
|
||||
self.pending_error_kind = None;
|
||||
self.pending_completion = None;
|
||||
self.native_call_depth = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
//! try/finally + break/continue/return 控制流冒烟:finally 必须按语义执行。
|
||||
//!
|
||||
//! 覆盖:break/continue/return 穿越 finally、finally 内控制流覆盖、
|
||||
//! 异常路径不回归、嵌套 try/finally、for-of 迭代器关闭、普通循环零开销路径。
|
||||
//! 字符串值在此引擎 Display 为 `{string}`,断言一律用数值编码。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {e}"),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&module) {
|
||||
Ok(result) => format!("{result}"),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_runs_finally_and_exits_loop() {
|
||||
assert_eq!(eval("var fin = 0; var i = 0; for (; i < 5; ) { try { break; } finally { fin = 1; } i = i + 1; } fin * 10 + i"), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_runs_finally_every_iteration() {
|
||||
assert_eq!(eval("var fin = 0; for (var i = 0; i < 5; i++) { try { continue; } finally { fin = fin + 1; } } fin"), "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_runs_finally() {
|
||||
assert_eq!(eval("var fin = 0; function f() { try { return 1; } finally { fin = 1; } } var r = f(); r * 10 + fin"), "11");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_inside_finally_breaks_loop() {
|
||||
assert_eq!(eval("var fin = 0; var n = 0; for (var i = 0; i < 5; i++) { try { } finally { fin = fin + 1; break; } n = n + 1; } fin * 10 + n"), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throw_still_runs_finally() {
|
||||
assert_eq!(eval("var fin = 0; function g() { try { throw 1; } finally { fin = 1; } } try { g(); } catch (e) {} fin"), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_try_finally_both_run_on_break() {
|
||||
assert_eq!(eval("var log = 0; for (var i = 0; i < 3; i++) { try { try { break; } finally { log = log * 10 + 1; } } finally { log = log * 10 + 2; } } log"), "12");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_break_runs_finally() {
|
||||
assert_eq!(eval("var fin = 0; var it = 0; for (var x of [1,2,3]) { try { break; } finally { fin = 1; } it = it + 1; } fin * 10 + it"), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_loop_without_try_unchanged() {
|
||||
assert_eq!(eval("var s = 0; for (var i = 0; i < 5; i++) { s = s + i; } s"), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_catch_break_does_not_leak_handler() {
|
||||
assert_eq!(
|
||||
eval("var caught = 0; for (var i = 0; i < 1; i++) { try { break; } catch (e) { caught = 1; } } try { throw 1; } catch (e) { caught = 2; } caught"),
|
||||
"2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_finally_break_does_not_leak_handler() {
|
||||
assert_eq!(
|
||||
eval("var caught = 0; for (var i = 0; i < 1; i++) { try { break; } finally { } } try { throw 1; } catch (e) { caught = 2; } caught"),
|
||||
"2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_in_finally_overrides() {
|
||||
assert_eq!(eval("function f() { try { return 1; } finally { return 2; } } f()"), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_in_finally_overrides_exception() {
|
||||
assert_eq!(eval("function f() { try { throw 1; } finally { return 2; } } f()"), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_in_finally_overrides_exception() {
|
||||
assert_eq!(
|
||||
eval("var n = 0; for (var i = 0; i < 5; i++) { try { try { throw 1; } finally { break; } } catch (e) { n = n + 1; } } n"),
|
||||
"0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_in_finally_overrides_break() {
|
||||
assert_eq!(eval("var log = 0; for (var i = 0; i < 3; i++) { try { break; } finally { log = log * 10 + i; continue; } } log"), "12");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throw_in_finally_overrides_previous_exception() {
|
||||
assert_eq!(eval("var fin = 0; try { try { throw 1; } finally { throw 2; } } catch (e) { fin = e; } fin"), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throw_then_finally_then_catch_chain_kept() {
|
||||
assert_eq!(
|
||||
eval("var fin = 0; var cc = 0; for (var i = 0; i < 3; i++) { try { try { throw i; } finally { fin = fin + 1; } } catch (e) { cc = cc + 1; } } fin * 10 + cc"),
|
||||
"33"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labeled_break_runs_finally() {
|
||||
assert_eq!(
|
||||
eval("var fin = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { try { break outer; } finally { fin = fin + 1; } } } fin"),
|
||||
"1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_function_break_ignores_caller_finally() {
|
||||
assert_eq!(
|
||||
eval("var log = 0; function inner() { for (var i = 0; i < 2; i++) { break; } return 1; } try { inner(); log = log * 10 + 1; } finally { log = log * 10 + 2; } log"),
|
||||
"12"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_with_finally_conditionally() {
|
||||
assert_eq!(
|
||||
eval("var fin = 0; var count = 0; for (var i = 0; i < 4; i++) { try { if (i % 2 === 0) continue; count = count + 1; } finally { fin = fin + 1; } } fin * 10 + count"),
|
||||
"42"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_finally_path_still_works() {
|
||||
assert_eq!(eval("var s = 0; for (var i = 0; i < 3; i++) { try { s = s + i; } finally { s = s + 10; } } s"), "33");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_loop_break_inside_try_does_not_trip_finally() {
|
||||
// break 目标是 try 域内的循环出口:不穿越 finally,finally 只走正常路径一次。
|
||||
assert_eq!(
|
||||
eval("var fin = 0; var i = 0; for (var k = 0; k < 3; k++) { try { while (i < 1) { break; } } finally { fin = fin + 1; } i = i + 1; } fin"),
|
||||
"3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triple_nested_finally_all_run_on_break() {
|
||||
// break 穿越三层 finally,每个都执行一次。
|
||||
assert_eq!(
|
||||
eval("var log = 0; for (var i = 0; i < 3; i++) { try { try { try { break; } finally { log = log * 10 + 1; } } finally { log = log * 10 + 2; } } finally { log = log * 10 + 3; } } log"),
|
||||
"123"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_crosses_multiple_finally() {
|
||||
assert_eq!(
|
||||
eval("var fin = 0; function f() { try { try { return 5; } finally { fin = fin + 1; } } finally { fin = fin + 10; } } var r = f(); r * 100 + fin"),
|
||||
"511"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_across_two_finally_runs_both_each_iteration() {
|
||||
// 内层 try/finally 包裹外层 try/finally,continue 逃出两者,两者每轮都执行。
|
||||
assert_eq!(
|
||||
eval("var a = 0; var b = 0; for (var i = 0; i < 3; i++) { try { try { continue; } finally { a = a + 1; } } finally { b = b + 1; } } a * 10 + b"),
|
||||
"33"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throw_in_finally_discards_pending_break() {
|
||||
// finally 内 throw 覆盖悬挂的 break 完成:break 不执行,异常向外传播。
|
||||
assert_eq!(
|
||||
eval("var fin = 0; var n = 0; for (var i = 0; i < 5; i++) { try { try { break; } finally { throw 9; } } catch (e) { n = e; } } n"),
|
||||
"9"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user