fix: TDZ 写与 const 赋值检查补全(含闭包捕获路径)
This commit is contained in:
@@ -252,6 +252,10 @@ impl Vm {
|
||||
}
|
||||
unsafe {
|
||||
let cell = &mut *cell_ptr;
|
||||
// TDZ 写检查:cell 未初始化(捕获绑定声明点前)写抛 ReferenceError。
|
||||
if !cell.is_initialized() {
|
||||
return self.raise_error_kind("ReferenceError", "Cannot access variable before initialization");
|
||||
}
|
||||
cell.value = src_val;
|
||||
cell.set_initialized(true);
|
||||
}
|
||||
@@ -330,7 +334,17 @@ impl Vm {
|
||||
if !upvals[uv_idx].is_null() {
|
||||
unsafe {
|
||||
let cell = &mut *upvals[uv_idx];
|
||||
if const_flag != 0 && !cell.value.is_undefined() {
|
||||
// TDZ 写检查:cell 未初始化(捕获绑定声明点前)写抛 ReferenceError。
|
||||
if !cell.is_initialized() {
|
||||
self.raise_error_kind(
|
||||
"ReferenceError",
|
||||
"Cannot access variable before initialization",
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
// const guard:TDZ 检查后 cell 必已初始化(存在性判定,与值无关),
|
||||
// const 初始值为 undefined 时再赋值同样抛。
|
||||
if const_flag != 0 {
|
||||
self.raise_error_kind("TypeError", "Assignment to constant variable")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -50,3 +50,64 @@ fn block_scoped_const_shadowing_independent() {
|
||||
fn const_destructuring_declaration_works() {
|
||||
assert_eq!(eval("const { a } = { a: 7 }; a"), "7");
|
||||
}
|
||||
|
||||
// const 初始化为 undefined 时再赋值也必须抛(guard 不能依赖槽值)。
|
||||
#[test]
|
||||
fn const_undefined_reassignment_throws() {
|
||||
let out = eval("const x = undefined; x = 5");
|
||||
assert!(out.contains("Assignment to constant variable"), "got: {out}");
|
||||
}
|
||||
|
||||
// const 初始化为 undefined 时读取不受影响。
|
||||
#[test]
|
||||
fn const_undefined_read_ok() {
|
||||
assert_eq!(eval("const x = undefined; x"), "undefined");
|
||||
}
|
||||
|
||||
// 闭包捕获 const 赋值抛(cell 写路径编译期拦截)。
|
||||
#[test]
|
||||
fn const_captured_write_throws() {
|
||||
let out = eval("const x = 1; (()=>x=2)()");
|
||||
assert!(out.contains("Assignment to constant variable"), "got: {out}");
|
||||
}
|
||||
|
||||
// 函数体内 const 被嵌套函数捕获(upvalue 路径)写同样抛。
|
||||
#[test]
|
||||
fn const_upvalue_write_throws() {
|
||||
let out = eval("function outer(){ const x=1; function inner(){ x=2; } inner(); } outer()");
|
||||
assert!(out.contains("Assignment to constant variable"), "got: {out}");
|
||||
}
|
||||
|
||||
// 自增/自减路径 const 赋值抛。
|
||||
#[test]
|
||||
fn const_update_throws() {
|
||||
let out = eval("const x = 1; x++");
|
||||
assert!(out.contains("Assignment to constant variable"), "got: {out}");
|
||||
}
|
||||
|
||||
// 复合赋值路径 const 抛。
|
||||
#[test]
|
||||
fn const_compound_throws() {
|
||||
let out = eval("const x = 1; x += 1");
|
||||
assert!(out.contains("Assignment to constant variable"), "got: {out}");
|
||||
}
|
||||
|
||||
// 解构赋值目标 const 抛。
|
||||
#[test]
|
||||
fn const_destructuring_assignment_throws() {
|
||||
let out = eval("const [a] = [1]; a = 5");
|
||||
assert!(out.contains("Assignment to constant variable"), "got: {out}");
|
||||
}
|
||||
|
||||
// 逻辑赋值短路场景:const truthy 时 ||= 不写不抛。
|
||||
#[test]
|
||||
fn const_logical_assign_short_circuit_ok() {
|
||||
assert_eq!(eval("const x = 1; x ||= 5; x"), "1");
|
||||
}
|
||||
|
||||
// 逻辑赋值非短路场景:const falsy 时 ||= 写抛。
|
||||
#[test]
|
||||
fn const_logical_assign_write_throws() {
|
||||
let out = eval("const x = 0; x ||= 5");
|
||||
assert!(out.contains("Assignment to constant variable"), "got: {out}");
|
||||
}
|
||||
|
||||
@@ -112,6 +112,49 @@ fn for_let_per_iteration_independent() {
|
||||
|
||||
#[test]
|
||||
fn tdz_access_before_init_throws() {
|
||||
let _ = eval("x; let x=1");
|
||||
// 未支持:精确 TDZ 尚未实现。
|
||||
// 块级预声明后声明点前读取:TDZ 读抛 ReferenceError。
|
||||
let err = eval("x; let x=1").unwrap_err();
|
||||
assert!(err.contains("ReferenceError"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tdz_write_throws() {
|
||||
// TDZ 写:赋值引用解析先于 RHS,`x=1; let x;` 抛 ReferenceError。
|
||||
let err = eval("x=1; let x;").unwrap_err();
|
||||
assert!(err.contains("ReferenceError"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tdz_compound_write_throws() {
|
||||
// 复合赋值路径 TDZ:`x += 1` 在读旧值前解析赋值引用。
|
||||
let err = eval("{ x += 1; let x; }").unwrap_err();
|
||||
assert!(err.contains("ReferenceError"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tdz_update_throws() {
|
||||
// 自增/自减路径 TDZ:`x++` 同样抛 ReferenceError。
|
||||
let err = eval("{ x++; let x; }").unwrap_err();
|
||||
assert!(err.contains("ReferenceError"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tdz_destructuring_throws() {
|
||||
// 解构赋值目标 TDZ:`[a]=[1]` 写未初始化 a 抛 ReferenceError。
|
||||
let err = eval("[a]=[1]; let a;").unwrap_err();
|
||||
assert!(err.contains("ReferenceError"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tdz_write_rhs_side_effect_not_evaluated() {
|
||||
// 检查点顺序:TDZ 检查在 RHS 求值之前,RHS 副作用不执行(s 保持 0)。
|
||||
let result = eval("var s=0; try{ x=(s=1); let x; }catch(e){} s").unwrap();
|
||||
assert_eq!(result.as_int(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typeof_tdz_throws() {
|
||||
// typeof 对 TDZ 绑定抛 ReferenceError(非 "undefined")。
|
||||
let err = eval("typeof x; let x;").unwrap_err();
|
||||
assert!(err.contains("ReferenceError"), "got: {}", err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user