diff --git a/crates/oxide_emit/src/closure.rs b/crates/oxide_emit/src/closure.rs index f6dd6f6f..e43d482b 100644 --- a/crates/oxide_emit/src/closure.rs +++ b/crates/oxide_emit/src/closure.rs @@ -148,6 +148,21 @@ impl Emitter { } } + /// 收集语句列表**直接子级**函数声明的绑定名(顶层函数声明是全局 var 绑定, + /// 裸读/裸写与 var 名同路由全局对象属性)。不递归进块:块内函数声明是块 + /// 作用域,不参与全局对象同步;`export default function` 是 lexical 绑定,不纳。 + pub(crate) fn collect_top_level_function_names(&self, stmts: &[Statement]) -> HashSet { + 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 { let mut names = HashSet::new(); diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 921d8f6b..dc73cdac 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -207,9 +207,10 @@ pub struct CompileCtx { /// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射, /// 消除符号表时序依赖与 cell 索引错位。 pub(crate) captured_bindings: BTreeMap, - /// 顶层已声明 var 名(脚本全局 GDI 提升名):裸读走全局对象属性(LOAD_GLOBAL)、 - /// 裸写走描述符感知 A 侧写,单一真值不落镜像 cell。仅脚本顶层 emit_program 计算, - /// 逐层继承给嵌套函数(嵌套函数的局部同名遮蔽不属此集,由作用域索引判定)。 + /// 顶层已声明 var 名与顶层函数声明名(脚本全局 GDI 提升名):裸读走全局对象 + /// 属性(LOAD_GLOBAL)、裸写走描述符感知 A 侧写,单一真值不落镜像 cell。 + /// 仅脚本顶层 emit_program 计算,逐层继承给嵌套函数(嵌套函数的局部同名 + /// 遮蔽不属此集,由作用域索引判定)。 pub(crate) global_tier_names: HashSet, /// 本函数从父函数捕获的 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 = self - .collect_var_binding_names(&program.body) + // 名集保持 var-only:函数声明名从不入捕获集(tier 剔除),无需入口 cell。 + let var_names: Vec = 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 { diff --git a/crates/oxide_vm/tests/global_var_binding_tests.rs b/crates/oxide_vm/tests/global_var_binding_tests.rs index 095f5431..705df8f1 100644 --- a/crates/oxide_vm/tests/global_var_binding_tests.rs +++ b/crates/oxide_vm/tests/global_var_binding_tests.rs @@ -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]