mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 19:21:11 +08:00
fix: 未声明标识符读取抛 ReferenceError(typeof 特判返回 undefined)
This commit is contained in:
@@ -186,6 +186,11 @@ pub struct CompileCtx {
|
||||
/// 本函数从父函数捕获的 const 绑定名:子 ctx 不继承父函数作用域符号表,
|
||||
/// 捕获 const 信息随 upvalue 收集一并快照,供 const 写检查(编译期拦截)使用。
|
||||
pub(crate) upvalue_const_flags: HashSet<String>,
|
||||
/// 未声明标识符读所分配的全局槽寄存器集合:标识符首次读未命中任何作用域时,
|
||||
/// `lookup_or_builtin` 按隐式全局登记并记录其寄存器,后续读取据此发射
|
||||
/// LOAD_GLOBAL(运行期查 global object 属性,缺失抛 ReferenceError)。
|
||||
/// 按寄存器而非名字记录:块作用域同名新绑定持不同槽位,不会被误判为隐式全局。
|
||||
pub(crate) implicit_global_reads: HashSet<u32>,
|
||||
/// 函数 `length` 属性值:首个带默认值形参之前的形参数(rest 不计)。
|
||||
/// emit_params_prologue 前由编译入口从 param_specs 计算。
|
||||
pub(crate) function_length: u32,
|
||||
@@ -302,6 +307,7 @@ impl CompileCtx {
|
||||
own_bindings: HashSet::new(),
|
||||
captured_bindings: BTreeMap::new(),
|
||||
upvalue_const_flags: HashSet::new(),
|
||||
implicit_global_reads: HashSet::new(),
|
||||
function_length: 0,
|
||||
const_overflow: false,
|
||||
with_stack: Vec::new(),
|
||||
@@ -407,12 +413,18 @@ impl CompileCtx {
|
||||
pub(crate) fn lookup_or_builtin(&mut self, name: &str) -> Result<u32, String> {
|
||||
match self.scopes.symbols.lookup(name) {
|
||||
Ok(reg) => Ok(reg),
|
||||
// 未声明标识符按全局处理:`typeof X` 守卫等合法 JS 应在运行期判定
|
||||
// 未定义(读取未定义全局返回 undefined),而非编译期报错。
|
||||
// 未声明标识符按隐式全局登记:读取语义(读未声明应抛 ReferenceError)由
|
||||
// 发射端按 implicit_global_reads 择 LOAD_GLOBAL 实现;`typeof X` 守卫等
|
||||
// 合法 JS 在运行期判定未定义(typeof 特判走 LOAD_GLOBAL_TYPEOF)。
|
||||
Err(err) if err.contains("is not defined") => {
|
||||
let reg = self.alloc_reg();
|
||||
self.scopes.symbols.pre_register_global(name, reg);
|
||||
self.scopes.builtin_reg_map.push((name.to_string(), reg));
|
||||
// 内置名(预扫描阶段在空符号表上登记)不是用户未声明读,不标记——
|
||||
// 否则内置标识符读取全部改走 LOAD_GLOBAL(热路径退化 + 槽位被修剪)。
|
||||
if !Self::is_known_builtin(name) {
|
||||
self.implicit_global_reads.insert(reg);
|
||||
}
|
||||
Ok(reg)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
|
||||
@@ -45,7 +45,14 @@ impl Emitter {
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(r), Operand::Reg(var_reg), Operand::None));
|
||||
if ctx.implicit_global_reads.contains(&var_reg) {
|
||||
// 未声明标识符读:运行期查 global object 属性存在性,缺失抛
|
||||
// ReferenceError(sloppy 写先登记全局槽的读不在此集合,仍走 LOAD_VAR)。
|
||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
ctx.inst(Inst::new(OpCode::LOAD_GLOBAL, Operand::Reg(r), Operand::Const(key_idx), Operand::None));
|
||||
} else {
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(r), Operand::Reg(var_reg), Operand::None));
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,11 @@ impl Emitter {
|
||||
_ => Err("invalid delete target".into()),
|
||||
};
|
||||
}
|
||||
let arg = self.emit_expression(&un.argument, ctx)?;
|
||||
let arg = if matches!(un.operator, UnaryOperator::Typeof) {
|
||||
self.emit_typeof_operand(&un.argument, ctx)?
|
||||
} else {
|
||||
self.emit_expression(&un.argument, ctx)?
|
||||
};
|
||||
match un.operator {
|
||||
UnaryOperator::UnaryNegation => {
|
||||
ctx.inst(Inst::new(OpCode::NEG, Operand::Reg(arg), Operand::Reg(arg), Operand::None));
|
||||
@@ -239,6 +243,45 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
/// typeof 操作数求值:未声明标识符特判为 "undefined"。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 剥括号后若为标识符且不在任何作用域/闭包捕获中、非 with 动态解析,
|
||||
/// 发射 LOAD_GLOBAL_TYPEOF:运行期查 global object 属性,命中取真实值
|
||||
/// (含不在 BUILTIN_GLOBALS 名单的真实全局,如 Temporal),缺失求值
|
||||
/// undefined(IsUnresolvableReference 语义)。
|
||||
/// 2. 其余(TDZ 绑定、已声明、with 内)走正常求值:TDZ 抛 ReferenceError、
|
||||
/// with 动态回退未定义、已声明读真实值。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 未声明标识符不能走 LOAD_GLOBAL(读未声明抛 ReferenceError),故必须在此特判;
|
||||
/// - `typeof x; var x;` / `typeof x; let x;` 分别由 var 预声明(已初始化→undefined)
|
||||
/// 与 TDZ 占位(未初始化→抛)覆盖,不落本分支。
|
||||
fn emit_typeof_operand(&self, argument: &Expression, ctx: &mut CompileCtx) -> Result<u32, String> {
|
||||
let mut arg_expr = argument;
|
||||
while let Expression::ParenthesizedExpression(p) = arg_expr {
|
||||
arg_expr = &p.expression;
|
||||
}
|
||||
if let Expression::Identifier(ident) = arg_expr {
|
||||
let name = ident.name.as_str();
|
||||
let in_with_dynamic = !ctx.with_stack.is_empty() && !ctx.is_with_internal_binding(name);
|
||||
let captured =
|
||||
ctx.current_upvalue_captures.iter().any(|u| u.name == name) || ctx.captured_bindings.contains_key(name);
|
||||
if !in_with_dynamic && !captured && ctx.scopes.symbols.lookup_any_binding(name).is_none() {
|
||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_GLOBAL_TYPEOF,
|
||||
Operand::Reg(r),
|
||||
Operand::Const(key_idx),
|
||||
Operand::None,
|
||||
));
|
||||
return Ok(r);
|
||||
}
|
||||
}
|
||||
self.emit_expression(argument, ctx)
|
||||
}
|
||||
|
||||
fn emit_conditional_expression(
|
||||
&self, cond: &oxide_parser::ConditionalExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
|
||||
@@ -67,8 +67,9 @@ impl Emitter {
|
||||
} else {
|
||||
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None));
|
||||
}
|
||||
// 脚本顶层函数声明:同步写全局对象,使 globalThis 可反射函数名。
|
||||
if ctx.is_global_scope {
|
||||
// 脚本顶层(非块内)函数声明:同步写全局对象,使 globalThis 可反射函数名。
|
||||
// 块内函数声明是块级绑定,不得落全局对象(作用域隔离,块外不可见)。
|
||||
if ctx.is_global_scope && ctx.scopes.symbols.scopes.len() == 1 {
|
||||
self.emit_global_prop_write(&name, var_reg, ctx);
|
||||
}
|
||||
Ok(None)
|
||||
|
||||
Reference in New Issue
Block a user