fix: 被捕获词法绑定入口建 TDZ 占位 cell 并跳过陈旧静态守卫

This commit is contained in:
2026-09-20 09:08:44 +00:00
parent 9a502e634a
commit 1433d47329
7 changed files with 438 additions and 4 deletions
+1
View File
@@ -16,6 +16,7 @@ mod upvalues;
use std::collections::HashSet;
pub(crate) use captured::collect_captured_bindings;
pub(crate) use names::collect_direct_lexical_names;
pub(crate) use names::collect_own_binding_names;
pub(crate) use names::collect_top_level_function_names;
pub(crate) use names::collect_top_level_function_names_ordered;
+57
View File
@@ -124,6 +124,63 @@ pub(crate) fn collect_top_level_function_names_ordered(stmts: &[Statement]) -> V
names
}
/// 收集语句列表**直接子级**词法声明名(let/const,含解构 pattern 叶):
/// 入口 TDZ 占位 cell 名集(被捕获的词法绑定在入口未初始化)。
///
/// 遍历面与 `predeclare_lexical_declarations` 一致——只走直接子级,不递归块/
/// if/for/switch case/try 区(块级名不提升到函数/程序作用域,不建入口
/// cell);for 头声明是循环作用域绑定,不纳;var 声明不纳(var 入口实例化
/// 独立覆盖);import 绑定由模块导入初始化的发射点建 cell,不纳。
pub(crate) fn collect_direct_lexical_names(stmts: &[Statement]) -> HashSet<String> {
let mut names = HashSet::new();
for stmt in stmts {
match stmt {
Statement::VariableDeclaration(decl) => {
if matches!(decl.kind, VariableDeclarationKind::Var) {
continue;
}
for d in &decl.declarations {
collect_binding_pattern_names(&d.id, &mut names);
}
}
Statement::ClassDeclaration(cd) => {
if let Some(id) = &cd.id {
names.insert(id.name.to_string());
}
}
Statement::ExportNamedDeclaration(exp) => {
if let Some(decl) = &exp.declaration {
match decl {
oxide_parser::Declaration::VariableDeclaration(vd) => {
if matches!(vd.kind, VariableDeclarationKind::Var) {
continue;
}
for d in &vd.declarations {
collect_binding_pattern_names(&d.id, &mut names);
}
}
oxide_parser::Declaration::ClassDeclaration(cd) => {
if let Some(id) = &cd.id {
names.insert(id.name.to_string());
}
}
_ => {}
}
}
}
Statement::ExportDefaultDeclaration(exp) => {
if let oxide_parser::ExportDefaultDeclarationKind::ClassDeclaration(cd) = &exp.declaration {
if let Some(id) = &cd.id {
names.insert(id.name.to_string());
}
}
}
_ => {}
}
}
names
}
/// 收集当前函数作用域声明的绑定名(参数 + 变量/函数声明,含嵌套 block,不含嵌套函数体)。
pub(crate) fn collect_own_binding_names(param_names: &[&str], stmts: &[Statement]) -> HashSet<String> {
let mut names = HashSet::new();
+11
View File
@@ -47,9 +47,20 @@ impl Emitter {
/// 赋值目标 TDZ 检查:未初始化绑定在赋值引用解析时抛 ReferenceError。
/// 须在 RHS 求值之前调用(规范:赋值 LHS 的 ResolveBinding 先于 RHS 副作用)。
///
/// # 边界与前提
/// - 被捕获名(upvalue)跳过:其 TDZ 由运行时 cell 初始化标志判定,编译期
/// 快照对声明序晚于函数编译的绑定是陈旧的。
///
/// # 副作用
/// - TDZ 命中时发射 THROW 指令序列,其后指令不可达但保持寄存器良定义。
pub(crate) fn emit_identifier_tdz_guard(&self, name: &str, ctx: &mut CompileCtx) -> Result<(), String> {
// 被捕获绑定经运行时 cell 的初始化标志统一判定(cell 读写与 upvalue
// 读写同面):嵌套函数 ctx 继承的父作用域 initialized 快照是陈旧的
// (函数声明编译早于 let/const 声明的初始化点),静态 throw 会误报,
// 命中捕获名即跳过,真 TDZ 由运行时抛。
if ctx.current_upvalue_captures.iter().any(|u| u.name == name) {
return Ok(());
}
if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) {
if !binding.initialized {
let _ = self.emit_tdz_throw(&format!("Cannot access '{name}' before initialization"), ctx)?;
+29 -2
View File
@@ -14,8 +14,8 @@ use oxide_ir::IRFunction;
use oxide_parser::{Expression, Statement, VariableDeclarationKind};
use crate::capture::{
collect_binding_pattern_names, collect_captured_bindings, collect_own_binding_names, collect_upvalue_names,
collect_var_binding_names,
collect_binding_pattern_names, collect_captured_bindings, collect_direct_lexical_names, collect_own_binding_names,
collect_upvalue_names, collect_var_binding_names,
};
use crate::compile_ctx::{CompileCtx, FieldBuffer};
use crate::symbol_table::{Binding, ScopeKind};
@@ -783,6 +783,33 @@ impl Emitter {
}
}
// 被捕获函数作用域词法(let/const/class)入口 TDZ 占位 cell:函数声明
// hoisting 先于声明语句执行时,闭包指向此未初始化 cell,声明前读写经
// 运行时抛真 TDZ;声明语句的 MAKE_CELL 按占位更新语义原位翻转。名集只取
// 直接子级(块级名不提升到函数作用域,与块内重执行 cell 族零交集),
// 排序保证发射序跨进程稳定。
let mut lex_names: Vec<String> = collect_direct_lexical_names(body_stmts)
.into_iter()
.filter(|n| !param_names.contains(&n.as_str()) && n != "arguments")
.filter(|n| ctx.captured_bindings.contains_key(n))
.collect();
lex_names.sort();
if !lex_names.is_empty() {
let undef_reg = self.emit_undefined(ctx);
for name in lex_names {
if let Some(&cell_idx) = ctx.captured_bindings.get(&name) {
// 未初始化标志折入 16 位立即数高字节(0x0100),dispatch 侧
// 按字节拆回两字段。
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(undef_reg),
Operand::Imm(cell_idx as u16 | 0x0100),
Operand::None,
));
}
}
}
Ok(param_base)
}
+25
View File
@@ -725,6 +725,31 @@ impl Emitter {
}
}
}
// 被捕获模块词法(let/const/class)入口 TDZ 占位 cell:模块词法导出
// 既有预注册保持未初始化 TDZ(命名空间对象面,按导出名),入口 cell
// 只覆盖被嵌套函数捕获的名(按捕获名),两面名集独立。声明语句的
// MAKE_CELL 按占位更新语义原位翻转;直接子级 only,块级名不提升。
let mut lex_names: Vec<String> = crate::capture::collect_direct_lexical_names(body)
.into_iter()
.filter(|n| !ctx.param_names.contains(n.as_str()))
.filter(|n| ctx.captured_bindings.contains_key(n))
.collect();
lex_names.sort();
if !lex_names.is_empty() {
let undef_reg = self.emit_undefined(ctx);
for name in lex_names {
if let Some(&cell_idx) = ctx.captured_bindings.get(&name) {
// 未初始化标志折入 16 位立即数高字节(0x0100),dispatch 侧
// 按字节拆回两字段。
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(undef_reg),
Operand::Imm(cell_idx as u16 | 0x0100),
Operand::None,
));
}
}
}
}
// —— re-export(export { x } from 'mod')链接检查:实例化期解析,须先于
+27 -2
View File
@@ -17,8 +17,8 @@ use oxide_ir::IRFunction;
use oxide_parser::Statement;
use crate::capture::{
collect_captured_bindings, collect_own_binding_names, collect_top_level_function_names,
collect_top_level_function_names_ordered, collect_var_binding_names,
collect_captured_bindings, collect_direct_lexical_names, collect_own_binding_names,
collect_top_level_function_names, collect_top_level_function_names_ordered, collect_var_binding_names,
};
use crate::compile_ctx::CompileCtx;
use crate::Emitter;
@@ -377,6 +377,31 @@ impl Emitter {
}
}
// 被捕获顶层词法(let/const/class)入口 TDZ 占位 cell:声明语句执行前
// 创建的闭包指向此未初始化 cell,声明前读写经运行时抛真 TDZ;声明语句
// 的 MAKE_CELL 按占位更新语义原位翻转为已初始化。名集取捕获集交集
// (直接子级词法名,块级名不提升到顶层),排序保证发射序跨进程稳定。
let mut lex_names: Vec<String> = collect_direct_lexical_names(&program.body)
.into_iter()
.filter(|n| ctx.captured_bindings.contains_key(n))
.collect();
lex_names.sort();
if !lex_names.is_empty() {
let undef_reg = self.emit_undefined(&mut ctx);
for name in lex_names {
if let Some(&cell_idx) = ctx.captured_bindings.get(&name) {
// 未初始化标志折入 16 位立即数高字节(0x0100),dispatch 侧
// 按字节拆回两字段。
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(undef_reg),
Operand::Imm(cell_idx as u16 | 0x0100),
Operand::None,
));
}
}
}
// 全局声明实例化序言:脚本求值前为顶层 var 名创建全局对象属性(值 undefined),
// 使声明语句执行前的读取(typeof、反射、自引用)可经全局对象见绑定。
// 顶层函数声明名不进序言:函数值是编译闭包,编译期不可得,其全局对象属性值
@@ -0,0 +1,288 @@
//! 被捕获词法绑定(let/const)的 TDZ 语义钉:入口 TDZ 占位 cell 与赋值引用形
//! 静态守卫跳过的引擎侧行为锁。顶层/函数作用域的词法绑定被嵌套函数捕获后,
//! 声明语句执行前的读写须经运行时 cell 的未初始化标志抛 ReferenceError,
//! 声明语句执行后读写正常;const 捕获绑定的写点抛 TypeError。
//!
//! 完成值一律收敛为布尔(JsValue 的 Display 不暴露字符串内容),字符串比较在
//! 引擎内完成。
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}"),
}
}
// ── 顶层 let 被函数声明捕获:声明后赋值引用形(编译序判别面)──
#[test]
fn captured_top_let_update_after_decl_returns_new_value() {
// 函数声明先于 let 声明语句编译,捕获 cell 在声明语句执行后才翻转已初始化,
// 赋值引用形不得误报编译期 TDZ。
assert_eq!(
eval("let step; function r() { step++; return step; } step = 0; r() === 1"),
"true",
"captured top-level let update works after the declaration executes"
);
}
#[test]
fn captured_top_let_simple_assign_after_decl() {
assert_eq!(
eval("let step; function r() { step = 7; return step; } step = 0; r() === 7"),
"true",
"captured top-level let simple assignment works after the declaration executes"
);
}
#[test]
fn captured_top_let_compound_assign_after_decl() {
assert_eq!(
eval("let step; function r() { step += 5; return step; } step = 1; r() === 6"),
"true",
"captured top-level let compound assignment works after the declaration executes"
);
}
#[test]
fn captured_top_let_bare_read_after_decl() {
// 裸读不经静态守卫,守卫跳过面不得波及裸读路径。
assert_eq!(
eval("let step; function r() { return step; } step = 0; r() === 0"),
"true",
"captured top-level let bare read works after the declaration executes"
);
}
#[test]
fn captured_top_let_update_in_stringify_replacer() {
// builtin 回调深度不影响判别:赋值引用形只由编译序决定。
assert_eq!(
eval(
"let step; function r() { step++; return step; } step = 0; JSON.stringify({ a: 1 }, (k, v) => { r(); return v; }); step === 2"
),
"true",
"captured top-level let update works from a stringify replacer"
);
}
#[test]
fn captured_top_let_update_in_map_callback() {
assert_eq!(
eval("let step; function r() { step++; return step; } step = 0; [1, 2].map(() => r()).join(',') === '1,2'"),
"true",
"captured top-level let update works from an array map callback"
);
}
#[test]
fn captured_top_let_update_in_stringify_replacer_expr() {
assert_eq!(
eval("let step; function r() { step++; return step; } step = 0; JSON.stringify({ a: 1 }, (k, v) => r() || v); step === 1"),
"true",
"captured top-level let update works when the replacer is a bare call expression"
);
}
#[test]
fn captured_top_let_update_in_getter() {
assert_eq!(
eval("let step; function r() { step++; return step; } step = 0; Object.values({ get a() { return r(); } }).join(',') === '1'"),
"true",
"captured top-level let update works from an object getter"
);
}
#[test]
fn captured_top_let_update_in_nested_arrow() {
// 链式捕获(内层箭头的 upvalue 指向外层闭包的 upvalue cell)。
assert_eq!(
eval("let step; function outer() { return () => { step++; return step; }; } step = 0; outer()() === 1"),
"true",
"chained capture of a top-level let update works in a nested arrow"
);
}
#[test]
fn captured_top_let_simple_assign_in_stringify_setter() {
assert_eq!(
eval(
"let step; function setter() { step = 5; } function reader() { return step; } step = 0; JSON.stringify({}, (k, v) => { setter(); return reader(); }); step === 5"
),
"true",
"captured top-level let simple assignment works from a stringify setter"
);
}
#[test]
fn captured_top_let_update_in_named_replacer() {
assert_eq!(
eval(
"let step; function replacer(x, k, v) { step++; return x; } step = 0; JSON.stringify({}, (k, v) => replacer(2, k, v)); step === 1"
),
"true",
"captured top-level let update works through an indirect named replacer"
);
}
#[test]
fn captured_top_let_with_init_bare_read() {
assert_eq!(
eval("let step = 3; function r() { return step; } r() === 3"),
"true",
"captured top-level let with initializer reads through the closure"
);
}
#[test]
fn function_decl_before_let_bare_read() {
// 函数声明在 let 之前(源序前),裸读经运行时 cell,声明后正常。
assert_eq!(
eval("function r() { return step; } let step; step = 0; r() === 0"),
"true",
"function declared before a captured let reads it after initialization"
);
}
// ── 箭头在 let 前:入口 TDZ 占位 cell 面(无编译期命中,运行时判定)──
#[test]
fn arrow_after_let_update_returns_new_value() {
assert_eq!(
eval("let step; const r = () => { step++; return step; }; step = 0; r() === 1"),
"true",
"arrow created after a captured let updates the binding"
);
}
#[test]
fn arrow_before_let_update_returns_new_value() {
// 箭头先于 let 创建:入口 TDZ 占位 cell 使声明前读抛真 TDZ,
// 声明后 MAKE_CELL 原位翻转,更新正常。
assert_eq!(
eval("const r = () => { step++; return step; }; let step; step = 0; r() === 1"),
"true",
"arrow created before the captured let updates the binding after initialization"
);
}
// ── 捕获 const:写点 TypeError(TDZ 守卫跳过面不得吞 const 判定)──
#[test]
fn captured_top_const_update_throws_type_error() {
assert_eq!(
eval("const step = 1; function r() { step++; } try { r(); false } catch (e) { e.constructor === TypeError }"),
"true",
"updating a captured const throws TypeError, not a TDZ reference error"
);
}
#[test]
fn captured_top_const_simple_assign_throws_type_error() {
assert_eq!(
eval("const step = 1; function r() { step = 2; return step; } try { r(); false } catch (e) { e.constructor === TypeError }"),
"true",
"assigning a captured const throws TypeError"
);
}
// ── 真 TDZ 保抛:声明语句执行前的读写仍抛 ReferenceError ──
#[test]
fn true_tdz_captured_let_update_throws_reference_error() {
// 声明语句在 try 块之后才执行:调用点命中入口 TDZ 占位 cell。
assert_eq!(
eval("function f() { x++; } try { f(); false } catch (e) { e.constructor === ReferenceError } let x = 1"),
"true",
"updating a captured let before its declaration throws ReferenceError"
);
}
#[test]
fn true_tdz_captured_let_update_in_nested_depth() {
assert_eq!(
eval(
"function f() { x++; } JSON.stringify({}, (k, v) => { try { f(); } catch (e) { return e.constructor.name; } return v; }); let x = 1; x === 1"
),
"true",
"true TDZ on a captured let survives builtin callback depth and stays catchable"
);
}
#[test]
fn true_tdz_captured_let_bare_read_throws_inside_catcher() {
// 裸读真 TDZ 在捕获器内抛(结果未检视,仅锁"抛且可捕获"行为)。
assert_eq!(
eval(
"function r() { return step; } JSON.stringify({}, (k, v) => { try { String(r()); return 'no-throw'; } catch (e) { return e.constructor.name; } }); let step = 1; 'done' === 'done'"
),
"true",
"bare read of a captured let before its declaration throws inside the catcher"
);
}
#[test]
fn block_level_let_tdz_update_still_throws() {
// 块级 let 无嵌套捕获(纯静态 TDZ 面):守卫跳过面不得波及,仍编译期抛。
assert_eq!(
eval("function f() { y = y + 1; let y; return y; } try { f(); false } catch (e) { e.constructor === ReferenceError }"),
"true",
"block-level let TDZ on an uncaptured binding still throws ReferenceError"
);
}
#[test]
fn block_level_let_captured_closure_reads_updated_value() {
// 块级 let 被块内闭包捕获:块内声明后更新,块外读回更新值(重执行族对照面)。
assert_eq!(
eval("function g() { { let a = 5; const c = () => a; a = 6; return c; } return null; } g()() === 6"),
"true",
"closure over a block-level let reads the updated value after the block"
);
}
// ── 非捕获对照面:顶层 var / delete / typeof 不变 ──
#[test]
fn captured_top_var_update_after_decl() {
// 顶层 var 被捕获:入口即初始化为 undefined(提升语义),更新正常。
assert_eq!(
eval("var x; function r() { x++; return x; } x = 0; r() === 1 && x === 1"),
"true",
"captured top-level var update works, var hoisting semantics preserved"
);
}
#[test]
fn delete_captured_top_let_returns_false() {
assert_eq!(
eval("let step = 1; function r() { return delete step; } r() === false && step === 1"),
"true",
"deleting a captured top-level let returns false and leaves the value"
);
}
#[test]
fn typeof_captured_top_let_after_decl() {
assert_eq!(
eval("let step; function r() { return typeof step === 'number'; } step = 0; r()"),
"true",
"typeof on a captured top-level let after initialization reports number"
);
}