fix: catch 参数名补入捕获扫描且 catch 入口新 cell

This commit is contained in:
2026-09-20 04:46:41 +00:00
parent 9b4f1e916d
commit 2b0238f310
3 changed files with 260 additions and 1 deletions
@@ -0,0 +1,251 @@
//! catch 参数捕获与每入口新 cell 形态锁:catch 参数绑定名(含解构模式全名)计入
//! 本函数 own 集,被嵌套闭包捕获时 catch 入口发 MAKE_CELL_FRESH——每次 catch 执行
//! 为参数新建词法环境(替换上一入口的 cell 而非原地覆写),子模块经 upvalue 读;
//! try 体/finally 体闭包对参数的域外引用保持运行期 ReferenceError。
//!
//! 完成值一律收敛为布尔(JsValue 的 Display 只暴露 number/bool,不暴露字符串内容),
//! 字符串比较在引擎内完成。
use std::sync::Arc;
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(&Arc::new(module)) {
Ok(result) => format!("{result}"),
Err(e) => format!("vm error: {e}"),
}
}
/// 断言运行期抛出未捕获 ReferenceError(域外引用守卫的期望形态)。
fn assert_uncaught_reference_error(source: &str, msg: &str) {
let res = eval(source);
assert!(
res.starts_with("vm error") && res.contains("ReferenceError"),
"{msg}: expected an uncaught ReferenceError, got: {res}"
);
}
// ── 闭包捕获参数:直引 / 模式名 / 同名遮蔽 / 嵌套函数形 / 顶层 / 写穿 / 跨帧 ──
#[test]
fn catch_param_closure_direct_ref_reads_thrown_value() {
// 主形态:catch 体闭包直引参数,读值为抛入的异常值。
assert_eq!(
eval("(() => { let f; try { throw 1; } catch (e) { f = () => e; } return f(); })()"),
"1",
"closure in catch body must read the thrown value through the parameter cell"
);
}
#[test]
fn catch_param_destructured_name_captured_by_closure() {
// 解构参数模式内的绑定名与裸参数名同口径:闭包捕获读属性值。
assert_eq!(
eval("(() => { let f; try { throw { message: 7 }; } catch ({ message: e }) { f = () => e; } return f(); })()"),
"7",
"destructuring catch parameter names are captured bindings too"
);
}
#[test]
fn catch_param_shadows_outer_var_closure_reads_param() {
// 外层 var 同名遮蔽:catch 体内的闭包捕获参数(内层绑定),读抛入值而非外层 var。
assert_eq!(
eval("(() => { var e = 99; try { throw 5; } catch (e) { const f = () => e; return f(); } })()"),
"5",
"closure captures the catch parameter, not the same-named outer var"
);
}
#[test]
fn catch_param_shadows_function_param_closure_reads_param() {
// 外层函数形参同名遮蔽:函数作用域内 catch 参数遮蔽形参,闭包读参数。
assert_eq!(
eval("(function(e) { try { throw 3; } catch (e) { const f = () => e; return f(); } })(88)"),
"3",
"closure captures the catch parameter, not the same-named outer function param"
);
}
#[test]
fn catch_param_function_decl_inside_catch_reads_param() {
// 函数声明形(非箭头):catch 体内声明的函数引用参数,读抛入值。
assert_eq!(
eval("(() => { let f; try { throw 6; } catch (e) { function g() { return e; } f = g; } return f(); })()"),
"6",
"function declared in catch body reads the captured parameter"
);
}
#[test]
fn catch_param_class_method_inside_catch_reads_param() {
// 类方法形:catch 体内类表达式的方法引用参数,读抛入值。
assert_eq!(
eval(
"(() => { let C; try { throw 8; } catch (e) { C = class { m() { return e; } }; } return new C().m(); })()"
),
"8",
"class method defined in catch body reads the captured parameter"
);
}
#[test]
fn catch_param_top_level_closure_reads_param() {
// 脚本顶层 catch 参数被顶层闭包捕获:顶层 cell 层同样承载参数 cell。
assert_eq!(
eval("let f; try { throw 11; } catch (e) { f = () => e; } f()"),
"11",
"top-level catch parameter is capturable by top-level closures"
);
}
#[test]
fn catch_param_reassign_then_closure_reads_new_value() {
// 写穿面:catch 体内重赋参数后闭包读新值(赋值须写穿共享单元)。
assert_eq!(
eval("(() => { let f; try { throw 1; } catch (e) { e = 99; f = () => e; } return f(); })()"),
"99",
"assignment to the catch parameter writes through the captured cell"
);
}
#[test]
fn catch_param_recursive_frames_each_frame_isolated() {
// 跨帧隔离:同名参数各调用帧独立 cell,闭包读各自帧的抛入值。
assert_eq!(
eval(
"(() => { function g(n) { try { throw n; } catch (e) { return () => e; } } \
let fs = [g(1), g(2)]; return fs.map(f => f()).join('|') === '1|2'; })()"
),
"true",
"each call frame builds its own parameter cell for the closure"
);
}
#[test]
fn catch_param_default_value_inner_fn_reads_param() {
// 参数默认值时序:模式默认值内嵌套函数引用参数,绑定先于默认值求值。
assert_eq!(
eval("(() => { let f; try { throw 4; } catch (e) { const g = (x = () => e) => x; f = g(); } return f(); })()"),
"4",
"nested function in a parameter default reads the catch parameter"
);
}
// ── 每 catch 入口新 cell(替换而非覆写)──
#[test]
fn catch_param_loop_reentry_each_iteration_new_cell() {
// 循环重入:每迭代 catch 入口新 cell,各迭代闭包读各自迭代值。
assert_eq!(
eval(
"(() => { let r = []; for (let i = 0; i < 2; i++) { try { throw i; } catch (e) { r.push(() => e); } } \
return r.map(f => f()).join('|') === '0|1'; })()"
),
"true",
"each loop iteration's catch entry builds a fresh parameter cell"
);
}
#[test]
fn catch_param_consecutive_try_catch_each_entry_new_cell() {
// 无循环连续两个 try/catch 同名参数:第二个入口新 cell 不覆写第一个入口闭包
// 已持的 cell 指针(门控若按循环在飞判定,本形态读错值)。
assert_eq!(
eval(
"(() => { let r = []; try { throw 1; } catch (e) { r.push(() => e); } \
try { throw 2; } catch (e) { r.push(() => e); } \
return r.map(f => f()).join('|') === '1|2'; })()"
),
"true",
"consecutive catch entries with the same parameter name build fresh cells"
);
}
#[test]
fn catch_param_nested_catch_outer_closure_keeps_old_cell() {
// 同名嵌套 catch:内层入口新 cell 替换槽位,外层闭包仍持旧 cell 指针读旧值。
assert_eq!(
eval(
"(() => { let outer; try { throw 1; } catch (e) { outer = () => e; \
try { throw 2; } catch (e) { } } return outer(); })()"
),
"1",
"inner catch entry must not clobber the outer closure's cell"
);
}
// ── 守卫:既有直读/遮蔽/B022 模式面语义保真 ──
#[test]
fn catch_param_direct_read_in_body() {
// 无闭包直读参数(最简形态):直读保真。
assert_eq!(
eval("try { throw 42; } catch (e) { e }"),
"42",
"direct read of the catch parameter in its own body"
);
}
#[test]
fn catch_param_shadows_outer_var_direct_read() {
// var 同名遮蔽直读(无闭包):catch 体内直读参数值。
assert_eq!(
eval("(() => { var e = 99; try { throw 5; } catch (e) { return e; } })()"),
"5",
"direct read in catch body sees the parameter, not the outer var"
);
}
#[test]
fn catch_param_destructured_direct_read() {
// 解构参数直读(无闭包):模式绑定求值后直读求和。
assert_eq!(
eval("(() => { try { throw [1, 2]; } catch ([a, b]) { return a + b; } })()"),
"3",
"destructuring catch parameter direct read"
);
}
#[test]
fn catch_param_pattern_default_value_evaluated() {
// 模式默认值面:抛入值非 undefined 时默认值不求值,直读抛入元素。
assert_eq!(
eval("(() => { const g = () => 55; try { throw [1]; } catch ([x = g()]) { return x; } })()"),
"1",
"pattern default value is not evaluated when the thrown element is present"
);
}
#[test]
fn catch_param_ref_from_try_body_stays_reference_error() {
// 域外引用:try 体闭包引用 catch 参数——参数在 try 体域内未声明,
// 修复后仍须运行期 ReferenceError(与 node 一致)。
assert_uncaught_reference_error(
"(() => { let f; try { f = () => e; throw 1; } catch (e) {} return f(); })()",
"closure in try body referencing the catch parameter must stay unresolvable",
);
}
#[test]
fn catch_param_ref_from_finally_body_stays_reference_error() {
// 域外引用:finally 体闭包引用 catch 参数——参数作用域不含 finally 体,
// 修复后仍须运行期 ReferenceError(与 node 一致)。
assert_uncaught_reference_error(
"(() => { let f; try { throw 1; } catch (e) {} finally { f = () => e; } return f(); })()",
"closure in finally body referencing the catch parameter must stay unresolvable",
);
}