diff --git a/crates/oxide_builtins/src/module.rs b/crates/oxide_builtins/src/module.rs index 4cd4c870..a26dd002 100644 --- a/crates/oxide_builtins/src/module.rs +++ b/crates/oxide_builtins/src/module.rs @@ -113,6 +113,23 @@ fn push_uninitialized_entry(table: *mut ModuleNsTable, name_si: u32) { ); } +/// 追加一条已初始化为 undefined 的 `Value` 条目(来源为本地导出)。 +/// +/// # 边界与前提 +/// - 供 var 作用域导出(`VarScopedDeclarations`)在实例化期预初始化:读时为 +/// undefined 而非 TDZ,写点由 `__moduleSet` 就地覆盖。 +fn push_var_initialized_entry(table: *mut ModuleNsTable, name_si: u32) { + push_entry( + table, + name_si, + ModuleNsOrigin::Local, + ModuleNsState::Value { + value: JsValue::undefined(), + initialized: true, + }, + ); +} + /// 向条目表追加一条带来源身份的条目。 /// /// # 注意事项 @@ -300,19 +317,21 @@ pub fn drop_module_ns_native(obj: &mut JsObject) -> u64 { bytes } -/// `__modulePreRegister(ns, name)`:live 模块在 body 求值前预注册静态导出名。 +/// `__modulePreRegister(ns, name, var_like)`:live 模块在 body 求值前预注册静态导出名。 /// /// # 步骤 /// 1. 校验 ns 为 module namespace 对象,取导出名的 interned 键。 /// 2. 无表时分配;键已有条目时 no-op(幂等)。 /// 3. 经瞬态可扩展窗口在 ns 上定义真实数据属性 `undefined`(可写可枚举不可配置)。 -/// 4. 追加未初始化 `Value` 条目并按规范顺序重排导出键。 +/// 4. 追加条目:`var_like` 为真(var/函数类导出,属 `VarScopedDeclarations`)时 +/// 初始化为 undefined;否则保持未初始化(lexical/class 的 TDZ)。随后按规范顺序 +/// 重排导出键。 /// /// # 副作用 /// - 在 ns 上定义真实属性槽、挂载/扩展条目表,并重排 `[[OwnPropertyKeys]]` 顺序。 pub fn module_pre_register(vm: &mut H, args: &[u8]) -> NativeResult { - if args.len() < 3 { - return type_error(vm, "__modulePreRegister: 2 arguments required"); + if args.len() < 4 { + return type_error(vm, "__modulePreRegister: 3 arguments required"); } let ns_val = vm.reg(args[1]); let ns_ptr = match vm.checked_object_ptr(ns_val, "__modulePreRegister: target is not an object") { @@ -322,6 +341,7 @@ pub fn module_pre_register(vm: &mut H, args: &[u8]) -> NativeResult { }; let name_val = vm.reg(args[2]); let name_si = vm.property_key_si(name_val); + let var_like = oxide_runtime_api::to_boolean(vm.reg(args[3])); let obj = unsafe { &mut *ns_ptr }; if !obj.is_module_namespace() { return type_error(vm, "__modulePreRegister: target is not a module namespace"); @@ -342,7 +362,11 @@ pub fn module_pre_register(vm: &mut H, args: &[u8]) -> NativeResult { } let table = if existing.is_null() { install_ns_table(obj) } else { existing }; - push_uninitialized_entry(table, name_si); + if var_like { + push_var_initialized_entry(table, name_si); + } else { + push_uninitialized_entry(table, name_si); + } crate::object::sort_namespace_exports(vm, obj); NativeResult::Ok(JsValue::undefined()) } diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index 56ead56f..e6f046fe 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -153,6 +153,101 @@ fn module_own_export_names(body: &[Statement]) -> HashSet { names } +/// 收集模块实例化期即初始化为 undefined 的导出名(VarScopedDeclarations): +/// `export var`、`export function`、`export default function`(具名与匿名)以及 +/// 经 `export { x }` 本地再导出、底层绑定为 var/function 的导出名。 +/// +/// # 边界与前提 +/// - lexical(let/const/class)与跨模块再导出(`export { x } from`)不在集合内, +/// 其命名空间条目保持未初始化(TDZ)。 +/// - `export { x as y }` 只认本模块顶层直接声明的 var/function;自导入别名等 +/// 间接名不判真(按 TDZ 处理)。 +/// - 匿名 `export default function` 的导出名是 `default`,按 HoistableDeclaration +/// 预初始化。 +fn module_var_scoped_export_names(body: &[Statement]) -> HashSet { + let mut var_locals: HashSet = HashSet::new(); + for stmt in body { + match stmt { + Statement::VariableDeclaration(vd) => { + if matches!(vd.kind, VariableDeclarationKind::Var) { + for d in &vd.declarations { + if let BindingPattern::BindingIdentifier(bi) = &d.id { + var_locals.insert(bi.name.to_string()); + } + } + } + } + Statement::FunctionDeclaration(fd) => { + if let Some(id) = &fd.id { + var_locals.insert(id.name.to_string()); + } + } + Statement::ExportNamedDeclaration(exp) => { + if let Some(decl) = &exp.declaration { + match decl { + Declaration::VariableDeclaration(vd) => { + if matches!(vd.kind, VariableDeclarationKind::Var) { + for d in &vd.declarations { + if let BindingPattern::BindingIdentifier(bi) = &d.id { + var_locals.insert(bi.name.to_string()); + } + } + } + } + Declaration::FunctionDeclaration(fd) => { + if let Some(id) = &fd.id { + var_locals.insert(id.name.to_string()); + } + } + _ => {} + } + } + } + _ => {} + } + } + + let mut names = HashSet::new(); + for stmt in body { + match stmt { + Statement::ExportNamedDeclaration(exp) => { + if let Some(decl) = &exp.declaration { + match decl { + Declaration::VariableDeclaration(vd) => { + if matches!(vd.kind, VariableDeclarationKind::Var) { + for d in &vd.declarations { + if let BindingPattern::BindingIdentifier(bi) = &d.id { + names.insert(bi.name.to_string()); + } + } + } + } + Declaration::FunctionDeclaration(fd) => { + if let Some(id) = &fd.id { + names.insert(id.name.to_string()); + } + } + _ => {} + } + } else if exp.source.is_none() { + for spec in &exp.specifiers { + if var_locals.contains(module_export_name_str(&spec.local).as_str()) { + names.insert(module_export_name_str(&spec.exported)); + } + } + } + } + Statement::ExportDefaultDeclaration(exp) => { + if matches!(&exp.declaration, ExportDefaultDeclarationKind::FunctionDeclaration(_)) { + names.insert(DEFAULT_EXPORT_NAME.to_string()); + } + } + _ => {} + } + } + names +} + /// 判断语句是否为提升的函数声明(含 export 包装的函数声明)。 fn is_hoisted_function_decl(stmt: &Statement) -> bool { match stmt { @@ -513,13 +608,17 @@ impl Emitter { } // —— live 命名空间预注册:早于 hoisted 函数声明的 __moduleSet 与 body 求值, - // 按名序预注册全部本地导出名,使自导入 ns 在读点先观察到未初始化状态。 —— + // 按名序预注册全部本地导出名,使自导入 ns 在读点先观察到未初始化状态。 + // var/函数类导出(VarScopedDeclarations)在实例化期即初始化为 undefined, + // lexical/class 保持未初始化(TDZ)。 —— if has_self_ns_import { + let var_scoped = module_var_scoped_export_names(body); let mut export_names: Vec = own_export_names.into_iter().collect(); export_names.sort(); for name in export_names { let name_reg = self.load_string_const(&name, ctx); - self.emit_module_call(ctx, "__modulePreRegister", &[ns_reg, name_reg])?; + let var_like_reg = self.load_bool_const(var_scoped.contains(&name), ctx); + self.emit_module_call(ctx, "__modulePreRegister", &[ns_reg, name_reg, var_like_reg])?; } } @@ -667,6 +766,13 @@ impl Emitter { reg } + fn load_bool_const(&self, b: bool, ctx: &mut CompileCtx) -> u32 { + let idx = ctx.add_constant(Constant::Boolean(b)); + let reg = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(reg), idx)); + reg + } + /// 自导入别名回写:导出名若被本模块 self-import 绑定别名引用, /// export 语句执行时同步把值写回本地绑定槽。 fn emit_self_alias_write(&self, ctx: &mut CompileCtx, export_name: &str, value_reg: u32) { diff --git a/crates/oxide_vm/src/bindings/bind_global.rs b/crates/oxide_vm/src/bindings/bind_global.rs index 96ea8742..0ad335ad 100644 --- a/crates/oxide_vm/src/bindings/bind_global.rs +++ b/crates/oxide_vm/src/bindings/bind_global.rs @@ -31,7 +31,7 @@ pub fn bind_global(core: &Arc, session: &KernelSession, global: &mut ( "__modulePreRegister", oxide_builtins::module::module_pre_register:: as *const (), - 2, + 3, ), ("__moduleSet", oxide_builtins::module::module_set:: as *const (), 3), ( diff --git a/crates/oxide_vm/tests/module_namespace_tests.rs b/crates/oxide_vm/tests/module_namespace_tests.rs index 6de4e43b..d32f4052 100644 --- a/crates/oxide_vm/tests/module_namespace_tests.rs +++ b/crates/oxide_vm/tests/module_namespace_tests.rs @@ -144,7 +144,8 @@ fn namespace_exotic_semantics() { ); } -/// live 自导入 ns 的条目语义:预注册后初始化前读抛 ReferenceError,`in` / +/// live 自导入 ns 的条目语义:lexical/class 导出预注册后初始化前读抛 ReferenceError, +/// `export var` 在实例化期即初始化为 undefined(读得 undefined 不抛);`in` / /// `Reflect.has` 已为 true;初始化后读到活值;枚举未初始化名不抛错且键数正确。 /// 循环读 `ns.y` 同时钉住 IC 慢路径与普通路径读结果一致。 #[test] @@ -182,8 +183,8 @@ fn namespace_self_import_live_entries() { let result = run_namespace_module(&dir); assert_eq!( - result, "true|true|true;true,true,true;default,x,y;4;sx;256;3", - "live 自导入 ns 的未初始化读 / 键存在性 / 活值读不符" + result, "true|value|true;true,true,true;default,x,y;4;sx;256;3", + "live 自导入 ns 的未初始化读 / var 预初始化 / 键存在性 / 活值读不符" ); } @@ -295,6 +296,28 @@ fn namespace_write_through_covers_update_logical_destructuring() { assert_eq!(result, "2|7|9", "Update/逻辑/解构赋值的写穿缺失"); } +/// `export var` 属 VarScopedDeclarations,模块实例化期即初始化为 undefined: +/// body 语句执行前经命名空间读应得 undefined,而非 ReferenceError;`in` 已为 true。 +#[test] +fn namespace_export_var_preinitialized() { + let cwd = std::env::current_dir().expect("cwd"); + let dir = cwd.join("__module_namespace_var_preinit__"); + std::fs::create_dir_all(&dir).expect("mkdir"); + std::fs::write( + dir.join("main.mjs"), + "import * as ns from './main.mjs';\n \ + let before;\n \ + try { before = ns.y; } catch (e) { before = 'threw'; }\n \ + globalThis.__ns = [String(before), 'y' in ns].join('|');\n \ + export var y = 2;", + ) + .expect("write main"); + let _cleanup = Cleanup(dir.clone()); + + let result = run_namespace_module(&dir); + assert_eq!(result, "undefined|true", "export var 应在实例化期预初始化为 undefined"); +} + /// G2 反向守卫:外部导入的命名空间无条目表(非 live),枚举与 for-in 行为不变。 #[test] fn external_namespace_enumeration_stays_unaffected() {