diff --git a/crates/oxide_emit/src/capture/mod.rs b/crates/oxide_emit/src/capture/mod.rs index 83b18bff..8538fd36 100644 --- a/crates/oxide_emit/src/capture/mod.rs +++ b/crates/oxide_emit/src/capture/mod.rs @@ -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; diff --git a/crates/oxide_emit/src/capture/names.rs b/crates/oxide_emit/src/capture/names.rs index b05287cf..fc51cd9d 100644 --- a/crates/oxide_emit/src/capture/names.rs +++ b/crates/oxide_emit/src/capture/names.rs @@ -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 { + 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 { let mut names = HashSet::new(); diff --git a/crates/oxide_emit/src/errors.rs b/crates/oxide_emit/src/errors.rs index 6219ba1e..db72802e 100644 --- a/crates/oxide_emit/src/errors.rs +++ b/crates/oxide_emit/src/errors.rs @@ -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)?; diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index 4b017026..75936072 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -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 = 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) } diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index 5e402159..e1ff60bd 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -725,6 +725,31 @@ impl Emitter { } } } + // 被捕获模块词法(let/const/class)入口 TDZ 占位 cell:模块词法导出 + // 既有预注册保持未初始化 TDZ(命名空间对象面,按导出名),入口 cell + // 只覆盖被嵌套函数捕获的名(按捕获名),两面名集独立。声明语句的 + // MAKE_CELL 按占位更新语义原位翻转;直接子级 only,块级名不提升。 + let mut lex_names: Vec = 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')链接检查:实例化期解析,须先于 diff --git a/crates/oxide_emit/src/program.rs b/crates/oxide_emit/src/program.rs index bd5eac3b..a610bb2f 100644 --- a/crates/oxide_emit/src/program.rs +++ b/crates/oxide_emit/src/program.rs @@ -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 = 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、反射、自引用)可经全局对象见绑定。 // 顶层函数声明名不进序言:函数值是编译闭包,编译期不可得,其全局对象属性值 diff --git a/crates/oxide_vm/tests/tdz_upvalue_capture_tests.rs b/crates/oxide_vm/tests/tdz_upvalue_capture_tests.rs new file mode 100644 index 00000000..55221b5f --- /dev/null +++ b/crates/oxide_vm/tests/tdz_upvalue_capture_tests.rs @@ -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" + ); +}