fix: 模块 var 与函数导出实例化期预初始化

This commit is contained in:
2026-09-18 00:32:29 +00:00
parent 39183f449c
commit 1855775956
4 changed files with 164 additions and 11 deletions
+29 -5
View File
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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())
}
+108 -2
View File
@@ -153,6 +153,101 @@ fn module_own_export_names(body: &[Statement]) -> HashSet<String> {
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<String> {
let mut var_locals: HashSet<String> = 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<String> = 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) {
+1 -1
View File
@@ -31,7 +31,7 @@ pub fn bind_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
(
"__modulePreRegister",
oxide_builtins::module::module_pre_register::<crate::vm::Vm> as *const (),
2,
3,
),
("__moduleSet", oxide_builtins::module::module_set::<crate::vm::Vm> as *const (), 3),
(
@@ -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() {