mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 模块写穿按绑定槽过滤并覆盖更新赋值路径
This commit is contained in:
@@ -137,10 +137,11 @@ pub struct CompileCtx {
|
||||
pub(crate) for_head_store_registers: HashSet<String>,
|
||||
/// 模块编译上下文:当前模块命名空间对象寄存器(`__moduleObject` 返回值)。
|
||||
pub(crate) module_ns_reg: Option<u32>,
|
||||
/// 写穿反演表:本模块源绑定名 → 引用它的导出名集合。仅自导入 ns 的 live 模块
|
||||
/// 填充;顶层对该源绑定的赋值据此同步命名空间条目。嵌套函数 ctx 为新建,
|
||||
/// 不继承本字段,写穿天然只作用于模块顶层。
|
||||
pub(crate) module_local_exports: HashMap<String, Vec<String>>,
|
||||
/// 写穿反演表:本模块源绑定槽寄存器 → 引用它的导出名集合。仅自导入 ns 的
|
||||
/// live 模块填充;顶层赋值按解析后的绑定槽命中此表时同步命名空间条目。
|
||||
/// 以槽位而非名字为键,块级/catch 同名遮蔽解析到不同槽位,天然不误触发。
|
||||
/// 嵌套函数 ctx 为新建,不继承本字段,写穿只作用于模块顶层。
|
||||
pub(crate) module_local_export_regs: HashMap<u32, Vec<String>>,
|
||||
/// 已求值依赖模块的命名空间对象寄存器(按 import/export source 字符串索引)。
|
||||
pub(crate) module_dep_ns_regs: HashMap<String, u32>,
|
||||
/// 依赖模块规范路径(按 import/export source 字符串索引):再导出来源身份用。
|
||||
@@ -235,7 +236,7 @@ impl CompileCtx {
|
||||
register_update_names: Vec::new(),
|
||||
for_head_store_registers: HashSet::new(),
|
||||
module_ns_reg: None,
|
||||
module_local_exports: HashMap::new(),
|
||||
module_local_export_regs: HashMap::new(),
|
||||
module_dep_ns_regs: HashMap::new(),
|
||||
module_dep_paths: HashMap::new(),
|
||||
module_import_origins: HashMap::new(),
|
||||
|
||||
@@ -474,6 +474,8 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 逻辑赋值仅在短路通过后到达此处:写穿同步命名空间条目。
|
||||
self.emit_module_write_through(name, val_reg, ctx)?;
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_VAR,
|
||||
Operand::Reg(result_reg),
|
||||
|
||||
@@ -674,6 +674,7 @@ impl Emitter {
|
||||
Operand::Reg(new_reg),
|
||||
Operand::Imm(uv as u16),
|
||||
));
|
||||
self.emit_module_write_through(name, new_reg, ctx)?;
|
||||
Ok(if update.prefix { new_reg } else { old_reg })
|
||||
} else if let Some(cell_idx) = captured_cell {
|
||||
// 被捕获 cell:CELL_GET 旧值 + 常量 1 + ADD/SUB + CELL_SET。
|
||||
@@ -710,6 +711,7 @@ impl Emitter {
|
||||
Operand::Reg(new_reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
self.emit_module_write_through(name, new_reg, ctx)?;
|
||||
Ok(if update.prefix { new_reg } else { old_reg })
|
||||
} else {
|
||||
let var_reg = ctx.lookup_or_global(name);
|
||||
@@ -766,6 +768,9 @@ impl Emitter {
|
||||
// 可写内置名:RMW 新值同步落全局对象属性。
|
||||
self.emit_global_put_write(name, var_reg, ctx);
|
||||
}
|
||||
// 槽位寄存器 rd 承载更新后的新值:后缀形式 result_reg 保留旧值,
|
||||
// 写穿必须取 var_reg,否则命名空间条目被写回旧值。
|
||||
self.emit_module_write_through(name, var_reg, ctx)?;
|
||||
Ok(result_reg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,18 +496,19 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
// —— 写穿反演:源绑定名 → 导出名集合。仅 live 模块(自导入 ns)填充:
|
||||
// 非 live 模块不激活写穿,编译产物逐字节零 IR 变化。按名排序保证多导出名
|
||||
// 映射到同一源绑定时的发射顺序确定。——
|
||||
// —— 写穿反演:源绑定槽寄存器 → 导出名集合。仅 live 模块(自导入 ns)填充:
|
||||
// 非 live 模块不激活写穿,编译产物逐字节零 IR 变化。以槽位为键使块级/catch
|
||||
// 同名遮蔽解析到内层槽时不命中;按名排序保证多导出名映射到同一源绑定的
|
||||
// 发射顺序确定。——
|
||||
if has_self_ns_import {
|
||||
let mut pairs: Vec<(&String, &String)> =
|
||||
export_name_map.iter().map(|(exported, source)| (source, exported)).collect();
|
||||
pairs.sort();
|
||||
for (source, exported) in pairs {
|
||||
ctx.module_local_exports
|
||||
.entry(source.clone())
|
||||
.or_default()
|
||||
.push(exported.clone());
|
||||
let Some(reg) = ctx.scopes.symbols.lookup_any(source) else {
|
||||
continue;
|
||||
};
|
||||
ctx.module_local_export_regs.entry(reg).or_default().push(exported.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,8 +711,10 @@ impl Emitter {
|
||||
/// 顶层对导出源绑定的写入同步到命名空间:按反演表把新值写入每个导出名。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 仅在模块顶层 live 模块激活:`module_local_exports` 只由自导入 ns 模块填充,
|
||||
/// 嵌套函数 ctx 不继承该表,行为自然为零;`name` 非导出源时直接返回。
|
||||
/// - 仅在模块顶层 live 模块激活:`module_local_export_regs` 只由自导入 ns 模块
|
||||
/// 填充,嵌套函数 ctx 不继承该表,行为自然为零。
|
||||
/// - `name` 按当前作用域解析到绑定槽,槽不在表内(未声明名、块级/catch 同名
|
||||
/// 遮蔽等)时直接返回,不误写同名导出。
|
||||
/// - 调用方保证 `value_reg` 是写入后的当前值;const 抛错分支不得调用本函数。
|
||||
///
|
||||
/// # 副作用
|
||||
@@ -722,7 +725,10 @@ impl Emitter {
|
||||
let Some(ns_reg) = ctx.module_ns_reg else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(export_names) = ctx.module_local_exports.get(name).cloned() else {
|
||||
let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(export_names) = ctx.module_local_export_regs.get(&binding.reg).cloned() else {
|
||||
return Ok(());
|
||||
};
|
||||
for export_name in export_names {
|
||||
|
||||
@@ -179,6 +179,8 @@ impl Emitter {
|
||||
// 可写内置名:值同步落全局对象属性。
|
||||
self.emit_global_put_write(name, var_reg, ctx);
|
||||
}
|
||||
// live 模块顶层解构赋值目标同样写穿命名空间条目。
|
||||
self.emit_module_write_through(name, src_reg, ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
AssignmentTarget::ArrayAssignmentTarget(ap) => self.emit_array_assignment(ap, src_reg, ctx),
|
||||
@@ -390,6 +392,8 @@ impl Emitter {
|
||||
// 可写内置名:值同步落全局对象属性。
|
||||
self.emit_global_put_write(name, var_reg, ctx);
|
||||
}
|
||||
// live 模块顶层解构赋值目标同样写穿命名空间条目。
|
||||
self.emit_module_write_through(name, src_reg, ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err("assignment target not supported".into()),
|
||||
@@ -483,6 +487,8 @@ impl Emitter {
|
||||
// 可写内置名:值同步落全局对象属性(其余属性继续赋值)。
|
||||
self.emit_global_put_write(name, var_reg, ctx);
|
||||
}
|
||||
// live 模块顶层解构赋值目标同样写穿命名空间条目。
|
||||
self.emit_module_write_through(name, prop_reg, ctx)?;
|
||||
}
|
||||
AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => {
|
||||
let (prop_reg, static_key, key_reg) =
|
||||
|
||||
@@ -248,6 +248,53 @@ fn namespace_assignment_write_through_multiple_export_names() {
|
||||
assert_eq!(result, "2|2|3", "导出源绑定重赋未写穿命名空间活值");
|
||||
}
|
||||
|
||||
/// 写穿以解析后的绑定身份为准:块级/catch 同名遮蔽的赋值不得污染模块导出。
|
||||
/// 遮蔽名解析到内层绑定槽,与导出源绑定槽不同,写穿须按槽位过滤而非仅按名字。
|
||||
#[test]
|
||||
fn namespace_write_through_respects_block_shadowing() {
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
let dir = cwd.join("__module_namespace_shadow__");
|
||||
std::fs::create_dir_all(&dir).expect("mkdir");
|
||||
std::fs::write(
|
||||
dir.join("main.mjs"),
|
||||
"import * as ns from './main.mjs';\n \
|
||||
export let x = 1;\n \
|
||||
{ let x = 2; x = 3; }\n \
|
||||
try { throw 0; } catch (x) { x = 4; }\n \
|
||||
globalThis.__ns = [ns.x, x].join('|');",
|
||||
)
|
||||
.expect("write main");
|
||||
let _cleanup = Cleanup(dir.clone());
|
||||
|
||||
let result = run_namespace_module(&dir);
|
||||
assert_eq!(result, "1|1", "块级/catch 同名遮蔽的赋值不得写穿命名空间条目");
|
||||
}
|
||||
|
||||
/// 写穿覆盖 Update 表达式、逻辑赋值与解构赋值三条发射路径,与简单/二元复合
|
||||
/// 赋值一致地把新值同步到命名空间条目。
|
||||
#[test]
|
||||
fn namespace_write_through_covers_update_logical_destructuring() {
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
let dir = cwd.join("__module_namespace_write_paths__");
|
||||
std::fs::create_dir_all(&dir).expect("mkdir");
|
||||
std::fs::write(
|
||||
dir.join("main.mjs"),
|
||||
"import * as ns from './main.mjs';\n \
|
||||
export let a = 1;\n \
|
||||
export let b = 0;\n \
|
||||
export let c = 1;\n \
|
||||
a++;\n \
|
||||
b ||= 7;\n \
|
||||
[c] = [9];\n \
|
||||
globalThis.__ns = [ns.a, ns.b, ns.c].join('|');",
|
||||
)
|
||||
.expect("write main");
|
||||
let _cleanup = Cleanup(dir.clone());
|
||||
|
||||
let result = run_namespace_module(&dir);
|
||||
assert_eq!(result, "2|7|9", "Update/逻辑/解构赋值的写穿缺失");
|
||||
}
|
||||
|
||||
/// G2 反向守卫:外部导入的命名空间无条目表(非 live),枚举与 for-in 行为不变。
|
||||
#[test]
|
||||
fn external_namespace_enumeration_stays_unaffected() {
|
||||
|
||||
Reference in New Issue
Block a user