fix: 块级函数声明补 web-compat 外层 var 绑定实例化与声明点写回

This commit is contained in:
2026-09-15 03:48:19 +00:00
parent 0f5bd1577e
commit 320c660620
6 changed files with 418 additions and 9 deletions
+61 -2
View File
@@ -216,6 +216,13 @@ pub struct CompileCtx {
pub(crate) current_upvalue_captures: Vec<UpvalueCapture>,
/// 本函数作用域声明的绑定名(参数 + 变量/函数声明,AST 收集,emit 前确定)。
pub(crate) own_bindings: HashSet<String>,
/// 本函数形参名集(含解构形参叶子与 rest,编译入口收集):块级函数名与
/// 形参同名时不建外层绑定、不求值写回(规范 paramNames 守卫面)。子函数
/// ctx 为新建,不继承。
pub(crate) param_names: HashSet<String>,
/// 块级函数名 web-compat 外层绑定抑制集(形参 ∪ 函数作用域树内词法声明名):
/// 抑制集内名字退化为纯块作用域。预声明期定稿,实例化与声明点写回两侧同查。
pub(crate) block_fn_suppressed: HashSet<String>,
/// 本函数被嵌套函数捕获的绑定名 → cell_idx(名字排序分配,稳定跨 run)。
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
/// 消除符号表时序依赖与 cell 索引错位。
@@ -362,6 +369,8 @@ impl CompileCtx {
class_keys_reg: None,
current_upvalue_captures: Vec::new(),
own_bindings: HashSet::new(),
param_names: HashSet::new(),
block_fn_suppressed: HashSet::new(),
captured_bindings: BTreeMap::new(),
global_tier_names: HashSet::new(),
upvalue_const_flags: HashSet::new(),
@@ -1147,6 +1156,16 @@ impl Emitter {
// 类元素方法/构造器恒 strict(ClassBody 是严格模式代码)。
ctx.is_strict = own_strict || matches!(body_context, FunctionBodyContext::ClassElement) || parent_ctx.is_strict;
// 形参名集(含解构叶子与 rest):块级函数名同名的 web-compat 守卫查此集。
let mut param_names = HashSet::new();
for spec in param_specs {
param_names.insert(spec.register_name().to_string());
if let ParamSpec::Pattern { pattern, .. } = spec {
self.collect_binding_pattern_names(pattern, &mut param_names);
}
}
ctx.param_names = param_names;
// length = 第一个带默认值形参之前的形参数(解构默认与标识符默认同规则);
// rest 参数不计入 length(以 0 结尾即止)。
ctx.function_length = param_specs
@@ -1253,6 +1272,19 @@ impl Emitter {
// 局部绑定,不做受限全局名检查;重复声明错在 emit 期报。
let _ = self.predeclare_lexical_declarations(body_stmts, &mut ctx, false);
// 块级函数名 web-compat 外层 var 绑定(sloppy):块内函数声明名在实例化
// 期于函数作用域建外层 var 绑定——无同名绑定则新建 var 槽;名在抑制集
// (形参/词法声明同名,该形退化为纯块作用域)则不建。求值期声明点把
// 函数对象写回此绑定(见函数声明 emit)。
if !ctx.is_strict {
ctx.block_fn_suppressed = self.collect_block_fn_suppressed_names(body_stmts, &ctx.param_names);
for name in self.collect_block_function_names(body_stmts) {
if !ctx.block_fn_suppressed.contains(&name) {
self.predeclare_var_name(&name, &mut ctx);
}
}
}
// 生成器:body 起点标记——调用时参数初始化(emit_params_prologue)结束后挂起于此,
// 参数副作用/异常在 `g()` 调用时刻生效,首次 next() 从这继续执行 body。
if is_generator {
@@ -1801,6 +1833,20 @@ impl Emitter {
let global_lexical = !ctx.is_eval_script;
self.predeclare_lexical_declarations(&program.body, &mut ctx, global_lexical)?;
// 顶层块级函数名 web-compat 外层绑定(sloppy、非 eval):实例化 var 绑定
// (新建 var 槽;顶层 builtin 名不可声明不建)、并入顶层 var 名集(裸读/
// 写路由全局对象属性)、GDI 序言建属性(define-if-absent,既有属性零动作)。
let mut block_fn_names: Vec<String> = Vec::new();
if !ctx.is_strict && !ctx.is_eval_script {
ctx.block_fn_suppressed = self.collect_block_fn_suppressed_names(&program.body, &ctx.param_names);
block_fn_names = self.collect_block_function_names(&program.body);
for name in &block_fn_names {
if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_known_builtin(name) {
self.predeclare_var_name(name, &mut ctx);
}
}
}
// 闭包捕获分析(AST 级,emit 前确定)
ctx.own_bindings = self.collect_own_binding_names(&[], &program.body);
// 顶层已声明名(A 侧单一真值):裸读走全局对象属性、裸写走描述符感知
@@ -1811,6 +1857,12 @@ impl Emitter {
let var_names = self.collect_var_binding_names(&program.body);
let mut tier_names = var_names.clone();
tier_names.extend(self.collect_top_level_function_names(&program.body));
// 块级函数泄漏名并入:求值期写回与头写同走全局对象属性(A 侧单一真值)。
for name in &block_fn_names {
if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_known_builtin(name) {
tier_names.insert(name.clone());
}
}
ctx.global_tier_names = tier_names;
ctx.captured_bindings = self.collect_captured_bindings(&program.body, &[], &ctx.own_bindings);
ctx.captured_bindings.retain(|n, _| !ctx.global_tier_names.contains(n));
@@ -1846,8 +1898,15 @@ impl Emitter {
// 新建,可写/不可写/可配置既有属性(含值)一律保留。builtin 名的全局属性
// 运行期预存(session 绑定):缺失分支写入值取 builtin 镜像槽(run 起点
// 预载全局属性值),既有属性零动作,值幂等保留。
// 序言名集 var-only,不随 tier 集扩宽。
let gdi_var_names = self.collect_var_binding_names(&program.body);
// 序言名集 = 顶层 var 名 ∪ 顶层块级函数泄漏名(web-compat 外层绑定同样
// 在求值前实例化,同走 define-if-absent)。
let mut gdi_var_names = self.collect_var_binding_names(&program.body);
gdi_var_names.extend(
block_fn_names
.iter()
.filter(|n| !ctx.block_fn_suppressed.contains(*n) && !CompileCtx::is_known_builtin(n))
.cloned(),
);
if !gdi_var_names.is_empty() {
let undef_reg = self.emit_undefined(&mut ctx);
for name in &gdi_var_names {
+195 -1
View File
@@ -4,6 +4,8 @@
//! (builtin_reg_map),避免与临时寄存器池冲突;同时预声明函数/var 声明,
//! 支持提升语义。
use std::collections::HashSet;
use crate::{CompileCtx, Emitter};
use oxide_parser::{
BindingPattern, Declaration, ExportDefaultDeclarationKind, Expression, Statement, VariableDeclarationKind,
@@ -393,7 +395,7 @@ impl Emitter {
/// (run 起点预载全局属性值),使 GDI 序言与声明点同步都用入口原值而非
/// fresh undefined 槽,现存值(NaN 等)不被抹;函数体内 builtin 名仍是局部
/// var 遮蔽(fresh 槽,不命中只读内置拦截);其余名 fresh var 槽。
fn predeclare_var_name(&self, name: &str, ctx: &mut CompileCtx) {
pub(crate) fn predeclare_var_name(&self, name: &str, ctx: &mut CompileCtx) {
if ctx.is_global_scope && CompileCtx::is_known_builtin(name) {
let _ = ctx.lookup_or_builtin(name);
} else {
@@ -749,3 +751,195 @@ impl Emitter {
Ok(())
}
}
impl Emitter {
/// 递归收集语句树内块级函数声明名(源序去重),供 web-compat 外层 var 绑定
/// 实例化。
///
/// # 边界与前提
/// - 调用点直接子级(函数体/程序顶层直接子句)是提升 var 声明,非块级
/// 函数,不收集;进入嵌套语句(块/if/循环/switch case/try 各区)后的
/// 函数声明才是块级。
/// - 不递归进嵌套函数体:那是独立编译单元,其内部块函数名由该单元自身收集。
/// - switch 不推作用域(case 内函数声明随外层块),收集须进入 case 才能到达
/// case 内的 for/块等块级形。
pub(crate) fn collect_block_function_names(&self, statements: &[Statement]) -> Vec<String> {
let mut names = Vec::new();
self.collect_block_function_names_into(statements, &mut names, false);
names
}
fn collect_block_function_names_into(&self, statements: &[Statement], names: &mut Vec<String>, block_level: bool) {
for statement in statements {
match statement {
Statement::FunctionDeclaration(f) => {
if block_level {
if let Some(id) = &f.id {
let name = id.name.to_string();
if !names.contains(&name) {
names.push(name);
}
}
}
}
Statement::BlockStatement(bs) => self.collect_block_function_names_into(&bs.body, names, true),
Statement::IfStatement(is) => {
self.collect_block_function_names_into(std::slice::from_ref(&is.consequent), names, true);
if let Some(alt) = &is.alternate {
self.collect_block_function_names_into(std::slice::from_ref(alt), names, true);
}
}
Statement::WhileStatement(wh) => {
self.collect_block_function_names_into(std::slice::from_ref(&wh.body), names, true)
}
Statement::DoWhileStatement(dw) => {
self.collect_block_function_names_into(std::slice::from_ref(&dw.body), names, true)
}
Statement::ForStatement(fs) => {
self.collect_block_function_names_into(std::slice::from_ref(&fs.body), names, true)
}
Statement::ForInStatement(fi) => {
self.collect_block_function_names_into(std::slice::from_ref(&fi.body), names, true)
}
Statement::ForOfStatement(fo) => {
self.collect_block_function_names_into(std::slice::from_ref(&fo.body), names, true)
}
Statement::SwitchStatement(sw) => {
for case in &sw.cases {
self.collect_block_function_names_into(&case.consequent, names, true);
}
}
Statement::TryStatement(ts) => {
self.collect_block_function_names_into(&ts.block.body, names, true);
if let Some(handler) = &ts.handler {
self.collect_block_function_names_into(&handler.body.body, names, true);
}
if let Some(finalizer) = &ts.finalizer {
self.collect_block_function_names_into(&finalizer.body, names, true);
}
}
Statement::LabeledStatement(ls) => {
self.collect_block_function_names_into(std::slice::from_ref(&ls.body), names, true)
}
Statement::WithStatement(ws) => {
self.collect_block_function_names_into(std::slice::from_ref(&ws.body), names, true)
}
_ => {}
}
}
}
/// 收集语句树内的词法声明名(let/const/class 含解构叶、for 头词法名、catch
/// 参数):块级函数名与树内任意词法声明同名时退化为纯块作用域(无外层
/// 绑定、无求值写回)。
///
/// # 边界与前提
/// - 不递归进嵌套函数体/class 元素体:独立编译单元,其词法名不撞本单元的
/// 块函数名。
/// - var 声明是 var 绑定,不撞词法环境,不入集。
fn collect_lexical_name_suppressions(&self, statements: &[Statement], names: &mut HashSet<String>) {
for statement in statements {
match statement {
Statement::VariableDeclaration(vd) => {
if !matches!(vd.kind, VariableDeclarationKind::Var) {
for d in &vd.declarations {
self.collect_binding_pattern_names(&d.id, names);
}
}
}
Statement::ClassDeclaration(cd) => {
if let Some(id) = &cd.id {
names.insert(id.name.to_string());
}
}
Statement::ForStatement(fs) => {
if let Some(oxide_parser::ForStatementInit::VariableDeclaration(decl)) = &fs.init {
if !matches!(decl.kind, VariableDeclarationKind::Var) {
for d in &decl.declarations {
self.collect_binding_pattern_names(&d.id, names);
}
}
}
self.collect_lexical_name_suppressions(std::slice::from_ref(&fs.body), names);
}
Statement::ForInStatement(fi) => {
if let oxide_parser::ForStatementLeft::VariableDeclaration(decl) = &fi.left {
if !matches!(decl.kind, VariableDeclarationKind::Var) {
for d in &decl.declarations {
self.collect_binding_pattern_names(&d.id, names);
}
}
}
self.collect_lexical_name_suppressions(std::slice::from_ref(&fi.body), names);
}
Statement::ForOfStatement(fo) => {
if let oxide_parser::ForStatementLeft::VariableDeclaration(decl) = &fo.left {
if !matches!(decl.kind, VariableDeclarationKind::Var) {
for d in &decl.declarations {
self.collect_binding_pattern_names(&d.id, names);
}
}
}
self.collect_lexical_name_suppressions(std::slice::from_ref(&fo.body), names);
}
Statement::TryStatement(ts) => {
// catch 参数是 handler 环境的词法绑定。
if let Some(handler) = &ts.handler {
if let Some(param) = &handler.param {
self.collect_binding_pattern_names(&param.pattern, names);
}
self.collect_lexical_name_suppressions(&handler.body.body, names);
}
self.collect_lexical_name_suppressions(&ts.block.body, names);
if let Some(finalizer) = &ts.finalizer {
self.collect_lexical_name_suppressions(&finalizer.body, names);
}
}
Statement::BlockStatement(bs) => self.collect_lexical_name_suppressions(&bs.body, names),
Statement::IfStatement(is) => {
self.collect_lexical_name_suppressions(std::slice::from_ref(&is.consequent), names);
if let Some(alt) = &is.alternate {
self.collect_lexical_name_suppressions(std::slice::from_ref(alt), names);
}
}
Statement::WhileStatement(wh) => {
self.collect_lexical_name_suppressions(std::slice::from_ref(&wh.body), names)
}
Statement::DoWhileStatement(dw) => {
self.collect_lexical_name_suppressions(std::slice::from_ref(&dw.body), names)
}
Statement::SwitchStatement(sw) => {
for case in &sw.cases {
self.collect_lexical_name_suppressions(&case.consequent, names);
}
}
Statement::LabeledStatement(ls) => {
self.collect_lexical_name_suppressions(std::slice::from_ref(&ls.body), names)
}
Statement::WithStatement(ws) => {
self.collect_lexical_name_suppressions(std::slice::from_ref(&ws.body), names)
}
Statement::ExportNamedDeclaration(exp) => {
if let Some(Declaration::VariableDeclaration(vd)) = &exp.declaration {
if !matches!(vd.kind, VariableDeclarationKind::Var) {
for d in &vd.declarations {
self.collect_binding_pattern_names(&d.id, names);
}
}
}
}
_ => {}
}
}
}
/// 块级函数名 web-compat 外层绑定抑制集:形参名(含解构叶/rest)∪ 语句树内
/// 词法声明名。命中抑制的名字不建外层 var 绑定、不求值写回。
pub(crate) fn collect_block_fn_suppressed_names(
&self, statements: &[Statement], param_names: &HashSet<String>,
) -> HashSet<String> {
let mut names = param_names.clone();
self.collect_lexical_name_suppressions(statements, &mut names);
names
}
}
@@ -68,8 +68,35 @@ impl Emitter {
} else {
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None));
}
// 块内函数声明求值写回外层 var 绑定(sloppy web-compat):预声明期为该
// 名实例化的外层 var 绑定(函数作用域)在此写入块槽位里的函数对象,
// 求值一次写回一次(循环体内每迭代覆写头绑定)。形参/词法声明同名
// (抑制集)与顶层 builtin 名不写回;顶层 A 侧补全局对象属性写(与
// for 头写同形)。
if !ctx.is_strict
&& ctx.scopes.symbols.scopes.len() > 1
&& !ctx.block_fn_suppressed.contains(&name)
&& !(ctx.is_global_scope && CompileCtx::is_known_builtin(&name))
{
let var_target = ctx.scopes.symbols.find_var_target_scope();
if let Some(outer) = ctx.scopes.symbols.scopes[var_target].bindings.get(&name) {
ctx.inst(Inst::new(
OpCode::STORE_VAR,
Operand::Reg(outer.reg),
Operand::Reg(var_reg),
Operand::None,
));
// 顶层 A 侧同步写以外层绑定是否落全局作用域为准:此处块内
// Let 绑定已占内层作用域,按名解析(is_global_tier_name)会
// 命中块绑定误判为非顶层;嵌套函数作用域同名局部 var 不在
// 全局 ctx 上,经 is_global_scope 门禁不走全局对象写。
if ctx.is_global_scope && var_target == 0 && ctx.global_tier_names.contains(&name) {
self.emit_tier_global_write(&name, var_reg, ctx);
}
}
}
// 脚本顶层(非块内)函数声明:同步写全局对象,使 globalThis 可反射函数名。
// 块内函数声明是块级绑定,不得落全局对象(作用域隔离,块外不可见)。
// 块内声明不走本臂(其全局可见性只经上方外层 var 写回面建立)。
// 严格 eval 代码函数声明绑定 eval 自身 lexical 环境、不落全局对象:抑制 A
// 侧写(局部绑定读面未暴露),同时避免不可写全局内置在 0x98 上误抛 strict。
if ctx.is_global_scope && ctx.scopes.symbols.scopes.len() == 1 && !(ctx.is_eval_script && ctx.is_strict) {
+1 -1
View File
@@ -70,7 +70,7 @@ impl SymbolTable {
}
}
fn find_var_target_scope(&self) -> usize {
pub(crate) fn find_var_target_scope(&self) -> usize {
for (i, scope) in self.scopes.iter().enumerate().rev() {
if scope.kind == ScopeKind::FunctionScope {
return i;
@@ -0,0 +1,127 @@
//! 块级函数声明与 for-in/of var 头同名(Annex B 块级函数绑定面):
//! 求值期把函数对象写回外层 var 绑定,无外层绑定的名字在声明实例化期建绑定。
//!
//! 覆盖:顶层/函数作用域 for-in 头形、嵌套块/switch case/for-of 变体、
//! 无碰撞泄漏名、while 块、arguments 名守卫、既有全局属性保留、形参/词法
//! 碰撞守卫、strict 无写回对照。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?;
let mut vm = Vm::new();
vm.run(&Arc::new(module))
}
fn truthy(source: &str) {
let result = eval(source).unwrap_or_else(|e| panic!("{source} -> {e}"));
assert!(result.as_bool(), "{source} -> 期望 true,得 {:?}", result);
}
#[test]
fn for_in_var_head_same_name_rebinds_head() {
// 顶层 for-in var 头同名:循环后头绑定被函数对象覆写。
truthy("var o={a:1,b:2}; for (var x in o){function x(){}} typeof x === 'function'");
}
#[test]
fn for_in_var_head_same_name_reaches_global_property() {
// 顶层形同时同步全局对象属性(脚本环境记录 var 绑定)。
truthy("var o={a:1,b:2}; for (var x in o){function x(){}} typeof globalThis.x === 'function'");
}
#[test]
fn for_in_var_head_same_name_in_function_scope() {
// 函数作用域 for-in 头同名:写回函数作用域 var 绑定,不落全局对象。
truthy("function f(){ var o={a:1,b:2}; for (var x in o){function x(){}} return typeof x === 'function' } f()");
}
#[test]
fn nested_blocks_inside_for_body() {
// 循环体内再嵌块层的块函数声明同样写回头绑定。
truthy("var o={a:1}; for (var x in o){ { { function x(){} } } } typeof x === 'function'");
}
#[test]
fn assignment_head_same_name() {
// 赋值头(未声明名)同名:声明实例化期建外层绑定,写回后为函数。
truthy("var o={a:1,b:2}; for (x in o){function x(){}} typeof x === 'function'");
}
#[test]
fn for_of_var_head_same_name() {
// for-of var 头同名孪生形。
truthy("for (var y of [1,2]){function y(){}} typeof y === 'function'");
}
#[test]
fn switch_case_for_head_same_name() {
// switch case 内 for-in 头同名(case 不推作用域,块函数随外层块)。
truthy("var o={a:1,b:2}; switch(1){case 1: for (var x in o){function x(){}}} typeof x === 'function'");
}
#[test]
fn for_of_destructuring_var_head_same_name() {
// for-of 解构 var 头同名:写回后头绑定为函数而非末迭代值。
truthy("for (var [x] of [[1],[2]]){function x(){}} typeof x === 'function'");
}
#[test]
fn no_collision_block_fn_in_for_body_instantiates_outer_var() {
// 无碰撞泄漏形:体内块函数名无既有 var 绑定,声明实例化期新建外层绑定。
truthy("var o={a:1,b:2}; for (var x in o){function f(){}} typeof f === 'function'");
}
#[test]
fn while_body_block_fn_instantiates_outer_var() {
// while 体内块函数(非循环头碰撞形):外层绑定实例化 + 每迭代写回,
// 顶层同步全局对象属性。
truthy(
"var n=0; while(n<2){ n++; {function w(){}} } typeof w === 'function' && typeof globalThis.w === 'function'",
);
}
#[test]
fn function_scope_block_fn_named_arguments_overwrites_arguments_object() {
// 函数作用域块函数名 arguments:arguments 对象被函数对象覆写。
truthy("function f(){ {function arguments(){}} return typeof arguments === 'function' } f()");
}
#[test]
fn top_level_block_fn_named_arguments_reaches_global() {
// 脚本顶层块函数名 arguments:外层绑定落全局对象属性。
truthy("{function arguments(){}} typeof globalThis.arguments === 'function'");
}
#[test]
fn existing_global_property_kept_before_block_fn_decl() {
// 既有全局属性(值 7)在块函数声明前被读为 number,声明后该属性被覆写为
// 函数(声明实例化对既有可写数据属性零动作,求值期写回才覆写)。
truthy("globalThis.w=7; var r=typeof w; {function w(){}} r === 'number' && typeof w === 'function'");
}
#[test]
fn param_name_collision_no_write_back() {
// 形参同名守卫:块函数退化为纯块作用域,形参值不受写回影响。
truthy("function f(x){ {function x(){}} return String(x) } f(42) === '42'");
}
#[test]
fn lexical_name_collision_no_outer_binding() {
// 词法声明同名守卫:不建外层 var 绑定、不求值写回,词法值保持。
truthy("function f(){ let g='L'; {function g(){}} return g } f() === 'L'");
}
#[test]
fn strict_for_body_no_write_back() {
// strict 无 web-compat 写回:循环后头绑定保留末迭代值。
truthy(
"function f(){ \"use strict\"; var o={a:1,b:2}; for (var x in o){function x(){}} return typeof x } f() === 'string'",
);
}
@@ -56,10 +56,12 @@ fn block_function_decl_capture_escape() {
}
#[test]
fn block_function_decl_scope_isolated() {
// 嵌套块内函数是块级绑定:块外不可见(typeof 未声明标识符返回 undefined)。
let result = eval("{ { function g(){return 2;} } } typeof g === 'undefined'").unwrap();
assert!(result.as_bool());
fn block_function_decl_leaks_outer_in_sloppy_script() {
// sloppy 块级函数声明名泄漏外层(web-compat 外层 var 绑定 + 求值写回):
// 块后读见函数对象而非未声明名。块内声明前读仍为 undefined(块入口初始化
// 未建,见 hoisted_call 测试),块内声明后读为函数。
let result = eval("{ { function g(){return 2;} } } typeof g === 'function'").unwrap();
assert!(result.as_bool(), "sloppy 块函数名应泄漏外层,got {:?}", result);
}
#[test]