mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
fix: try/finally 支持 break/continue/return 穿越 finally(completion 机制)
This commit is contained in:
@@ -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