fix: 顶层函数声明名并入 A 侧 tier 路由——再赋值/裸读与全局属性收敛单一真值

This commit is contained in:
2026-09-13 02:05:31 +00:00
parent 16c1c3ecf1
commit 675b6c10e9
3 changed files with 104 additions and 12 deletions
+15
View File
@@ -148,6 +148,21 @@ impl Emitter {
}
}
/// 收集语句列表**直接子级**函数声明的绑定名(顶层函数声明是全局 var 绑定,
/// 裸读/裸写与 var 名同路由全局对象属性)。不递归进块:块内函数声明是块
/// 作用域,不参与全局对象同步;`export default function` 是 lexical 绑定,不纳。
pub(crate) fn collect_top_level_function_names(&self, stmts: &[Statement]) -> HashSet<String> {
let mut names = HashSet::new();
for stmt in stmts {
if let Statement::FunctionDeclaration(fd) = stmt {
if let Some(id) = &fd.id {
names.insert(id.name.to_string());
}
}
}
names
}
/// 收集当前函数作用域声明的绑定名(参数 + 变量/函数声明,含嵌套 block,不含嵌套函数体)。
pub(crate) fn collect_own_binding_names(&self, param_names: &[&str], stmts: &[Statement]) -> HashSet<String> {
let mut names = HashSet::new();
+19 -12
View File
@@ -207,9 +207,10 @@ pub struct CompileCtx {
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
/// 消除符号表时序依赖与 cell 索引错位。
pub(crate) captured_bindings: BTreeMap<String, u8>,
/// 顶层已声明 var 名(脚本全局 GDI 提升名):裸读走全局对象属性(LOAD_GLOBAL)、
/// 裸写走描述符感知 A 侧写,单一真值不落镜像 cell。仅脚本顶层 emit_program 计算,
/// 逐层继承给嵌套函数(嵌套函数的局部同名遮蔽不属此集,由作用域索引判定)。
/// 顶层已声明 var 名与顶层函数声明名(脚本全局 GDI 提升名):裸读走全局对象
/// 属性(LOAD_GLOBAL)、裸写走描述符感知 A 侧写,单一真值不落镜像 cell。
/// 仅脚本顶层 emit_program 计算,逐层继承给嵌套函数(嵌套函数的局部同名
/// 遮蔽不属此集,由作用域索引判定)。
pub(crate) global_tier_names: HashSet<String>,
/// 本函数从父函数捕获的 const 绑定名:子 ctx 不继承父函数作用域符号表,
/// 捕获 const 信息随 upvalue 收集一并快照,供 const 写检查(编译期拦截)使用。
@@ -1676,18 +1677,23 @@ impl Emitter {
// 闭包捕获分析(AST 级,emit 前确定)
ctx.own_bindings = self.collect_own_binding_names(&[], &program.body);
// 顶层已声明 var 名(A 侧单一真值):裸读走全局对象属性、裸写走描述符感知
// 顶层已声明名(A 侧单一真值):裸读走全局对象属性、裸写走描述符感知
// A 侧写,不落镜像 cell——从捕获集剔除,使嵌套函数经继承 scope-0 直连全局。
// 集 = 顶层 var 名 ∪ 顶层函数声明名:函数值是编译闭包,编译期不可得,
// 其 A 侧值由首 sub-pass 以真闭包建立,先于任何用户代码。
// 仅在此顶层调用点过滤:嵌套函数的局部同名遮蔽是独立绑定,其调用点不过滤。
ctx.global_tier_names = self.collect_var_binding_names(&program.body);
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));
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));
// 顶层 var 入口实例化:被捕获的 var 名统一 MAKE_CELL(undefined),使 var
// 声明语句执行前创建的闭包读取到 undefined(脚本 GlobalDeclarationInstantiation
// 语义),而非占位 cell 的 TDZ 误报。声明语句的 MAKE_CELL 覆盖此初值。
let var_names: Vec<String> = self
.collect_var_binding_names(&program.body)
// 名集保持 var-only:函数声明名从不入捕获集(tier 剔除),无需入口 cell。
let var_names: Vec<String> = var_names
.into_iter()
.filter(|n| ctx.captured_bindings.contains_key(n))
.collect();
@@ -1706,15 +1712,16 @@ impl Emitter {
}
// 全局声明实例化序言:脚本求值前为顶层 var 名创建全局对象属性(值 undefined),
// 使声明语句执行前的读取(typeof、反射、自引用)可经全局对象见绑定;同名
// 函数声明的属性由首个 sub-pass 以函数值覆盖,声明语句保持值更新语义。
// 使声明语句执行前的读取(typeof、反射、自引用)可经全局对象见绑定。
// 顶层函数声明名不进序言:函数值是编译闭包,编译期不可得,其 A 侧值由
// 首 sub-pass 的声明写以真闭包建立,先于任何用户代码(首 sub-pass 仅发
// 函数声明),无 undefined 读窗口。
// CreateGlobalVarBinding 对既有属性零动作:define-if-absent 只在属性缺失时
// 新建,可写/不可写/可配置既有属性(含值)一律保留。builtin 名的全局属性
// 运行期预存(session 绑定):缺失分支写入值取 builtin 镜像槽(run 起点
// 预载全局属性值),既有属性零动作,值幂等保留。
// 复用顶层 var 名集(global_tier_names,与捕获集剔除同源);克隆到本地
// 避免序言循环内 &mut ctx 与名集借用冲突。
let gdi_var_names = ctx.global_tier_names.clone();
// 序言名集 var-only,不随 tier 集扩宽。
let gdi_var_names = self.collect_var_binding_names(&program.body);
if !gdi_var_names.is_empty() {
let undef_reg = self.emit_undefined(&mut ctx);
for name in &gdi_var_names {
@@ -389,6 +389,76 @@ fn tier_declaration_respects_existing_property() {
));
}
// ── 顶层函数声明名与 var 名同列单一真值:再赋值/裸读/GOPD 收敛全局对象属性 ──
/// 顶层函数声明名再赋值:裸读、globalThis 别名、GOPD 反射三者见同一值——
/// 写点落全局对象属性(A 侧),声明处的函数值建立不随裸写失步。
#[test]
fn top_level_function_reassign_single_source() {
eval_truthy("function f(){} f = 5; f === 5 && globalThis.f === 5");
eval_truthy("function f(){} f = 5; Object.getOwnPropertyDescriptor(globalThis, 'f').value === 5 && f === 5");
// 复合赋值 RMW 取属性侧旧值:基址须跟随属性而非声明期残留。
eval_truthy("function f(){return 1;} f = 5; f += 2; f === 7 && globalThis.f === 7");
// 嵌套 strict 写经 session 落属性(写点不依赖 this)。
eval_truthy("function f(){} (function(){ \"use strict\"; f = 9; })(); f === 9 && globalThis.f === 9");
}
/// 声明处 A 侧写新建描述符 {writable, enumerable, configurable:false},与 var
/// 声明描述符同形。
#[test]
fn top_level_function_decl_descriptor_match_var_shape() {
eval_truthy(
"function f(){} var d = Object.getOwnPropertyDescriptor(globalThis, 'f'); \
d.writable === true && d.enumerable === true && d.configurable === false",
);
}
/// 嵌套局部同名遮蔽不穿透顶层函数绑定;无初始化 var 不抹函数值,带初始化
/// var 按 PutValue 覆盖函数值。
#[test]
fn top_level_function_shadow_and_var_no_init() {
eval_truthy(
"function f(){return 1;} (function(){ var f = 9; return f; })() === 9 \
&& typeof f === 'function' && globalThis.f() === 1",
);
eval_truthy("function f(){return 1;} var f; typeof f === 'function' && globalThis.f === f");
eval_truthy("function f(){return 1;} var f = 5; f === 5 && globalThis.f === 5");
}
/// 声明处描述符感知写:既有不可写不可配置属性声明不更值(定义不变量静默
/// no-op),裸读与反射一致见既有值。
#[test]
fn top_level_function_decl_respects_nonwritable_property() {
assert_two_phases_truthy(
"Object.defineProperty(globalThis, 'f', {value: 1, writable: false, configurable: false})",
"function f(){} f === 1 && globalThis.f === 1 && typeof f === 'number'",
);
}
/// 跨 run(reset)契约:函数对象的 A 侧值直落 session,run2 经全局别名
/// 调用值正确。
#[test]
fn top_level_function_survives_reset() {
assert!(eval_two_phases(
"function f(){ return 1; } 0",
"typeof globalThis.f === 'function' && globalThis.f() === 1",
));
}
/// builtin 名形零污染:可写 builtin 名(Math)声明更值保描述符;不可写
/// builtin 名(undefined)声明不更新,裸读与反射一致见既有值。
#[test]
fn top_level_function_decl_builtin_name_zero_pollution() {
eval_truthy("function Math(){ return 7; } Math() === 7 && globalThis.Math() === 7 && Math === globalThis.Math");
eval_truthy(
"function Math(){} var d = Object.getOwnPropertyDescriptor(globalThis, 'Math'); \
d.writable === true && d.enumerable === false && d.configurable === true",
);
eval_truthy(
"function undefined(){ return 1; } typeof undefined === 'undefined' && globalThis.undefined === undefined",
);
}
// ── 声明带初始化的 A 侧写是 PutValue:strict 撞既有不可写用户属性抛 TypeError ──
#[test]