mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
170 lines
6.3 KiB
Rust
170 lines
6.3 KiB
Rust
//! 块作用域退出后的名字解析钉:块级 `let`/`const` 退出后名字不可解析,读取
|
||
//! 须落全局解析(ReferenceError / typeof 为 "undefined"),不得按名命中残留
|
||
//! 捕获 cell 读到已失效的值。
|
||
//!
|
||
//! 覆盖同函数直读、嵌套函数捕获、`typeof`、严格模式写与 direct eval 各面,
|
||
//! 并锁住合法块内捕获与参数遮蔽不回归。
|
||
|
||
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}"),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn catch_nested_function_bare_read_of_popped_block_let_throws_reference_error() {
|
||
// 语料主形:catch 内嵌套函数裸读 try 内已退出块的 let,须抛 ReferenceError
|
||
// 而非命中残留 cell 读到 18。
|
||
assert_eq!(
|
||
eval(
|
||
"var ref = false; \
|
||
try { { let xx = 18; throw 25; } } catch (e) { \
|
||
(function () { try { xx; } catch (e2) { ref = (e2 instanceof ReferenceError); } })(); \
|
||
} ref"
|
||
),
|
||
"true"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn typeof_popped_block_let_in_nested_function_is_undefined() {
|
||
// 块外嵌套函数对已退出块 let 的 typeof:未解析引用求值 "undefined",
|
||
// 不得命中残留 cell 得到 "number"。
|
||
assert_eq!(eval("{ let zz = 7; } (function () { return typeof zz === \"undefined\"; })()"), "true");
|
||
}
|
||
|
||
#[test]
|
||
fn direct_read_of_popped_block_let_throws_reference_error_with_legal_capture() {
|
||
// 同函数内既有合法块内捕获,块外直读仍须抛 ReferenceError;捕获闭包本身
|
||
// 读到块内值不受影响。
|
||
assert_eq!(
|
||
eval(
|
||
"var f; { let aa = 1; f = function () { return aa; }; } \
|
||
var ref = false; try { aa; } catch (e) { ref = (e instanceof ReferenceError); } \
|
||
ref && f() === 1"
|
||
),
|
||
"true"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn typeof_popped_block_let_with_legal_capture_is_undefined() {
|
||
// 同函数 typeof:块外 typeof 已退出块 let 为 "undefined"(非抛非旧值)。
|
||
assert_eq!(
|
||
eval("{ let aa = 1; var f = function () { return aa; }; } typeof aa === \"undefined\""),
|
||
"true"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn strict_write_to_popped_block_let_throws_reference_error() {
|
||
// 严格模式对已退出块 let 的块外写须抛 ReferenceError,不得按名 CELL_SET
|
||
// 污染残留 cell;块内闭包捕获值不变。
|
||
assert_eq!(
|
||
eval(
|
||
"\"use strict\"; var f; { let aa = 1; f = function () { return aa; }; } \
|
||
var ref = false; try { aa = 99; } catch (e) { ref = (e instanceof ReferenceError); } \
|
||
ref && f() === 1"
|
||
),
|
||
"true"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn eval_of_popped_block_let_throws_reference_error() {
|
||
// direct eval 面锁:调用方块外的 eval('xx') 看不到已退出块绑定,抛
|
||
// ReferenceError(当前已绿,锁不回归)。
|
||
assert_eq!(
|
||
eval(
|
||
"var ref = false; \
|
||
try { { let xx = 18; throw 25; } } catch (e) { \
|
||
(function () { try { eval(\"xx\"); } catch (e2) { ref = (e2 instanceof ReferenceError); } })(); \
|
||
} ref"
|
||
),
|
||
"true"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn closure_created_inside_block_reads_let_after_block() {
|
||
// 合法块内捕获守卫:块内创建的闭包在块外调用仍读到块绑定值。
|
||
assert_eq!(eval("var r; { let bb = 7; r = function () { return bb; }; } r() === 7"), "true");
|
||
}
|
||
|
||
#[test]
|
||
fn chained_closure_reads_grandparent_block_let() {
|
||
// 链式捕获守卫:跨两层闭包读取祖父块内绑定不受可见性过滤影响。
|
||
assert_eq!(eval("var h; { let cc = 5; h = () => () => cc; } h()() === 5"), "true");
|
||
}
|
||
|
||
#[test]
|
||
fn catch_block_let_shadows_parameter() {
|
||
// 参数遮蔽守卫:catch/try 块内 let 只遮蔽块内引用,catch 外仍读参数值。
|
||
assert_eq!(
|
||
eval("(function (x) { try { let x = \"in\"; throw 0; } catch (e) { return x === \"out\"; } })(\"out\")"),
|
||
"true"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn read_of_uncaptured_popped_block_let_throws_reference_error() {
|
||
// 无捕获对照守卫:无任何闭包捕获时块外直读同样抛 ReferenceError。
|
||
assert_eq!(
|
||
eval("var ref = false; { let aa = 1; } try { aa; } catch (e) { ref = (e instanceof ReferenceError); } ref"),
|
||
"true"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn for_head_forward_capture_of_self_is_visible() {
|
||
// C 风格 for 头 init 内闭包对同头头名的前向引用须命中父捕获 cell:头名不像
|
||
// 块级 let 那样预声明,可见性过滤不得将其误删。
|
||
assert_eq!(eval("var r; for (let i = (() => i); !r; ) { r = i; } r() === r"), "true");
|
||
}
|
||
|
||
#[test]
|
||
fn for_head_forward_capture_of_later_declarator_is_visible() {
|
||
// 多声明符头:首声明符 init 内闭包引用后一声明符头名,须读到其初始化值。
|
||
assert_eq!(eval("var g; for (let f = () => j, j = 1; ; ) { g = f; break; } g() === 1"), "true");
|
||
}
|
||
|
||
#[test]
|
||
fn sloppy_write_then_read_of_popped_block_capture_name_keeps_global_value() {
|
||
// 块外 sloppy 简单写物化隐式全局属性后,同名裸读须读回该属性(99),
|
||
// 不得因隐式全局登记被误判为可见而命中残留捕获 cell(1)。
|
||
assert_eq!(eval("{ let aa = 1; f = () => aa; } aa = 99; aa"), "99");
|
||
}
|
||
|
||
#[test]
|
||
fn repeated_bare_read_of_invisible_capture_name_throws_each_time() {
|
||
// 不可见捕获名二次裸读:读侧隐式全局登记不构成真实词法绑定,两次均须抛
|
||
// ReferenceError,不得第二次命中残留 cell 返回旧值。
|
||
assert_eq!(
|
||
eval(
|
||
"var r; { let bb = 7; r = () => bb; } \
|
||
var first = false, second = false; \
|
||
try { bb; } catch (e) { first = e instanceof ReferenceError; } \
|
||
try { bb; } catch (e) { second = e instanceof ReferenceError; } \
|
||
first && second"
|
||
),
|
||
"true"
|
||
);
|
||
}
|