fix: C 风格 for 词法头建立独立每迭代环境

This commit is contained in:
2026-09-17 21:21:36 +00:00
parent 9b65feb2ab
commit 1e8861e14f
5 changed files with 312 additions and 27 deletions
+6
View File
@@ -130,6 +130,11 @@ pub struct CompileCtx {
/// let/const 循环变量每迭代新分配一个 cell,update 写寄存器(不污染本迭代
/// 闭包捕获的 cell),下一迭代把这个寄存器值拷入新分配的 cell。
pub(crate) register_update_names: Vec<String>,
/// 正在发射的 C 风格 for 头词法绑定名(含解构叶):这些头名在 init 中经覆盖
/// cell 建 `MAKE_CELL`,但循环体/test/update 读的是绑定寄存器、每迭代 fresh
/// 拷贝的源也是寄存器,故 `emit_bind_target` 对集合内名字在 cell 写之外补写
/// 绑定寄存器。init 发射完毕后清空。
pub(crate) for_head_store_registers: HashSet<String>,
/// 模块编译上下文:当前模块命名空间对象寄存器(`__moduleObject` 返回值)。
pub(crate) module_ns_reg: Option<u32>,
/// 已求值依赖模块的命名空间对象寄存器(按 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(),
+10
View File
@@ -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 语句本身已是稀见用例。
+65 -26
View File
@@ -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<String> = Vec::new();
// 词法头独立环境:init 求值前把头名全部覆盖为未初始化 cell,使 init 内的
// 直读与该区创建闭包的捕获命中 TDZ cell(不穿透外层同名已初始化绑定)。
// init 后仅「被闭包引用」或「与顶层 var/函数同名」的头名保留覆盖贯穿循环
// (体/测试/update 走 cell),其余撤出覆盖回退寄存器循环——普通 for-let
// 不付每迭代 cell 代价。循环收尾恢复捕获映射,兄弟语句仍见外层同名绑定的
// 原 cell。
let mut head_keep: HashSet<String> = HashSet::new();
let mut head_names: Option<Vec<String>> = None;
let mut head_env: Option<ForHeadEnv> = 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);
+24 -1
View File
@@ -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<Vec<String>> {
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<Vec<String>> {
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<String> = names.into_iter().collect();
v.sort();
Some(v)
}
/// 把词法头名覆盖到捕获映射(右值区阶段):保存旧条目、分配 TDZ cell 并
/// 发射未初始化 MAKE_CELL、头名映射 TDZ cell。右值区内对头名的直读与
/// 该区内创建闭包的捕获均命中未初始化 cell(读抛 ReferenceError)。
+207
View File
@@ -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"
);
}