Files
OxideJS-New/crates/oxide_vm/tests/block_scope_leave_tests.rs
T

172 lines
6.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 块作用域退出后的名字解析钉:块级 `let`/`const` 退出后名字不可解析,读取
//! 须落全局解析(ReferenceError / typeof 为 "undefined"),不得按名命中残留
//! 捕获 cell 读到已失效的值。
//!
//! 覆盖同函数直读、嵌套函数捕获、`typeof`、严格模式写与 direct eval 各面,
//! 并锁住合法块内捕获与参数遮蔽不回归。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_compiler::DefaultCompilerService;
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();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
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"
);
}