feat: 默认参数自引用与后位引用读抛 TDZ ReferenceError

This commit is contained in:
2026-10-09 23:58:14 +00:00
parent 2b18c4d264
commit dbdc5556c6
6 changed files with 157 additions and 24 deletions
+5
View File
@@ -80,6 +80,10 @@ pub struct CompileCtx {
/// 形参同名时不建外层绑定、不求值写回(规范 paramNames 守卫面)。子函数
/// ctx 为新建,不继承。
pub(crate) param_names: HashSet<String>,
/// 参数序言默认值发射期的形参名 TDZ 集:当前形参自身名(含解构叶)加其后
/// 全部形参名。发射期临时置位,每形参处理完即清空;重名形参不建集(规范
/// 变量环境面无 TDZ)。标识符静态读入口命中此集时发射运行时 ReferenceError。
pub(crate) param_tdz_names: HashSet<String>,
/// 块级函数名按 sloppy 模式下与浏览器/web 实现惯例兼容的行为建外层绑定的抑制集
/// (形参 ∪ 函数作用域树内词法声明名):抑制集内名字退化为纯块作用域;预声明期定稿,实例化与声明点写回两侧同查。
pub(crate) block_fn_suppressed: HashSet<String>,
@@ -281,6 +285,7 @@ impl CompileCtx {
current_upvalue_captures: Vec::new(),
own_bindings: HashSet::new(),
param_names: HashSet::new(),
param_tdz_names: HashSet::new(),
block_fn_suppressed: HashSet::new(),
block_fn_entry_mats: Vec::new(),
captured_bindings: BTreeMap::new(),
+14 -3
View File
@@ -16,6 +16,14 @@ impl Emitter {
/// - 活读映射仅模块顶层 ctx 填充:嵌套函数内读 import 名仍走链接期快照
/// (`current_upvalue_captures` 不在此列,闭包内不继承映射)。
fn emit_static_identifier_read(&self, name: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
// 形参名 TDZ:默认值表达式内引用当前形参与其后形参是形参环境未初始化
// 绑定,运行期抛 ReferenceError(重名形参无 TDZ,编译期集为空)。判定置于
// 读入口最前:被捕获参数在默认值发射期经 visible_cell 回退命中 CELL_GET,
// 判定后置即漏判。
if ctx.param_tdz_names.contains(name) {
return self.emit_tdz_throw(&format!("Cannot access '{name}' before initialization"), ctx);
}
// 可重赋依赖的命名/默认导入:读点直接查依赖命名空间当前值,跟随源模块重赋。
if let Some((dep_ns, exported)) = ctx.module_live_imports.get(name).cloned() {
let name_reg = self.load_string_const(&exported, ctx);
@@ -107,9 +115,12 @@ impl Emitter {
ctx.inst(Inst::jmp(end_label));
ctx.labels.set_label_pos(fallback_label, ctx.insts.len());
// 回退:upvalue / 被捕获 cell / 静态绑定 / 未定义四选一。
// 对象无该属性时才走此处,语义与静态解析一致。
if let Some((uv_idx, _)) = ctx.current_upvalue_captures.iter().enumerate().find(|(_, u)| u.name == name) {
// 回退:形参名 TDZ / upvalue / 被捕获 cell / 静态绑定 / 未定义五选一。
// 对象无该属性时才走此处,语义与静态解析一致。TDZ 判定置于回退入口最前:
// 被捕获参数经 visible_cell 回退命中 CELL_GET,判定后置即漏判。
if ctx.param_tdz_names.contains(name) {
let _ = self.emit_tdz_throw(&format!("Cannot access '{name}' before initialization"), ctx);
} else if let Some((uv_idx, _)) = ctx.current_upvalue_captures.iter().enumerate().find(|(_, u)| u.name == name) {
ctx.inst(Inst::new(
OpCode::LOAD_UPVALUE,
Operand::Reg(result_reg),
+38 -1
View File
@@ -860,7 +860,41 @@ impl Emitter {
ctx.inst(Inst::create_arguments(Operand::Reg(reg)));
}
for spec in param_specs {
// 形参名 TDZ 集:按形参序收集每形参绑定名(含解构叶),重名面不建集
// (规范变量环境面无 TDZ)。默认值发射期读路径命中此集抛 ReferenceError。
let param_bound_names: Vec<Vec<String>> = param_specs
.iter()
.map(|spec| match spec {
ParamSpec::Identifier { name, .. } | ParamSpec::Rest { name } => vec![name.clone()],
ParamSpec::Pattern { pattern, .. } => {
let mut names = HashSet::new();
collect_binding_pattern_names(pattern, &mut names);
names.into_iter().collect()
}
})
.collect();
let has_param_duplicates = {
let total: usize = param_bound_names.iter().map(|v| v.len()).sum();
let mut seen = HashSet::new();
for names in &param_bound_names {
for n in names {
seen.insert(n.clone());
}
}
seen.len() < total
};
for (idx, spec) in param_specs.iter().enumerate() {
// 循环首置 TDZ 集:当前形参自身名(含解构叶)加其后全部形参名;
// 重名面保持空集。
if !has_param_duplicates {
ctx.param_tdz_names.clear();
ctx.param_tdz_names.extend(param_bound_names[idx].iter().cloned());
for subsequent in &param_bound_names[idx + 1..] {
ctx.param_tdz_names.extend(subsequent.iter().cloned());
}
}
match spec {
ParamSpec::Pattern {
synthetic_name,
@@ -890,6 +924,9 @@ impl Emitter {
ctx.inst(Inst::create_rest_array(Operand::Reg(reg), fixed_count));
}
}
// 每形参处理完即清空,防 TDZ 集泄漏到 body 读取。
ctx.param_tdz_names.clear();
}
// 被捕获的参数也必须建 cell(MAKE_CELL):否则子函数经 lazy upvalue 路径读