fix: 不可写全局内置(undefined/NaN/Infinity)寄存器镜像写编译期拦截——sloppy 静默 no-op、strict 抛 TypeError

This commit is contained in:
2026-09-09 23:17:01 +08:00
parent 2457f283fe
commit b77a9322e5
9 changed files with 394 additions and 33 deletions
+23 -16
View File
@@ -64,12 +64,15 @@ impl Emitter {
for name in &names {
if ctx.captured_bindings.contains_key(name) {
if let Some(reg) = ctx.scopes.symbols.lookup_any(name) {
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(reg),
Operand::Reg(val_reg),
Operand::None,
));
// 全局不可写内置的捕获写被声明路径拦截,同步写一并跳过。
if !ctx.targets_readonly_builtin(name, reg) {
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(reg),
Operand::Reg(val_reg),
Operand::None,
));
}
}
}
}
@@ -78,21 +81,25 @@ impl Emitter {
let tmp = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(tmp), idx));
let var_reg = ctx.alloc_reg();
let target_reg = if matches!(decl.kind, VariableDeclarationKind::Var) {
// 无初始化 var 是纯声明而非赋值:绑定已预先存在(提升引用或
// 先前写入)时保留槽值,仅首次声明把 undefined 物化进槽。
let (target_reg, already_bound) = if matches!(decl.kind, VariableDeclarationKind::Var) {
match ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const) {
Ok(()) => var_reg,
Err(_) => ctx.lookup(bi.name.as_str()).unwrap_or(var_reg),
Ok(()) => (var_reg, false),
Err(_) => (ctx.lookup(bi.name.as_str()).unwrap_or(var_reg), true),
}
} else {
ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?;
var_reg
(var_reg, false)
};
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(target_reg),
Operand::Reg(tmp),
Operand::None,
));
if !already_bound {
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(target_reg),
Operand::Reg(tmp),
Operand::None,
));
}
ctx.init_var(bi.name.as_str());
}
// 记录 let/const 循环头声明名(update 段写豁免所需)与被捕获的绑定
+21 -8
View File
@@ -64,15 +64,28 @@ impl Emitter {
ForStatementLeft::AssignmentTargetIdentifier(id_ref) => {
let name = id_ref.name.as_str();
let var_reg = ctx.lookup_or_global(name);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
// 未声明写仅在本迭代实际产生键后才发生(for-in NEXT 之后),空集合不抛。
if is_implicit && ctx.is_strict {
// 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。
self.emit_strict_undeclared_write(name, ctx)?;
// 写仅在本迭代实际产生键后发生(for-in NEXT 之后),空集合不抛。
if ctx.targets_readonly_builtin(name, var_reg) {
// 全局不可写内置:sloppy 静默跳过写(槽保留入口预载原值);
// strict 在本迭代抛 TypeError(put 失败)。
if ctx.is_strict {
self.emit_throw_error("TypeError", "cannot assign to read-only property", ctx)?;
}
} else {
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(key_reg), Operand::None));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。
self.emit_strict_undeclared_write(name, ctx)?;
} else {
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(var_reg),
Operand::Reg(key_reg),
Operand::None,
));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
}
}
}
+21 -8
View File
@@ -108,15 +108,28 @@ impl Emitter {
ForStatementLeft::AssignmentTargetIdentifier(id_ref) => {
let name = id_ref.name.as_str();
let var_reg = ctx.lookup_or_global(name);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
// 未声明写仅在本迭代实际产生值后才发生(for-of NEXT 之后),空集合不抛。
if is_implicit && ctx.is_strict {
// 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。
self.emit_strict_undeclared_write(name, ctx)?;
// 写仅在本迭代实际产生值后发生(for-of NEXT 之后),空集合不抛。
if ctx.targets_readonly_builtin(name, var_reg) {
// 全局不可写内置:sloppy 静默跳过写(槽保留入口预载原值);
// strict 在本迭代抛 TypeError(put 失败)。
if ctx.is_strict {
self.emit_throw_error("TypeError", "cannot assign to read-only property", ctx)?;
}
} else {
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(val_reg), Operand::None));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。
self.emit_strict_undeclared_write(name, ctx)?;
} else {
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(var_reg),
Operand::Reg(val_reg),
Operand::None,
));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
}
}
}