fix: 块级异步与生成器函数声明不再泄漏全局

This commit is contained in:
2026-09-17 22:08:56 +00:00
parent ed3e8cf372
commit e4b17d2ecf
4 changed files with 182 additions and 7 deletions
+7 -4
View File
@@ -1,5 +1,5 @@
//! emit 前置 pass,块级函数名收集:web-compat 外层 var 绑定实例化支撑——
//! 语句树内块级函数声明名源序去重收集、词法声明名抑制集(块函数名与词法名
//! 语句树内块级普通函数声明名源序去重收集、词法声明名抑制集(块函数名与词法名
//! 同名时退化为纯块作用域)、形参名 ∪ 词法名抑制集构建。
use std::collections::HashSet;
@@ -9,10 +9,13 @@ use crate::Emitter;
use oxide_parser::{Declaration, Statement, VariableDeclarationKind};
impl Emitter {
/// 递归收集语句树内块级函数声明名(源序去重),供 sloppy 模式浏览器兼容行为
/// (块级函数声明建外层 var 绑定)下的外层 var 绑定实例化。
/// 递归收集语句树内块级普通函数声明名(源序去重),供 sloppy 模式浏览器兼容
/// 行为(Annex B.3.3 为块级普通函数声明建外层 var 绑定)下的外层 var 绑定实例化。
///
/// # 边界与前提
/// - 只收普通 `FunctionDeclaration`:Annex B.3.3 的 web-compat 仅改写普通函数
/// 声明,async 函数、生成器与 async 生成器是各自独立的块级词法绑定,块外
/// 不可见,不入名集。
/// - 调用点直接子级(函数体/程序顶层直接子句)是提升 var 声明,非块级
/// 函数,不收集;进入嵌套语句(块/if/循环/switch case/try 各区)后的
/// 函数声明才是块级。
@@ -29,7 +32,7 @@ impl Emitter {
for statement in statements {
match statement {
Statement::FunctionDeclaration(f) => {
if block_level {
if block_level && !f.generator && !f.r#async {
if let Some(id) = &f.id {
let name = id.name.to_string();
if !names.contains(&name) {
@@ -49,17 +49,20 @@ impl Emitter {
} else {
self.materialize_function_declaration(fd, &name, ctx)?
};
// 块内函数声明求值后按三种情形处理:
//
// sloppy 模式下按浏览器/web 实现惯例为块级函数声明建外层 var 绑定并写回:
// sloppy 模式下按 Annex B.3.3 为块级普通函数声明建外层 var 绑定并写回:
// 预声明期为该名实例化的函数作用域 var 绑定在此写入块槽位里的函数对象,
// 求值一次写回一次(循环体内每迭代覆写头绑定)。
//
// async 函数、生成器与 async 生成器是块级词法绑定,不在 Annex B.3.3 改写
// 范围,块外不可见,不写回外层 var。
//
// 形参或函数作用域树内同名词法声明(抑制集)不写回:该名被同名声明遮蔽。
//
// 顶层不可写内置名不写回:sloppy 下对其赋值(put)永不成功,跳过与执行等价;
// 顶层情形另补全局对象属性写(与 for-in 循环头的顶层写同形)。
if !ctx.is_strict
&& !fd.generator
&& !fd.r#async
&& ctx.scopes.symbols.scopes.len() > 1
&& !ctx.block_fn_suppressed.contains(&name)
&& !(ctx.is_global_scope && CompileCtx::is_non_writable_global_builtin(&name))
@@ -1,5 +1,7 @@
//! try/catch/finally 语句 emit:`emit_try_statement` 生成 TRY_BEGIN 与处理入口。
use std::collections::HashMap;
use crate::{CompileCtx, Emitter};
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
@@ -32,13 +34,21 @@ impl Emitter {
// try block 是独立块作用域:lexical 声明(let/const/class)限定于 try 内,
// 与外层同名绑定互不干扰;块级预声明使声明点前读取编译为 TDZ 抛错。
ctx.push_scope();
// 函数预声明先于 lexical:同名 let 命中已有函数绑定报重复声明错。
self.predeclare_block_function_declarations(&ts.block.body, ctx, false);
// try 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。
let _ = self.predeclare_lexical_declarations(&ts.block.body, ctx, false);
// 块级函数声明的块入口初始化:声明点前读命中函数对象,声明点复用入口槽。
ctx.block_fn_entry_mats.push(HashMap::new());
for s in &ts.block.body {
self.emit_block_fn_entry_init_stmt(s, ctx)?;
}
for s in &ts.block.body {
if let Some(r) = self.emit_statement(s, ctx)? {
last_try_result = Some(r);
}
}
ctx.block_fn_entry_mats.pop();
ctx.pop_scope();
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
@@ -64,14 +74,21 @@ impl Emitter {
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(src_reg), Operand::None, Operand::None));
self.emit_binding_pattern(&param.pattern, src_reg, VariableDeclarationKind::Let, false, false, ctx)?;
}
// catch 块函数预声明先于 lexical,块入口物化与 try/普通块同口径。
self.predeclare_block_function_declarations(&catch.body.body, ctx, false);
// catch 块 lexical 声明同为局部绑定:不做受限全局名检查。
let _ = self.predeclare_lexical_declarations(&catch.body.body, ctx, false);
ctx.block_fn_entry_mats.push(HashMap::new());
for s in &catch.body.body {
self.emit_block_fn_entry_init_stmt(s, ctx)?;
}
let mut last_catch_result: Option<u32> = None;
for s in &catch.body.body {
if let Some(r) = self.emit_statement(s, ctx)? {
last_catch_result = Some(r);
}
}
ctx.block_fn_entry_mats.pop();
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(result_reg),
@@ -91,11 +108,18 @@ impl Emitter {
// try/catch 收敛值保持 try 体或 catch 体值,finally 值不渗入。
// finally block 同为独立块作用域,lexical 声明互不泄漏。
ctx.push_scope();
// finally 块函数预声明先于 lexical,块入口物化与 try/普通块同口径。
self.predeclare_block_function_declarations(&ts.finalizer.as_ref().unwrap().body, ctx, false);
// finally 块 lexical 声明同为局部绑定:不做受限全局名检查。
let _ = self.predeclare_lexical_declarations(&ts.finalizer.as_ref().unwrap().body, ctx, false);
ctx.block_fn_entry_mats.push(HashMap::new());
for s in &ts.finalizer.as_ref().unwrap().body {
self.emit_block_fn_entry_init_stmt(s, ctx)?;
}
for s in &ts.finalizer.as_ref().unwrap().body {
self.emit_statement(s, ctx)?;
}
ctx.block_fn_entry_mats.pop();
ctx.pop_scope();
ctx.inst(Inst::new(OpCode::TRY_FINALLY_END, Operand::None, Operand::None, Operand::None));
ctx.pop_open_try_handler();