fix: sloppy 未声明写隐式全局创建 + strict ReferenceError——DEFINE_GLOBAL_PROP_C 0x98

This commit is contained in:
2026-09-09 18:44:38 +08:00
parent c8bcff83f6
commit d9c2847730
14 changed files with 348 additions and 9 deletions
+35
View File
@@ -207,6 +207,11 @@ pub struct CompileCtx {
/// LOAD_GLOBAL(运行期查 global object 属性,缺失抛 ReferenceError)。
/// 按寄存器而非名字记录:块作用域同名新绑定持不同槽位,不会被误判为隐式全局。
pub(crate) implicit_global_reads: HashSet<u32>,
/// 未声明标识符写所登记的全局槽寄存器集合:`lookup_or_global` 未命中任何作用域
/// 时登记全局作用域绑定并记录其寄存器,写调用点据此补全局对象属性写(sloppy)
/// 或抛 ReferenceError(strict)。子函数 ctx 从父继承——继承绑定命中同一寄存器,
/// 补写/抛错判定跨嵌套函数一致。
pub(crate) implicit_global_writes: HashSet<u32>,
/// 函数 `length` 属性值:首个带默认值形参之前的形参数(rest 不计)。
/// emit_params_prologue 前由编译入口从 param_specs 计算。
pub(crate) function_length: u32,
@@ -332,6 +337,7 @@ impl CompileCtx {
captured_bindings: BTreeMap::new(),
upvalue_const_flags: HashSet::new(),
implicit_global_reads: HashSet::new(),
implicit_global_writes: HashSet::new(),
function_length: 0,
const_overflow: false,
with_stack: Vec::new(),
@@ -461,6 +467,9 @@ impl CompileCtx {
return reg;
}
let reg = self.alloc_reg();
// 未声明标识符写:登记全局作用域绑定,记录寄存器供写调用点补全局对象属性
// 写(sloppy)或抛 ReferenceError(strict)。
self.implicit_global_writes.insert(reg);
self.scopes.symbols.lookup_or_global(name, reg)
}
@@ -1096,6 +1105,9 @@ impl Emitter {
);
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
}
// 隐式全局写集合随继承绑定传入:父层未声明写已登记全局作用域,子层解析命中
// 继承绑定时须同样补全局对象属性写(或严格模式抛错)。
ctx.implicit_global_writes = parent_ctx.implicit_global_writes.clone();
for (name, reg) in extra_bindings {
ctx.scopes.symbols.scopes[0].bindings.insert(
(*name).to_string(),
@@ -1488,6 +1500,29 @@ impl Emitter {
}
}
/// 隐式全局写(sloppy 未声明标识符写):把值寄存器写到全局对象可写/可枚举/
/// 可配置数据属性(未解析引用上的 PutValue 语义)。全局对象由 VM 运行期从
/// session 解析,不依赖 this。
///
/// # 边界与前提
/// - 仅 `implicit_global_writes` 命中的寄存器调用(未声明标识符写)。
///
/// # 副作用
/// - 定义全局对象数据属性(可写/可枚举/可配置),属性缺失时新建。
pub(crate) fn emit_implicit_global_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
let idx = ctx.add_constant(Constant::String(name.to_string()));
ctx.inst(Inst::define_global_prop_c(Operand::Reg(val_reg), idx));
}
/// 严格模式未声明写:发射 ReferenceError 抛错指令序列(未解析引用不可 put,
/// 值无关,编译期拦截)。错误消息格式与读侧 LOAD_GLOBAL 运行期消息一致。
///
/// # 副作用
/// - 发射 THROW 指令序列,其后控制流不可达,dummy 值保持寄存器良定义。
pub(crate) fn emit_strict_undeclared_write(&self, name: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
self.emit_throw_error("ReferenceError", &format!("{name} is not defined"), ctx)
}
/// 把脚本顶层 var/function 绑定的当前值同步写入全局对象属性,使顶层声明
/// 可经 `globalThis` 反射(脚本环境记录的 var 绑定全局对象属性)。
///
+25 -6
View File
@@ -72,7 +72,15 @@ impl Emitter {
Ok(val_reg)
} else {
let var_reg = ctx.lookup_or_global(name);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明复合写:未解析引用不可 put,值无关抛 ReferenceError。
return self.emit_strict_undeclared_write(name, ctx);
}
ctx.inst(Inst::new(op, Operand::Reg(var_reg), Operand::Reg(rhs), Operand::None));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
Ok(var_reg)
}
}
@@ -368,12 +376,23 @@ impl Emitter {
let is_const = ctx.lookup_const_flag(name);
let const_flag = if is_const { 1 } else { 0 };
let var_reg = ctx.lookup_or_global(name);
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(var_reg),
Operand::Reg(val_reg),
Operand::Imm(const_flag),
));
// 隐式全局判定以寄存器集合为准:读侧(旧值解析)已登记绑定,
// 写侧二次解析命中集合而非"新登记"。
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明写:抛 ReferenceError,后续 LOAD_VAR 不可达。
let _ = self.emit_strict_undeclared_write(name, ctx)?;
} else {
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(var_reg),
Operand::Reg(val_reg),
Operand::Imm(const_flag),
));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
}
}
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
+9
View File
@@ -164,12 +164,21 @@ impl Emitter {
return;
}
let var_reg = ctx.lookup_or_global(name);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明写:发射 ReferenceError 抛错,跳过寄存器写(值无关)。
let _ = self.emit_strict_undeclared_write(name, ctx);
return;
}
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(var_reg),
Operand::Reg(val_reg),
Operand::Imm(const_flag),
));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
}
/// with 内动态写入:对象有该属性则写对象,否则回退静态写入。
@@ -599,6 +599,11 @@ impl Emitter {
Ok(if update.prefix { new_reg } else { old_reg })
} else {
let var_reg = ctx.lookup_or_global(name);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明更新写:值无关抛 ReferenceError。
return self.emit_strict_undeclared_write(name, ctx);
}
let result_reg = ctx.alloc_reg();
let op = match (update.operator, update.prefix) {
(UpdateOperator::Increment, true) => OpCode::INC_PRE,
@@ -607,6 +612,9 @@ impl Emitter {
(UpdateOperator::Decrement, false) => OpCode::DEC_POST,
};
ctx.inst(Inst::new(op, Operand::Reg(var_reg), Operand::Reg(result_reg), Operand::Reg(result_reg)));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
Ok(result_reg)
}
}
+27
View File
@@ -115,12 +115,21 @@ impl Emitter {
self.emit_identifier_tdz_guard(name, ctx)?;
self.emit_const_write_guard(name, ctx)?;
let var_reg = ctx.lookup_or_global(name);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。
self.emit_strict_undeclared_write(name, ctx)?;
return Ok(());
}
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(var_reg),
Operand::Reg(src_reg),
Operand::Imm(if ctx.lookup_const_flag(name) { 1 } else { 0 }),
));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
Ok(())
}
AssignmentTarget::ArrayAssignmentTarget(ap) => self.emit_array_assignment(ap, src_reg, ctx),
@@ -300,12 +309,21 @@ impl Emitter {
self.emit_identifier_tdz_guard(name, ctx)?;
self.emit_const_write_guard(name, ctx)?;
let var_reg = ctx.lookup_or_global(name);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。
self.emit_strict_undeclared_write(name, ctx)?;
return Ok(());
}
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(var_reg),
Operand::Reg(src_reg),
Operand::Imm(if ctx.lookup_const_flag(name) { 1 } else { 0 }),
));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
Ok(())
}
_ => Err("assignment target not supported".into()),
@@ -367,12 +385,21 @@ impl Emitter {
self.emit_identifier_tdz_guard(name, ctx)?;
self.emit_const_write_guard(name, ctx)?;
let var_reg = ctx.lookup_or_global(name);
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
if is_implicit && ctx.is_strict {
// 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。
self.emit_strict_undeclared_write(name, ctx)?;
return Ok(());
}
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(var_reg),
Operand::Reg(prop_reg),
Operand::Imm(if ctx.lookup_const_flag(name) { 1 } else { 0 }),
));
if is_implicit {
self.emit_implicit_global_write(name, var_reg, ctx);
}
}
AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => {
let (prop_reg, static_key, key_reg) =
+11 -1
View File
@@ -64,7 +64,17 @@ impl Emitter {
ForStatementLeft::AssignmentTargetIdentifier(id_ref) => {
let name = id_ref.name.as_str();
let var_reg = ctx.lookup_or_global(name);
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(key_reg), Operand::None));
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)?;
} 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);
}
}
}
_ => return Err("unsupported for-in left-hand side".into()),
}
+11 -1
View File
@@ -108,7 +108,17 @@ impl Emitter {
ForStatementLeft::AssignmentTargetIdentifier(id_ref) => {
let name = id_ref.name.as_str();
let var_reg = ctx.lookup_or_global(name);
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(val_reg), Operand::None));
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)?;
} 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);
}
}
}
ForStatementLeft::ArrayAssignmentTarget(ap) => {
self.emit_array_assignment(ap, val_reg, ctx)?;