diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index 64e1b5e0..aee998ca 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -130,6 +130,11 @@ pub struct CompileCtx { /// let/const 循环变量每迭代新分配一个 cell,update 写寄存器(不污染本迭代 /// 闭包捕获的 cell),下一迭代把这个寄存器值拷入新分配的 cell。 pub(crate) register_update_names: Vec, + /// 正在发射的 C 风格 for 头词法绑定名(含解构叶):这些头名在 init 中经覆盖 + /// cell 建 `MAKE_CELL`,但循环体/test/update 读的是绑定寄存器、每迭代 fresh + /// 拷贝的源也是寄存器,故 `emit_bind_target` 对集合内名字在 cell 写之外补写 + /// 绑定寄存器。init 发射完毕后清空。 + pub(crate) for_head_store_registers: HashSet, /// 模块编译上下文:当前模块命名空间对象寄存器(`__moduleObject` 返回值)。 pub(crate) module_ns_reg: Option, /// 已求值依赖模块的命名空间对象寄存器(按 import/export source 字符串索引)。 @@ -216,6 +221,7 @@ impl CompileCtx { with_stack: Vec::new(), open_try_handlers: Vec::new(), register_update_names: Vec::new(), + for_head_store_registers: HashSet::new(), module_ns_reg: None, module_dep_ns_regs: HashMap::new(), module_self_import_specs: HashSet::new(), diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index 4040a1da..70dff001 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -89,6 +89,16 @@ impl Emitter { if let Some(&cell_idx) = ctx.captured_bindings.get(name) { let op = if fresh_cell { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL }; ctx.inst(Inst::new(op, Operand::Reg(src_reg), Operand::Imm(cell_idx as u16), Operand::None)); + // C 风格 for 头绑定:cell 之外补写绑定寄存器——循环体/test/update 读寄存器, + // 每迭代 fresh 拷贝的源也是寄存器(见 `CompileCtx::for_head_store_registers`)。 + if ctx.for_head_store_registers.contains(name) { + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(target_reg), + Operand::Reg(src_reg), + Operand::Imm(0), + )); + } } else if !ctx.with_stack.is_empty() && !ctx.is_with_internal_binding(name) { // with 内 var 初始化:对象有该属性则写对象,否则写提升槽(动态解析)。 // ponytail: with 内顶层 var 不写全局对象属性,with 语句本身已是稀见用例。 diff --git a/crates/oxide_emit/src/stmt/iteration/for_.rs b/crates/oxide_emit/src/stmt/iteration/for_.rs index f8200b8f..875e13d7 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_.rs @@ -1,5 +1,12 @@ //! for 语句 emit:`emit_for_statement` 含 init/test/update 三段与循环跳转。 +//! +//! C 风格 for 的词法头按规范建模为独立环境:init 求值前头名绑定未初始化 cell +//! (直读与捕获均抛 ReferenceError),被闭包引用或与顶层 var/函数同名的头名保留 +//! 独立 cell 贯穿循环,其余 init 后撤出覆盖回退寄存器循环。 +use std::collections::HashSet; + +use super::for_in::ForHeadEnv; use crate::{CompileCtx, Emitter}; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; @@ -42,13 +49,32 @@ impl Emitter { let end_label = ctx.next_label_id(); ctx.push_loop(end_label, update_label, crate::emit_ctx::LoopKind::Plain); let n_labeled = ctx.take_pending_loop_labels(end_label, update_label); - // 循环头声明中被嵌套函数捕获的 let/const 绑定:每迭代 fresh cell。 - let mut fresh_bindings: Vec<(String, u8)> = Vec::new(); // 循环头 let/const 声明名:update 段是 per-iteration 可变绑定(规范 // §14.7.4.4 CreatePerIterationEnvironment 用 CreateMutableBinding)—— // 规范允许 update 段写 let/const 循环变量,每迭代新建一个可变绑定; // 编译期 const 写检查对 update 段豁免,故记录全部声明名(含未被捕获者)。 let mut update_names: Vec = Vec::new(); + // 词法头独立环境:init 求值前把头名全部覆盖为未初始化 cell,使 init 内的 + // 直读与该区创建闭包的捕获命中 TDZ cell(不穿透外层同名已初始化绑定)。 + // init 后仅「被闭包引用」或「与顶层 var/函数同名」的头名保留覆盖贯穿循环 + // (体/测试/update 走 cell),其余撤出覆盖回退寄存器循环——普通 for-let + // 不付每迭代 cell 代价。循环收尾恢复捕获映射,兄弟语句仍见外层同名绑定的 + // 原 cell。 + let mut head_keep: HashSet = HashSet::new(); + let mut head_names: Option> = None; + let mut head_env: Option = None; + if let Some(init) = &fr.init { + if let Some(names) = self.collect_for_init_lexical_names(init) { + for name in &names { + if ctx.captured_bindings.contains_key(name) || self.is_global_tier_name(ctx, name) { + head_keep.insert(name.clone()); + } + } + head_env = Some(self.begin_for_head_env(names.clone(), ctx)?); + ctx.for_head_store_registers = names.iter().cloned().collect(); + head_names = Some(names); + } + } if let Some(init) = &fr.init { if let Some(expr) = init.as_expression() { self.emit_expression(expr, ctx)?; @@ -68,26 +94,9 @@ impl Emitter { let is_const = matches!(decl.kind, VariableDeclarationKind::Const); if let Some(init_expr) = &d.init { let val_reg = self.emit_expression(init_expr, ctx)?; + // 捕获头名的寄存器补写在 `emit_bind_target` 经 + // `for_head_store_registers` 完成(解构叶用各自的叶值,不用整头 RHS)。 self.emit_binding_pattern(&d.id, val_reg, decl.kind, is_const, false, ctx)?; - // 被捕获绑定:fresh 从寄存器读当前值,须把 init 值写入寄存器槽。 - // emit_binding_pattern 对捕获绑定只建 cell,不写寄存器。 - let mut names = Vec::new(); - self.collect_pattern_binding_names(&d.id, &mut names); - for name in &names { - if ctx.captured_bindings.contains_key(name) { - if let Some(reg) = ctx.scopes.symbols.lookup_any(name) { - // 全局不可写内置的捕获写被声明路径拦截,同步写一并跳过。 - if !ctx.targets_readonly_builtin(name, reg) { - ctx.inst(Inst::new( - OpCode::STORE_VAR, - Operand::Reg(reg), - Operand::Reg(val_reg), - Operand::None, - )); - } - } - } - } } else if let BindingPattern::BindingIdentifier(bi) = &d.id { let idx = ctx.add_constant(Constant::Undefined); let tmp = ctx.alloc_reg(); @@ -112,24 +121,49 @@ impl Emitter { Operand::None, )); } + // 词法头无初始化声明:覆盖 cell 须置为已初始化(undefined), + // 否则后续 init 表达式内创建、捕获该头名的闭包读时误报 TDZ。 + if !matches!(decl.kind, VariableDeclarationKind::Var) { + if let Some(&cell_idx) = ctx.captured_bindings.get(bi.name.as_str()) { + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(tmp), + Operand::Imm(cell_idx as u16), + Operand::None, + )); + } + } ctx.init_var(bi.name.as_str()); } - // 记录 let/const 循环头声明名(update 段写豁免所需)与被捕获的绑定 - // (解构 pattern 递归收集绑定名,被捕获者每迭代 fresh cell)。 + // 记录 let/const 循环头声明名(update 段写豁免所需);fresh cell 判定 + // 在 init 后按保留集统一重算(见下方)。 if !matches!(decl.kind, VariableDeclarationKind::Var) { let mut names = Vec::new(); self.collect_pattern_binding_names(&d.id, &mut names); for name in &names { update_names.push(name.clone()); - if let Some(&cell_idx) = ctx.captured_bindings.get(name) { - fresh_bindings.push((name.clone(), cell_idx)); - } } } } ctx.pending_for_head_names = saved_pending; } } + ctx.for_head_store_registers.clear(); + // 撤出非保留头名的覆盖:普通 for-let 回退寄存器循环,只有被闭包引用或与顶层 + // var/函数同名的头名保留独立 cell 贯穿循环。 + for name in head_names.iter().flatten() { + if !head_keep.contains(name) { + ctx.captured_bindings.remove(name); + } + } + // 每迭代 fresh cell 的绑定:保留覆盖者(仍含 cell 下标);已撤出覆盖的头名 + // 走寄存器,不含在内。 + let mut fresh_bindings: Vec<(String, u8)> = Vec::new(); + for name in &update_names { + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { + fresh_bindings.push((name.clone(), cell_idx)); + } + } ctx.labels.set_label_pos(start_label, ctx.insts.len()); // 每迭代 fresh cell:被捕获的 let/const 循环变量在迭代开始拷贝当前值到新 cell, // 本迭代闭包捕获新 cell(规范 CreatePerIterationEnvironment)。 @@ -159,6 +193,11 @@ impl Emitter { self.emit_expression(update, ctx)?; ctx.register_update_names = prev; } + // 恢复捕获映射:循环后的兄弟语句仍见外层同名绑定的原 cell(词法头环境 + // 作用是语句局部的)。 + if let Some(env) = head_env { + self.restore_for_head_env(env, ctx); + } ctx.inst(Inst::jmp(start_label)); ctx.labels.set_label_pos(end_label, ctx.insts.len()); ctx.pop_label_scopes(n_labeled); diff --git a/crates/oxide_emit/src/stmt/iteration/for_in.rs b/crates/oxide_emit/src/stmt/iteration/for_in.rs index 2232ce0d..06a31f84 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_in.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_in.rs @@ -11,7 +11,7 @@ use crate::{CompileCtx, Emitter}; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; -use oxide_parser::{ForStatementLeft, Statement, VariableDeclarationKind}; +use oxide_parser::{ForStatementInit, ForStatementLeft, Statement, VariableDeclarationKind}; /// for-in/for-of 词法头的捕获映射覆盖:每头名一条记录,依次为名字、旧条目 /// (无旧条目为 None)、右值区 TDZ cell、体区 fresh cell(切换后填入)。 @@ -22,6 +22,8 @@ pub(crate) struct ForHeadEnv { impl Emitter { /// 收集 for-in/for-of 词法声明头(let/const/using,含解构 pattern 叶)的 /// 头名(排序保证稳定);var 头与赋值头无 TDZ 环境,返回 None。 + /// C 风格 for 的 init 布尔头同用 `ForHeadEnv`,其头名收集见 + /// [`Emitter::collect_for_init_lexical_names`]。 pub(crate) fn collect_for_head_lexical_names(&self, left: &ForStatementLeft) -> Option> { let ForStatementLeft::VariableDeclaration(decl) = left else { return None; @@ -38,6 +40,27 @@ impl Emitter { Some(v) } + /// 收集 C 风格 for 头词法声明(let/const,含解构 pattern 叶)的头名(排序 + /// 保证稳定);var 头与表达式头无 TDZ 环境,返回 None。 + /// + /// # 副作用 + /// - 无;纯 AST 收集。 + pub(crate) fn collect_for_init_lexical_names(&self, init: &ForStatementInit) -> Option> { + let ForStatementInit::VariableDeclaration(decl) = init else { + return None; + }; + if matches!(decl.kind, VariableDeclarationKind::Var) { + return None; + } + let mut names = HashSet::new(); + for d in &decl.declarations { + collect_binding_pattern_names(&d.id, &mut names); + } + let mut v: Vec = names.into_iter().collect(); + v.sort(); + Some(v) + } + /// 把词法头名覆盖到捕获映射(右值区阶段):保存旧条目、分配 TDZ cell 并 /// 发射未初始化 MAKE_CELL、头名映射 TDZ cell。右值区内对头名的直读与 /// 该区内创建闭包的捕获均命中未初始化 cell(读抛 ReferenceError)。 diff --git a/crates/oxide_vm/tests/for_head_lex_tests.rs b/crates/oxide_vm/tests/for_head_lex_tests.rs new file mode 100644 index 00000000..4ff41041 --- /dev/null +++ b/crates/oxide_vm/tests/for_head_lex_tests.rs @@ -0,0 +1,207 @@ +//! C 风格 for 词法头独立环境钉:头名与同函数外层绑定同名时的 cell 覆盖碰撞、头名 +//! 与顶层 var/函数同名时的捕获剔除、头初始化表达式与无 init 声明的 TDZ 建模缺失 +//! 三面的引擎侧形态锁。 +//! +//! 完成值一律收敛为布尔(JsValue 的 Display 只暴露 number/bool,不暴露字符串内容), +//! 字符串比较在引擎内完成。 + +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}"), + } +} + +// ── for 头 let/const 与同函数外层同名绑定的 cell 覆盖碰撞 ── + +#[test] +fn c_for_let_head_same_name_as_outer_let_keeps_outer_closure() { + // 头名与外层 let 同名且外层被闭包捕获:头 MAKE_CELL 不得原地覆写外层 cell, + // 头环境结束后外层闭包仍读外层值。 + assert_eq!( + eval("let x = \"out\"; let p = () => x; for (let x = \"in\"; false;) {} p() === \"out\""), + "true", + "for-let head must not clobber the same-named outer captured cell" + ); +} + +#[test] +fn c_for_let_head_same_name_as_function_scope_let_keeps_outer_closure() { + // 函数域同名外层 let 的孪生形:碰撞面不限于脚本顶层。 + assert_eq!( + eval("function f(){ let x = \"out\"; let p = () => x; for (let x = \"in\"; false;) {} return p(); } f() === \"out\""), + "true", + "function-scope outer let closure keeps its cell across the for head" + ); +} + +#[test] +fn c_for_let_head_same_name_as_outer_var_keeps_outer_closure() { + // 外层 var 同名:头绑定独立 cell,闭包捕获的外层 var 槽值不变。 + assert_eq!( + eval( + "function f(){ var x = \"out\"; let p = () => x; for (let x = \"in\"; false;) {} return p(); } f() === \"out\"" + ), + "true", + "outer var closure keeps its value when a for-let head shadows it" + ); +} + +#[test] +fn c_for_const_head_same_name_as_outer_let_keeps_outer_closure() { + // const 头同形:不可变绑定的声明路径同样建独立 cell。 + assert_eq!( + eval("let x = \"out\"; let p = () => x; for (const x = \"in\"; false;) {} p() === \"out\""), + "true", + "for-const head builds its own cell and leaves the outer captured cell intact" + ); +} + +// ── 四位置头名可见 + 循环后恢复外层环境(scope-head-lex-open/close)── + +#[test] +fn c_for_head_lex_env_visible_in_all_four_positions_then_restored() { + // init/test/body/update 四位置的闭包都读头绑定('inside'),循环前后外层读 + // 仍读外层绑定('outside')——对应 scope-head-lex-open/close 两文件断言。 + assert_eq!( + eval( + "let x = 'outside'; var probeBefore = function(){ return x; }; \ + var probeDecl, probeTest, probeIncr, probeBody; var run = true; \ + for (let x = 'inside', _ = probeDecl = function(){ return x; }; \ + run && (probeTest = function(){ return x; }); \ + probeIncr = function(){ return x; }) \ + probeBody = function(){ return x; }, run = false; \ + probeBefore() === 'outside' && probeDecl() === 'inside' && probeTest() === 'inside' \ + && probeBody() === 'inside' && probeIncr() === 'inside' && x === 'outside'" + ), + "true", + "head lexical environment is visible in init/test/body/update and removed after the loop" + ); +} + +// ── 头名与顶层 var/函数同名:顶层剔除后体/测试闭包须捕获头绑定 ── + +#[test] +fn c_for_let_head_same_name_as_top_var_body_closure_reads_head() { + assert_eq!( + eval("var x = \"top\"; var p; for (let x = 0; x < 2; x++) { p = () => x; } p() === 1"), + "true", + "body closure reads the per-iteration let head, not the same-named top-level var" + ); +} + +#[test] +fn c_for_let_head_same_name_as_top_var_test_closure_reads_head() { + assert_eq!( + eval("var x = \"top\"; var p; for (let x = 0; x < 1 && (p = () => x); x++) {} p() === 0"), + "true", + "test-position closure reads the let head binding, not the top-level var" + ); +} + +#[test] +fn c_for_let_head_same_name_as_top_function_reads_head() { + assert_eq!( + eval("function x(){}; var p; for (let x = 0; x < 2; x++) { p = () => x; } typeof p() === \"number\""), + "true", + "body closure reads the let head when it shadows a top-level function declaration" + ); +} + +#[test] +fn c_for_nested_same_name_heads_capture_inner_head() { + assert_eq!( + eval( + "var x = \"top\"; var p; for (let x = 0; x < 1; x++) { for (let x = 0; x < 2; x++) { p = () => x; } } p() === 1" + ), + "true", + "inner head shadows the outer head; inner closure reads the inner per-iteration binding" + ); +} + +// ── 头初始化表达式 / 无 init 声明的 TDZ 建模 ── + +#[test] +fn c_for_let_head_self_reference_throws_reference_error() { + // 头自引用:TDZ 读抛 ReferenceError,不穿透外层同名已初始化绑定;无外层时 + // 同样抛 ReferenceError。 + assert_eq!( + eval( + "let a = 1; var r1 = 0; \ + try { for (let a = a; false;) {} } catch (e) { r1 = (e instanceof ReferenceError) ? 1 : 2; } \ + var r2 = 0; \ + try { for (let b = b; false;) {} } catch (e) { r2 = (e instanceof ReferenceError) ? 1 : 2; } \ + r1 === 1 && r2 === 1" + ), + "true", + "self-reference in the for head initializer reads the uninitialized head binding" + ); +} + +#[test] +fn c_for_second_declarator_self_reference_throws_reference_error() { + assert_eq!( + eval( + "var r = 0; \ + try { for (let x = 1, y = y; false;) {} } catch (e) { r = (e instanceof ReferenceError) ? 1 : 2; } r === 1" + ), + "true", + "second declarator reading itself in the for head throws ReferenceError" + ); +} + +#[test] +fn c_for_let_head_without_init_initializes_undefined_for_closure() { + // 无 init 的头声明:头绑定已初始化为 undefined,后续 init 内创建、捕获头名的 + // 闭包读 undefined(不得误报 TDZ)。 + assert_eq!( + eval("var p; for (let x, _ = (p = () => x); false;) {} p() === undefined"), + "true", + "a for-let head without initializer is initialized to undefined for its closures" + ); +} + +#[test] +fn c_for_let_head_self_reference_with_top_var_throws_reference_error() { + assert_eq!( + eval( + "var x = \"top\"; var r = 0; \ + try { for (let x = x; false;) {} } catch (e) { r = (e instanceof ReferenceError) ? 1 : 2; } r === 1" + ), + "true", + "for head self-reference throws even when a same-named top-level var exists" + ); +} + +// ── 绿守卫:非捕获遮蔽与解构头 ── + +#[test] +fn c_for_let_head_non_captured_shadow_restores_outer_and_destructures() { + // 非捕获头名回退寄存器循环:循环后外层 let 读回原值;解构头叶绑定寄存器 + // 写的是叶值(不是整头右值)。 + assert_eq!( + eval( + "let x = \"out\"; for (let x = \"in\"; false;) {} \ + let value; for (let [v] = [23]; ;) { value = v; break; } \ + x === \"out\" && value === 23" + ), + "true", + "non-captured shadow restores the outer binding and destructuring heads bind leaf values" + ); +}