fix: 块级 let/const/class 预声明 + TDZ 读取运行时抛 ReferenceError
This commit is contained in:
@@ -373,6 +373,12 @@ impl CompileCtx {
|
||||
self.scopes.symbols.declare_initialized(name, reg, kind, is_const)
|
||||
}
|
||||
|
||||
pub(crate) fn declare_predeclared(
|
||||
&mut self, name: &str, reg: u32, kind: VariableDeclarationKind, is_const: bool,
|
||||
) -> Result<(), String> {
|
||||
self.scopes.symbols.declare_predeclared(name, reg, kind, is_const)
|
||||
}
|
||||
|
||||
pub(crate) fn push_scope_with_kind(&mut self, kind: ScopeKind) {
|
||||
self.scopes.symbols.push_scope_with_kind(kind);
|
||||
}
|
||||
@@ -650,6 +656,34 @@ impl Emitter {
|
||||
Self
|
||||
}
|
||||
|
||||
/// 生成运行时抛 ReferenceError 的指令序列,返回一个未定义 dummy 寄存器
|
||||
/// 保证 THROW 后不可达控制流的寄存器良定义。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 取全局 ReferenceError 构造器并 LOAD。
|
||||
/// 2. 加载错误消息常量,`new ReferenceError(msg)` 构造错误对象。
|
||||
/// 3. THROW 抛出;尾接 dummy 值保持后续读引用有确定寄存器。
|
||||
pub(crate) fn emit_tdz_throw(&self, msg: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
|
||||
let ctor_reg = ctx.lookup_or_builtin("ReferenceError")?;
|
||||
let ctor = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(ctor), Operand::Reg(ctor_reg), Operand::None));
|
||||
let msg_reg = ctx.alloc_reg();
|
||||
let msg_idx = ctx.add_constant(Constant::String(msg.to_string()));
|
||||
ctx.inst(Inst::load_const(Operand::Reg(msg_reg), msg_idx));
|
||||
let exc_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new_expression(
|
||||
Operand::Reg(exc_reg),
|
||||
Operand::Reg(ctor),
|
||||
Operand::Reg(msg_reg),
|
||||
1,
|
||||
));
|
||||
ctx.inst(Inst::new(OpCode::THROW, Operand::Reg(exc_reg), Operand::None, Operand::None));
|
||||
let dummy = ctx.alloc_reg();
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.inst(Inst::load_const(Operand::Reg(dummy), undef_idx));
|
||||
Ok(dummy)
|
||||
}
|
||||
|
||||
/// 遍历解构 pattern 收集内嵌默认值表达式(AssignmentPattern.right)。
|
||||
fn collect_pattern_default_exprs<'a>(
|
||||
&self, pattern: &'a oxide_parser::BindingPattern<'a>, out: &mut Vec<&'a oxide_parser::Expression<'a>>,
|
||||
@@ -911,6 +945,7 @@ impl Emitter {
|
||||
reg: binding.reg,
|
||||
initialized: binding.initialized,
|
||||
is_const: binding.is_const,
|
||||
predeclared: false,
|
||||
},
|
||||
);
|
||||
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
|
||||
@@ -922,6 +957,7 @@ impl Emitter {
|
||||
reg: *reg,
|
||||
initialized: true,
|
||||
is_const: true,
|
||||
predeclared: false,
|
||||
},
|
||||
);
|
||||
inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1));
|
||||
@@ -955,6 +991,10 @@ impl Emitter {
|
||||
// 预声明 `var` 名,使首个 sub-pass 中提升的函数声明能解析其闭包引用的外层 var。
|
||||
self.predeclare_var_declarations(body_stmts, &mut ctx);
|
||||
|
||||
// 预声明 body 级 `let`/`const`/`class`(未初始化 TDZ 占位),
|
||||
// 使声明点前读取可编译为运行时 ReferenceError。
|
||||
self.predeclare_lexical_declarations(body_stmts, &mut ctx);
|
||||
|
||||
// 生成器:body 起点标记——调用时参数初始化(emit_params_prologue)结束后挂起于此,
|
||||
// 参数副作用/异常在 `g()` 调用时刻生效,首次 next() 从这继续执行 body。
|
||||
if is_generator {
|
||||
@@ -1282,6 +1322,9 @@ impl Emitter {
|
||||
// 预声明顶层 `var` 名,使首个 sub-pass 中提升的函数声明能解析外层 var。
|
||||
self.predeclare_var_declarations(&program.body, &mut ctx);
|
||||
|
||||
// 预声明顶层 `let`/`const`/`class`(未初始化 TDZ 占位)。
|
||||
self.predeclare_lexical_declarations(&program.body, &mut ctx);
|
||||
|
||||
// 闭包捕获分析(AST 级,emit 前确定)
|
||||
ctx.own_bindings = self.collect_own_binding_names(&[], &program.body);
|
||||
ctx.captured_bindings = self.collect_captured_bindings(&program.body, &[], &ctx.own_bindings);
|
||||
|
||||
@@ -36,7 +36,14 @@ impl Emitter {
|
||||
return Ok(r);
|
||||
}
|
||||
|
||||
let var_reg = ctx.lookup_or_builtin(name)?;
|
||||
let var_reg = match ctx.lookup_or_builtin(name) {
|
||||
Ok(reg) => reg,
|
||||
Err(err) if err.contains("before initialization") => {
|
||||
// TDZ:块级预声明占位后,声明点前读取编译为运行时抛 ReferenceError。
|
||||
return self.emit_tdz_throw(&err, ctx);
|
||||
}
|
||||
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));
|
||||
Ok(r)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! 支持提升语义。
|
||||
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::{Expression, Statement, VariableDeclarationKind};
|
||||
use oxide_parser::{BindingPattern, Expression, Statement, VariableDeclarationKind};
|
||||
|
||||
impl Emitter {
|
||||
/// 在临时寄存器池之前分配 builtin 槽位。
|
||||
@@ -360,4 +360,105 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 预声明当前作用域直接子语句中的 `let`/`const`/`class` 绑定(未初始化,
|
||||
/// 建立 TDZ 占位)。使声明点之前的读取在编译期可分辨为 TDZ 而非隐式全局,
|
||||
/// 声明点复用预登记槽位。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 只扫描直接子语句 + 递归 switch case(switch 不推 scope,case 内 lexical
|
||||
/// 声明属于外层作用域);不递归块/if/for/while body(嵌套块自预声明,
|
||||
/// 单语句 body 不含 lexical 声明)。
|
||||
/// - 跳过 for 头声明(循环作用域由 for 分支内联 declare)。
|
||||
pub(crate) fn predeclare_lexical_declarations(&self, statements: &[Statement], ctx: &mut CompileCtx) {
|
||||
for statement in statements {
|
||||
match statement {
|
||||
Statement::VariableDeclaration(decl) => {
|
||||
if matches!(decl.kind, VariableDeclarationKind::Var) {
|
||||
continue;
|
||||
}
|
||||
let is_const = matches!(decl.kind, VariableDeclarationKind::Const);
|
||||
for d in &decl.declarations {
|
||||
self.predeclare_lexical_pattern(&d.id, is_const, ctx);
|
||||
}
|
||||
}
|
||||
Statement::ClassDeclaration(cd) => {
|
||||
if let Some(id) = &cd.id {
|
||||
let reg = ctx.alloc_reg();
|
||||
let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true);
|
||||
}
|
||||
}
|
||||
Statement::SwitchStatement(sw) => {
|
||||
for case in &sw.cases {
|
||||
for s in &case.consequent {
|
||||
self.predeclare_lexical_stmt(s, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 预声明单个语句中的 lexical 声明(供 switch case 递归;`let`/`const`/`class` 分支)。
|
||||
fn predeclare_lexical_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
||||
match stmt {
|
||||
Statement::VariableDeclaration(decl) => {
|
||||
if matches!(decl.kind, VariableDeclarationKind::Var) {
|
||||
return;
|
||||
}
|
||||
let is_const = matches!(decl.kind, VariableDeclarationKind::Const);
|
||||
for d in &decl.declarations {
|
||||
self.predeclare_lexical_pattern(&d.id, is_const, ctx);
|
||||
}
|
||||
}
|
||||
Statement::ClassDeclaration(cd) => {
|
||||
if let Some(id) = &cd.id {
|
||||
let reg = ctx.alloc_reg();
|
||||
let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// 递归预声明绑定 pattern 内的全部标识符(含数组/对象/默认值解构)。
|
||||
fn predeclare_lexical_pattern(&self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx) {
|
||||
match pattern {
|
||||
BindingPattern::BindingIdentifier(bi) => {
|
||||
let reg = ctx.alloc_reg();
|
||||
let _ = ctx.declare_predeclared(
|
||||
bi.name.as_str(),
|
||||
reg,
|
||||
if is_const {
|
||||
VariableDeclarationKind::Const
|
||||
} else {
|
||||
VariableDeclarationKind::Let
|
||||
},
|
||||
is_const,
|
||||
);
|
||||
}
|
||||
BindingPattern::ArrayPattern(ap) => {
|
||||
for elem in &ap.elements {
|
||||
if let Some(e) = elem {
|
||||
self.predeclare_lexical_pattern(e, is_const, ctx);
|
||||
}
|
||||
}
|
||||
if let Some(rest) = &ap.rest {
|
||||
self.predeclare_lexical_pattern(&rest.argument, is_const, ctx);
|
||||
}
|
||||
}
|
||||
BindingPattern::ObjectPattern(op) => {
|
||||
for prop in &op.properties {
|
||||
self.predeclare_lexical_pattern(&prop.value, is_const, ctx);
|
||||
}
|
||||
if let Some(rest) = &op.rest {
|
||||
self.predeclare_lexical_pattern(&rest.argument, is_const, ctx);
|
||||
}
|
||||
}
|
||||
BindingPattern::AssignmentPattern(ap) => {
|
||||
self.predeclare_lexical_pattern(&ap.left, is_const, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,10 +63,16 @@ impl Emitter {
|
||||
ctx.declare(name, var_reg, kind, is_const)?;
|
||||
var_reg
|
||||
}
|
||||
} else {
|
||||
// let/const:本块预声明(TDZ 占位)则复用预登记槽位,不重复 declare;
|
||||
// 否则(for 头等未预声明路径)按原逻辑声明。
|
||||
if let Some(reg) = ctx.scopes.symbols.consume_predeclared_slot(name) {
|
||||
reg
|
||||
} else {
|
||||
let var_reg = ctx.alloc_reg();
|
||||
ctx.declare(name, var_reg, kind, is_const)?;
|
||||
var_reg
|
||||
}
|
||||
};
|
||||
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
||||
let op = if fresh_cell { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL };
|
||||
|
||||
@@ -9,6 +9,7 @@ impl Emitter {
|
||||
return Ok(None);
|
||||
};
|
||||
ctx.push_scope();
|
||||
self.predeclare_lexical_declarations(&block.body, ctx);
|
||||
let mut r = None;
|
||||
for s in &block.body {
|
||||
if let Some(rr) = self.emit_statement(s, ctx)? {
|
||||
|
||||
@@ -18,8 +18,14 @@ impl Emitter {
|
||||
.as_ref()
|
||||
.map(|id| id.name.to_string())
|
||||
.ok_or_else(|| "ClassDeclaration without name".to_string())?;
|
||||
let var_reg = ctx.alloc_reg();
|
||||
ctx.declare(&name, var_reg, VariableDeclarationKind::Let, false)?;
|
||||
let var_reg = match ctx.scopes.symbols.consume_predeclared_slot(&name) {
|
||||
Some(reg) => reg,
|
||||
None => {
|
||||
let reg = ctx.alloc_reg();
|
||||
ctx.declare(&name, reg, VariableDeclarationKind::Let, false)?;
|
||||
reg
|
||||
}
|
||||
};
|
||||
ctx.init_var(&name);
|
||||
let ctor_reg = self.emit_class(class, ctx)?;
|
||||
if let Some(&cell_idx) = ctx.captured_bindings.get(&name) {
|
||||
|
||||
@@ -47,7 +47,16 @@ impl Emitter {
|
||||
Err(_) => ctx.lookup(bi.name.as_str()).unwrap_or(var_reg),
|
||||
}
|
||||
} else {
|
||||
ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?;
|
||||
// let/const 无初始化器:复用本块预声明槽位,否则新声明
|
||||
// (for 头等未预声明路径)。
|
||||
let var_reg =
|
||||
if let Some(reg) = ctx.scopes.symbols.consume_predeclared_slot(bi.name.as_str()) {
|
||||
reg
|
||||
} else {
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.declare(bi.name.as_str(), r, decl.kind, is_const)?;
|
||||
r
|
||||
};
|
||||
var_reg
|
||||
};
|
||||
if let Some(&cell_idx) = ctx.captured_bindings.get(bi.name.as_str()) {
|
||||
|
||||
@@ -29,11 +29,16 @@ impl Emitter {
|
||||
ctx.push_open_catch_handler();
|
||||
}
|
||||
let mut last_try_result: Option<u32> = None;
|
||||
// try block 是独立块作用域:lexical 声明(let/const/class)限定于 try 内,
|
||||
// 与外层同名绑定互不干扰;块级预声明使声明点前读取编译为 TDZ 抛错。
|
||||
ctx.push_scope();
|
||||
self.predeclare_lexical_declarations(&ts.block.body, ctx);
|
||||
for s in &ts.block.body {
|
||||
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||
last_try_result = Some(r);
|
||||
}
|
||||
}
|
||||
ctx.pop_scope();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_VAR,
|
||||
Operand::Reg(result_reg),
|
||||
@@ -58,6 +63,7 @@ impl Emitter {
|
||||
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(src_reg), Operand::None, Operand::None));
|
||||
self.emit_binding_pattern(¶m.pattern, src_reg, VariableDeclarationKind::Let, false, false, ctx)?;
|
||||
}
|
||||
self.predeclare_lexical_declarations(&catch.body.body, ctx);
|
||||
let mut last_catch_result: Option<u32> = None;
|
||||
for s in &catch.body.body {
|
||||
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||
@@ -75,11 +81,15 @@ impl Emitter {
|
||||
if has_finally {
|
||||
ctx.labels.set_label_pos(finally_label, ctx.insts.len());
|
||||
let mut last_finally_result: Option<u32> = None;
|
||||
// finally block 同为独立块作用域,lexical 声明互不泄漏。
|
||||
ctx.push_scope();
|
||||
self.predeclare_lexical_declarations(&ts.finalizer.as_ref().unwrap().body, ctx);
|
||||
for s in &ts.finalizer.as_ref().unwrap().body {
|
||||
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||
last_finally_result = Some(r);
|
||||
}
|
||||
}
|
||||
ctx.pop_scope();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_VAR,
|
||||
Operand::Reg(result_reg),
|
||||
|
||||
@@ -23,6 +23,9 @@ pub(crate) struct Binding {
|
||||
pub(crate) reg: u32,
|
||||
pub(crate) initialized: bool,
|
||||
pub(crate) is_const: bool,
|
||||
/// 由块/函数级预声明(TDZ 占位)创建,声明点据此复用槽位。
|
||||
/// 非预声明的同名绑定(如同 scope 的参数/var)不计,避免误复用。
|
||||
pub(crate) predeclared: bool,
|
||||
}
|
||||
|
||||
/// 作用域符号表:名字 → 寄存器号/初始化状态/const 标志。
|
||||
@@ -97,6 +100,7 @@ impl SymbolTable {
|
||||
reg,
|
||||
initialized: false,
|
||||
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
|
||||
predeclared: false,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
@@ -131,6 +135,19 @@ impl SymbolTable {
|
||||
None
|
||||
}
|
||||
|
||||
/// 消费当前(最内层)作用域中由预声明创建的绑定槽:存在且 `predeclared`
|
||||
/// 时返回其寄存器并清除标志(声明点复用后不再视作预声明);否则返回 None。
|
||||
/// 同 scope 的非预声明绑定(参数/var/未推 scope 的 try 内声明)不计,避免误复用。
|
||||
pub(crate) fn consume_predeclared_slot(&mut self, name: &str) -> Option<u32> {
|
||||
let scope = self.scopes.last_mut()?;
|
||||
let binding = scope.bindings.get_mut(name)?;
|
||||
if !binding.predeclared {
|
||||
return None;
|
||||
}
|
||||
binding.predeclared = false;
|
||||
Some(binding.reg)
|
||||
}
|
||||
|
||||
/// 查找或视为全局:未命中时以 `reg_for_new` 在全局作用域登记并返回(隐式全局)。
|
||||
pub fn lookup_or_global(&mut self, name: &str, reg_for_new: u32) -> u32 {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
@@ -144,6 +161,7 @@ impl SymbolTable {
|
||||
reg: reg_for_new,
|
||||
initialized: true,
|
||||
is_const: false,
|
||||
predeclared: false,
|
||||
},
|
||||
);
|
||||
reg_for_new
|
||||
@@ -183,6 +201,7 @@ impl SymbolTable {
|
||||
reg,
|
||||
initialized: true,
|
||||
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
|
||||
predeclared: false,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
@@ -194,9 +213,36 @@ impl SymbolTable {
|
||||
reg,
|
||||
initialized: true,
|
||||
is_const: false,
|
||||
predeclared: false,
|
||||
});
|
||||
}
|
||||
|
||||
/// 声明未初始化绑定并标记为预声明(TDZ 占位),供声明点复用槽位。
|
||||
/// 与 `declare` 同作用域规则,仅 `predeclared` 标志不同。
|
||||
pub(crate) fn declare_predeclared(
|
||||
&mut self, name: &str, reg: u32, kind: VariableDeclarationKind, is_const: bool,
|
||||
) -> Result<(), String> {
|
||||
let target_idx = if matches!(kind, VariableDeclarationKind::Var) {
|
||||
self.find_var_target_scope()
|
||||
} else {
|
||||
self.scopes.len() - 1
|
||||
};
|
||||
let target = &mut self.scopes[target_idx];
|
||||
if target.bindings.contains_key(name) {
|
||||
return Err(format!("Identifier '{name}' has already been declared"));
|
||||
}
|
||||
target.bindings.insert(
|
||||
name.to_string(),
|
||||
Binding {
|
||||
reg,
|
||||
initialized: false,
|
||||
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
|
||||
predeclared: true,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 从内到外将同名绑定标记为已初始化(var 提升后初始化阶段使用)。
|
||||
pub fn init_var(&mut self, name: &str) {
|
||||
for scope in self.scopes.iter_mut().rev() {
|
||||
|
||||
Reference in New Issue
Block a user