mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 11:41:13 +08:00
fix: sloppy 未声明写隐式全局创建 + strict ReferenceError——DEFINE_GLOBAL_PROP_C 0x98
This commit is contained in:
@@ -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 绑定全局对象属性)。
|
||||
///
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
Reference in New Issue
Block a user