fix: 块函数名绑定可写化并收窄 catch 参数抑制面

This commit is contained in:
2026-10-16 18:41:55 +00:00
parent 8a9d09aa5d
commit 50d05bd4a0
12 changed files with 88 additions and 54 deletions
Generated
+21 -21
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1258,7 +1258,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1272,7 +1272,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1288,7 +1288,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1300,7 +1300,7 @@ dependencies = [
[[package]]
name = "oxide_fold"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"num-bigint",
"oxide_bytecode",
@@ -1312,7 +1312,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"bumpalo",
"dashmap",
@@ -1335,7 +1335,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1347,7 +1347,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"tracing",
"tracing-appender",
@@ -1356,11 +1356,11 @@ dependencies = [
[[package]]
name = "oxide_net"
version = "0.19.36"
version = "0.19.37"
[[package]]
name = "oxide_parser"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1375,7 +1375,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1391,7 +1391,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"num-bigint",
"num-traits",
@@ -1404,7 +1404,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1424,7 +1424,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"bumpalo",
"dashmap",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.19.36"
version = "0.19.37"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -25,7 +25,7 @@ members = [
]
[workspace.package]
version = "0.19.36"
version = "0.19.37"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+19 -11
View File
@@ -44,8 +44,8 @@ pub(crate) struct FnScopeNames {
/// 块级普通函数声明名(源序去重;直接子级是提升 var 声明不纳,async/生成器
/// 是块级词法绑定不纳,不递归嵌套函数体)。
pub(crate) block_function_names: Vec<String>,
/// 块级函数名的外层 var 绑定抑制集(形参名 ∪ 树内词法声明名;命中抑制的名字
/// 不建外层 var 绑定、不求值写回)。
/// 块级函数名的外层 var 绑定抑制集(形参名 ∪ 树内词法声明名,绑定标识符 catch
/// 参数除外;命中抑制的名字不建外层 var 绑定、不求值写回)。
pub(crate) block_fn_suppressed: HashSet<String>,
}
@@ -112,6 +112,8 @@ fn collect_fn_scope_names_impl(stmts: &[Statement], out: &mut FnScopeNames, top:
}
}
// 抑制面:词法名与块函数名同名时退化为纯块作用域(各层均收集)。
// 规范口径:var 声明与函数体任一层词法声明同名即产生早期错误
// (LexicallyDeclaredNames 递归收集嵌套块),故嵌套 let/const 亦入集。
if !is_var {
for d in &vd.declarations {
collect_binding_pattern_names(&d.id, &mut out.block_fn_suppressed);
@@ -141,7 +143,7 @@ fn collect_fn_scope_names_impl(stmts: &[Statement], out: &mut FnScopeNames, top:
let name = id.name.to_string();
// own 面:类名是本作用域 const 绑定。
out.own_binding_names.insert(name.clone());
// 抑制面:类名是词法声明,与块函数名同名时退化纯块作用域。
// 抑制面:类名是词法声明,与块函数名同名时退化纯块作用域(各层均收集)。
out.block_fn_suppressed.insert(name.clone());
if top {
// 直接子词法面:顶层类是顶层词法绑定。
@@ -269,7 +271,7 @@ fn collect_fn_scope_names_impl(stmts: &[Statement], out: &mut FnScopeNames, top:
collect_binding_pattern_names(&d.id, &mut out.block_level_decl_names);
}
}
// 抑制面:for 头词法名与块函数名同名时退化纯块作用域。
// 抑制面:for 头词法名与块函数名同名时退化纯块作用域(各层均收集)。
if !is_var {
for d in &vd.declarations {
collect_binding_pattern_names(&d.id, &mut out.block_fn_suppressed);
@@ -288,7 +290,7 @@ fn collect_fn_scope_names_impl(stmts: &[Statement], out: &mut FnScopeNames, top:
collect_for_left_decl_names(&fi.left, &mut out.own_binding_names);
if !is_var {
collect_for_left_decl_names(&fi.left, &mut out.block_level_decl_names);
// 抑制面:for 头词法名与块函数名同名时退化纯块作用域。
// 抑制面:for 头词法名与块函数名同名时退化纯块作用域(各层均收集)。
collect_for_left_decl_names(&fi.left, &mut out.block_fn_suppressed);
}
}
@@ -303,7 +305,7 @@ fn collect_fn_scope_names_impl(stmts: &[Statement], out: &mut FnScopeNames, top:
collect_for_left_decl_names(&fo.left, &mut out.own_binding_names);
if !is_var {
collect_for_left_decl_names(&fo.left, &mut out.block_level_decl_names);
// 抑制面:for 头词法名与块函数名同名时退化纯块作用域。
// 抑制面:for 头词法名与块函数名同名时退化纯块作用域(各层均收集)。
collect_for_left_decl_names(&fo.left, &mut out.block_fn_suppressed);
}
}
@@ -319,12 +321,17 @@ fn collect_fn_scope_names_impl(stmts: &[Statement], out: &mut FnScopeNames, top:
// 与旧收集器逐位一致(各集合面不受访问序影响)。
collect_fn_scope_names_impl(&ts.block.body, out, false);
if let Some(h) = &ts.handler {
// catch 参数是本作用域的词法绑定:own、块级与抑制三面均计入。
// catch 参数是本作用域的词法绑定:own 与块级两面的计入。
// 抑制面:仅解构 catch 参数(BindingPattern)计入。绑定标识符
// catch 参数(CatchParameter:BindingIdentifier)按 B.3.5 例外条款
// 豁免 var 碰撞早期错误,同名块函数仍提升全局,不入抑制集。
if let Some(param) = &h.param {
collect_binding_pattern_names(&param.pattern, &mut out.own_binding_names);
collect_binding_pattern_names(&param.pattern, &mut out.block_level_decl_names);
if !matches!(param.pattern, oxide_parser::BindingPattern::BindingIdentifier(_)) {
collect_binding_pattern_names(&param.pattern, &mut out.block_fn_suppressed);
}
}
collect_fn_scope_names_impl(&h.body.body, out, false);
}
if let Some(f) = &ts.finalizer {
@@ -656,7 +663,7 @@ mod tests {
&[],
&[],
&[],
&["b", "e"],
&["b"],
);
assert_scope_names(
"try { var a; } catch { let b; }",
@@ -795,7 +802,7 @@ mod tests {
&[],
&[],
&["bf6", "bf7", "bf8"],
&["e"],
&[],
);
assert_scope_names(
"L2: { function bf9() {} } with (o) { function bf10() {} }",
@@ -891,7 +898,8 @@ mod tests {
&["s"],
&["s"],
);
// catch 参数与块函数名同名:catch 体外的 if 臂块函数被抑制。
// catch 参数与块函数名同名:绑定标识符 catch 参数按 B.3.5 例外条款豁免 var
// 碰撞早期错误,不入抑制集(同名块函数仍提升全局)。
assert_scope_names(
"try {} catch (e) {} if (x) { function e() {} }",
&[],
@@ -902,7 +910,7 @@ mod tests {
&[],
&[],
&["e"],
&["e"],
&[],
);
// 形参名进抑制面:块函数名撞形参名退化纯块作用域(形参名同时注入 own 面)。
assert_scope_names(
+1
View File
@@ -278,6 +278,7 @@ impl Emitter {
is_async: false,
own_strict: true,
name: None,
is_block_function: false,
};
let ctor_hooks = ClassElementHooks {
extra_bindings: &self_binding,
+1
View File
@@ -140,6 +140,7 @@ impl Emitter {
is_async: method_value.r#async,
own_strict: true,
name: None,
is_block_function: false,
},
hooks,
)?;
+5
View File
@@ -63,6 +63,10 @@ pub struct CompileCtx {
/// `is_eval_script`——该标志只标识当前顶层程序,且还控制顶层全局写点与块级
/// 函数门控,整体传播语义过宽。
pub(crate) is_eval_origin: bool,
/// 是否处于首个 sub-pass(提升函数声明发射期):声明点据此区分顶层函数
/// 声明(名绑定恒不可写)与块级函数声明(Annex B.3.3 块绑定为可变 var 绑定)。
/// 仅程序/函数体发射入口在提升 sub-pass 内置位,不跨嵌套函数继承。
pub(crate) in_hoisting_subpass: bool,
/// 源码是否为 `source_escape` 产物(动态编译入口):正则字面量源文本切片
/// 的池键形态据此选择(见 [`Emitter::source_encoded`])。
pub(crate) source_encoded: bool,
@@ -285,6 +289,7 @@ impl CompileCtx {
repl_persist: false,
is_eval_script: false,
is_eval_origin: false,
in_hoisting_subpass: false,
source_encoded: false,
static_block_this_reg: None,
field_buffer: None,
@@ -45,6 +45,7 @@ impl Emitter {
is_async: arrow.r#async,
own_strict: arrow.has_use_strict_directive(),
name: None,
is_block_function: false,
};
let mut sub_module =
self.compile_simple_function_body(&param_names, body_stmts, ctx, FunctionBodyContext::Arrow, flags)?;
@@ -100,6 +101,7 @@ impl Emitter {
is_async: fe.r#async,
own_strict,
name: fn_name,
is_block_function: false,
};
let mut sub_module =
self.compile_simple_function_body(&param_names, body_stmts, ctx, FunctionBodyContext::Ordinary, flags)?;
+13 -1
View File
@@ -73,6 +73,9 @@ pub struct FunctionFlags<'a> {
pub own_strict: bool,
/// 函数名(具名函数声明/表达式传名,箭头与匿名传 `None`),用于函数名不可写绑定登记。
pub name: Option<&'a str>,
/// 块级普通函数声明(Annex B.3.3):函数名绑定为可写 var 绑定,体内容器内
/// `f = 123` 可覆写块绑定(非块级函数声明的函数名绑定恒不可写)。
pub is_block_function: bool,
}
/// 类元素钩子:类元素函数(构造器/方法/字段)的额外绑定、字段发射与捕获组,
@@ -353,6 +356,7 @@ impl Emitter {
hooks.extra_upvalue_names,
flags.name,
&scope_names,
flags.is_block_function,
)?;
// 实例字段 computed key 数组所在 upvalue 下标,供字段初始化 emit 定位。
ctx.field_keys_uv = ctx
@@ -574,6 +578,7 @@ impl Emitter {
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
ctx: &mut CompileCtx, body_context: FunctionBodyContext, extra_capture_exprs: &[&'a Expression<'a>],
extra_upvalue_names: &[(String, u8)], name: Option<&str>, scope_names: &FnScopeNames,
is_block_function: bool,
) -> Result<u32, String> {
ctx.push_scope_with_kind(ScopeKind::FunctionScope);
let param_base = ctx.next_reg;
@@ -633,6 +638,9 @@ impl Emitter {
// 绑定,帧槽写入把它初始化为函数对象。抑制面(形参同名 / 体内容器 var
// 同名 / arguments 对象占位)不建名绑定:前两者名解析到参数/var 绑定,
// 后者 arguments 绑定占位(var 同名时由帧槽写入初始化为函数对象)。
// 块级普通函数声明(Annex B.3.3)的块绑定是可变 var 绑定:体内容器内
// `f = 123` 可覆写块绑定(非块级函数声明的函数名绑定恒不可写);strict
// 函数名绑定恒不可写(strict 块函数名绑定 eval 自身词法环境,同不可写)。
// 名登记须先于 set_captured_bindings:嵌套函数捕获名分析依赖 own_bindings
// 完整(名被捕获时须识别为本函数绑定并建 cell)。
if let Some(fn_name) = name {
@@ -651,7 +659,7 @@ impl Emitter {
lexical: false,
predeclared: false,
cell_idx: None,
non_writable: true,
non_writable: ctx.is_strict || !is_block_function,
},
);
ctx.own_bindings.insert(fn_name.to_string());
@@ -1088,6 +1096,9 @@ impl Emitter {
/// 入口物化闭包,次 sub-pass 跳过声明语句本身,声明点不重编以保函数对象同一性。
fn emit_body_stmts(&self, body_stmts: &[Statement], ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
let mut last_result_reg = None;
// 提升 sub-pass 标志置位:声明点据此区分顶层函数声明(名绑定恒不可写)
// 与块级函数声明(Annex B.3.3 块绑定为可变 var 绑定)。
ctx.in_hoisting_subpass = true;
for stmt in body_stmts {
if Self::is_body_hoisted_function_decl(stmt) {
if let Some(reg) = self.emit_statement(stmt, ctx)? {
@@ -1095,6 +1106,7 @@ impl Emitter {
}
}
}
ctx.in_hoisting_subpass = false;
for stmt in body_stmts {
if Self::is_body_hoisted_function_decl(stmt) {
continue;
+4
View File
@@ -448,11 +448,15 @@ impl Emitter {
}
// 首个 sub-pass:发函数声明(hoisting),保证任何代码运行前函数对象已就绪。
// 提升 sub-pass 标志置位:声明点据此区分顶层函数声明(名绑定恒不可写)
// 与块级函数声明(Annex B.3.3 块绑定为可变 var 绑定)。
ctx.in_hoisting_subpass = true;
for stmt in &program.body {
if matches!(stmt, Statement::FunctionDeclaration(_)) {
self.emit_statement(stmt, &mut ctx)?;
}
}
ctx.in_hoisting_subpass = false;
// 第二个 sub-pass:发其余所有语句。
let mut last_result: Option<u32> = None;
+1 -1
View File
@@ -77,7 +77,7 @@ impl Emitter {
return Ok(());
};
let name = id.name.to_string();
self.materialize_function_declaration(fd, &name, ctx)?;
self.materialize_function_declaration(fd, &name, ctx, true)?;
// 登记声明节点 arena 地址(Box 解引到 arena 本体):声明点
// 以同一节点地址判同一,区分本声明入口物化与同名支臂。
ctx.block_fn_entry_mats
@@ -1,6 +1,5 @@
//! 函数声明语句 emit:`emit_function_declaration_statement` 创建闭包并绑定函数名。
use crate::symbol_table::ScopeKind;
use crate::{CompileCtx, Emitter, FunctionBodyContext, FunctionFlags, ParamSpec};
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
@@ -34,20 +33,20 @@ impl Emitter {
// 闭包落 fresh 寄存器写回外层 var;
// - 其余(支臂首次求值):执行期自物化闭包写入块槽。
// 非块路径(顶层/函数作用域声明点)恒自物化。
let binding_scope_kind = ctx
.scopes
.symbols
.lookup_any_binding(&name)
.map(|(_, scope_idx)| ctx.scopes.symbols.scopes[scope_idx].kind);
let node = fd as *const _ as *const ();
let var_reg = if binding_scope_kind == Some(ScopeKind::BlockScope) {
// 块级普通函数声明(Annex B.3.3):块槽 Let 绑定承载块函数,函数名绑定
// 为可变 var 绑定(体内容器内 `f = 123` 可覆写块绑定);非块路径恒不可写。
// 判定依据提升 sub-pass 标志:首个 sub-pass 发顶层函数声明(恒不可写),
// 其余 sub-pass 发的函数声明皆为块级(if/while/for/switch/try 支臂与块直接子)。
let is_block_function = !ctx.in_hoisting_subpass;
let var_reg = if is_block_function {
match ctx.block_fn_entry_mats.last().and_then(|m| m.get(&name)) {
Some(&m) if m == node => ctx.lookup(&name)?,
Some(_) => self.materialize_function_declaration_var_only(fd, &name, ctx)?,
None => self.materialize_function_declaration(fd, &name, ctx)?,
Some(_) => self.materialize_function_declaration_var_only(fd, &name, ctx, true)?,
None => self.materialize_function_declaration(fd, &name, ctx, true)?,
}
} else {
self.materialize_function_declaration(fd, &name, ctx)?
self.materialize_function_declaration(fd, &name, ctx, false)?
};
// sloppy 模式下按 Annex B.3.3 为块级普通函数声明建外层 var 绑定并写回:
// 预声明期为该名实例化的函数作用域 var 绑定在此写入块槽位里的函数对象,
@@ -110,11 +109,11 @@ impl Emitter {
/// - 闭包的 upvalue/cell 捕获在函数入口的捕获分析期固定,本调用点的时序
/// 只决定 CREATE_CLOSURE 在指令流中的落点。
pub(crate) fn materialize_function_declaration(
&self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx,
&self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx, is_block_function: bool,
) -> Result<u32, String> {
let var_reg = ctx.lookup(name)?;
ctx.reserve_reg(var_reg);
self.materialize_function_closure(fd, name, var_reg, ctx, true)?;
self.materialize_function_closure(fd, name, var_reg, ctx, true, is_block_function)?;
Ok(var_reg)
}
@@ -122,15 +121,16 @@ impl Emitter {
/// 直接子占用块槽,嵌套捕获读槽 cell 与 V8 块绑定一致)。调用点经返回
/// 寄存器把闭包写回外层 var 绑定。
pub(crate) fn materialize_function_declaration_var_only(
&self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx,
&self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx, is_block_function: bool,
) -> Result<u32, String> {
let reg = ctx.alloc_reg();
self.materialize_function_closure(fd, name, reg, ctx, false)?;
self.materialize_function_closure(fd, name, reg, ctx, false, is_block_function)?;
Ok(reg)
}
fn materialize_function_closure(
&self, fd: &oxide_parser::Function, name: &str, reg: u32, ctx: &mut CompileCtx, write_slot: bool,
is_block_function: bool,
) -> Result<(), String> {
let mut param_names = Vec::new();
for (idx, param) in fd.params.items.iter().enumerate() {
@@ -157,6 +157,7 @@ impl Emitter {
is_async: fd.r#async,
own_strict,
name: Some(name),
is_block_function,
};
let mut sub_module =
self.compile_simple_function_body(&param_names, body_stmts, ctx, FunctionBodyContext::Ordinary, flags)?;
@@ -132,13 +132,13 @@ fn switch_case_function_captured_by_outer() {
#[test]
fn switch_case_function_binding_independent_of_outer_var() {
// 函数名不可写绑定(15.2.10.1 步 m):体内容器对 f 重新赋值静默失败(sloppy),
// 块内 f 仍为函数对象;外层 var 绑定独立,不被改写。
// 块级函数(Annex B.3.3)块绑定为可变 var 绑定:体内容器 `f = 123` 覆写块绑定,
// currentBV 取 123;外层 var 绑定(globalThis.f)独立,仍为函数对象。
let src = "var r1, r2; (function(){ var initialBV, currentBV; \
switch(1){case 1: function f(){ initialBV=f; f=123; currentBV=f; return 'decl'; }} \
var varBinding=f; f(); r1 = initialBV() === 'decl'; \
r2 = currentBV === f && varBinding() === 'decl'; }()); r1 && r2";
assert!(eval(src).unwrap().as_bool(), "函数名不可写绑定自引用重赋静默失败,外层 var 绑定独立");
r2 = currentBV === 123 && varBinding() === 'decl'; }()); r1 && r2";
assert!(eval(src).unwrap().as_bool(), "块函数块绑定可变覆写,外层 var 绑定独立");
}
#[test]