mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 写路径四路加函数名不可写守卫,sloppy 静默 strict 抛 TypeError
This commit is contained in:
Generated
+19
-19
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1226,7 +1226,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1235,7 +1235,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1255,7 +1255,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1269,7 +1269,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1284,7 +1284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1296,7 +1296,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1305,7 +1305,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1319,7 +1319,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1331,7 +1331,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1340,7 +1340,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1355,7 +1355,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1371,7 +1371,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1384,7 +1384,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1402,7 +1402,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1415,7 +1415,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.13.20"
|
||||
version = "0.13.21"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -116,6 +116,9 @@ pub struct CompileCtx {
|
||||
/// 本函数从父函数捕获的 const 绑定名:子 ctx 不继承父函数作用域符号表,
|
||||
/// 捕获 const 信息随 upvalue 收集一并快照,供 const 写检查(编译期拦截)使用。
|
||||
pub(crate) upvalue_const_flags: HashSet<String>,
|
||||
/// 本函数从父函数捕获的函数名不可写绑定名:子 ctx 不继承父函数作用域符号表,
|
||||
/// 捕获不可写信息随 upvalue 收集一并快照,供写路径 non_writable 守卫(编译期拦截)使用。
|
||||
pub(crate) upvalue_non_writable_flags: HashSet<String>,
|
||||
/// 未声明标识符读所分配的全局槽寄存器集合:标识符首次读未命中任何作用域时,
|
||||
/// `lookup_or_builtin` 按隐式全局登记并记录其寄存器,后续读取据此发射
|
||||
/// LOAD_GLOBAL(运行期查 global object 属性,缺失抛 ReferenceError)。
|
||||
@@ -286,6 +289,7 @@ impl CompileCtx {
|
||||
pending_for_head_names: HashSet::new(),
|
||||
global_tier_names: HashSet::new(),
|
||||
upvalue_const_flags: HashSet::new(),
|
||||
upvalue_non_writable_flags: HashSet::new(),
|
||||
implicit_global_reads: HashSet::new(),
|
||||
implicit_global_writes: HashSet::new(),
|
||||
function_length: 0,
|
||||
@@ -568,6 +572,12 @@ impl CompileCtx {
|
||||
self.scopes.symbols.lookup_is_const(name) || self.upvalue_const_flags.contains(name)
|
||||
}
|
||||
|
||||
/// 写路径 non_writable 守卫查询:名字是否为函数名不可写绑定(本作用域或 upvalue 捕获)。
|
||||
pub(crate) fn lookup_non_writable_flag(&self, name: &str) -> bool {
|
||||
// upvalue 捕获的函数名:子 ctx 符号表不含父函数作用域绑定,查快照标志。
|
||||
self.scopes.symbols.lookup_non_writable(name) || self.upvalue_non_writable_flags.contains(name)
|
||||
}
|
||||
|
||||
pub(crate) fn init_var(&mut self, name: &str) {
|
||||
self.scopes.symbols.init_var(name);
|
||||
}
|
||||
|
||||
@@ -72,10 +72,16 @@ impl Emitter {
|
||||
/// const 写检查:已初始化的 const 绑定再赋值编译期抛 TypeError(与槽值无关)。
|
||||
/// 简单赋值在 RHS 求值之后、复合/更新在读旧值之前调用;解构赋值在写目标时调用。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 函数名不可写绑定同形拦截:strict 抛 TypeError;sloppy 不抛(put 静默失败),
|
||||
/// 由调用点跳过写指令(值照算)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - const 命中时发射 THROW 指令序列,其后写指令不可达但保持寄存器良定义。
|
||||
/// - const 或 strict 函数名不可写命中时发射 THROW 指令序列,其后写指令不可达但保持寄存器良定义。
|
||||
pub(crate) fn emit_const_write_guard(&self, name: &str, ctx: &mut CompileCtx) -> Result<(), String> {
|
||||
if ctx.lookup_const_flag(name) {
|
||||
// const 与 strict 函数名不可写同形抛 TypeError(值无关);sloppy 函数名不可写不抛,
|
||||
// 由调用点跳过写指令。
|
||||
if ctx.lookup_const_flag(name) || (ctx.lookup_non_writable_flag(name) && ctx.is_strict) {
|
||||
let _ = self.emit_throw_error("TypeError", "Assignment to constant variable", ctx)?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -24,6 +24,32 @@ impl Emitter {
|
||||
// 循环 update 段的 let/const 循环变量是 per-iteration 可变绑定,复合写同样合法,豁免。
|
||||
if !ctx.register_update_names.iter().any(|n| n == name) {
|
||||
self.emit_const_write_guard(name, ctx)?;
|
||||
// 函数名不可写绑定:put 永不成功——sloppy 值照算(表达式值 = 运算结果),
|
||||
// 跳过槽写;strict 已在 emit_const_write_guard 抛 TypeError(不可达)。
|
||||
if ctx.lookup_non_writable_flag(name) {
|
||||
let val_reg = ctx.alloc_reg();
|
||||
let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name);
|
||||
if let Some(uv) = uv_idx {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_UPVALUE,
|
||||
Operand::Reg(val_reg),
|
||||
Operand::Imm(uv as u16),
|
||||
Operand::None,
|
||||
));
|
||||
} else if let Some((cell_idx, binding_reg)) = ctx.visible_cell(name) {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_GET,
|
||||
Operand::Reg(val_reg),
|
||||
Operand::Reg(binding_reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
} else {
|
||||
let var_reg = ctx.lookup_or_global(name);
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(val_reg), Operand::Reg(var_reg), Operand::None));
|
||||
}
|
||||
ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(rhs), Operand::None));
|
||||
return Ok(val_reg);
|
||||
}
|
||||
}
|
||||
// 循环 update 段:被捕获绑定走寄存器 RMW——C 风格 for 的 let/const
|
||||
// 循环变量每迭代新分配一个 cell,update 写寄存器供其拷入,不污染本迭代
|
||||
@@ -433,6 +459,19 @@ impl Emitter {
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
// 短路未通过才写:const 目标在此抛 TypeError(编译期拦截,值无关)。
|
||||
self.emit_const_write_guard(name, ctx)?;
|
||||
// 函数名不可写绑定(sloppy):put 永不成功,跳过写与命名空间写穿,
|
||||
// 表达式值 = RHS(短路已通过故取 val_reg)。strict 已在
|
||||
// emit_const_write_guard 抛 TypeError(不可达)。
|
||||
if ctx.lookup_non_writable_flag(name) {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_VAR,
|
||||
Operand::Reg(result_reg),
|
||||
Operand::Reg(val_reg),
|
||||
Operand::None,
|
||||
));
|
||||
ctx.labels.set_label_pos(end_label, ctx.insts.len());
|
||||
return Ok(result_reg);
|
||||
}
|
||||
let const_flag = if ctx.lookup_const_flag(name) { 1 } else { 0 };
|
||||
if let Some(uv) = uv_idx {
|
||||
ctx.inst(Inst::new(
|
||||
|
||||
@@ -181,6 +181,14 @@ impl Emitter {
|
||||
let _ = self.emit_throw_error("TypeError", "Assignment to constant variable", ctx)?;
|
||||
return Ok(());
|
||||
}
|
||||
// 函数名不可写绑定:put 永不成功——sloppy 静默跳过写指令(RHS 已求值,副作用保留),
|
||||
// strict 抛 TypeError(与 const 路径同形,值无关)。with 回退写经本入口同形覆盖。
|
||||
if !in_loop_update && ctx.lookup_non_writable_flag(name) {
|
||||
if ctx.is_strict {
|
||||
let _ = self.emit_throw_error("TypeError", "Assignment to constant variable", ctx)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
// 循环 update 段:被捕获绑定走寄存器而非 cell——C 风格 for 的 let/const
|
||||
// 循环变量每迭代新分配一个 cell,update 写寄存器供其拷入,不污染本迭代
|
||||
// 闭包捕获的 cell(机制见 `compile_ctx.rs` 的 `register_update_names` 字段文档)。
|
||||
|
||||
@@ -659,6 +659,41 @@ impl Emitter {
|
||||
self.emit_identifier_tdz_guard(name, ctx)?;
|
||||
if !ctx.register_update_names.iter().any(|n| n == name) {
|
||||
self.emit_const_write_guard(name, ctx)?;
|
||||
// 函数名不可写绑定(sloppy):put 永不成功,值照算(前缀返回新值、后缀返回
|
||||
// 旧值),跳过槽写;strict 已在 emit_const_write_guard 抛 TypeError(不可达)。
|
||||
if ctx.lookup_non_writable_flag(name) {
|
||||
let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name);
|
||||
let old_reg = ctx.alloc_reg();
|
||||
if let Some(uv) = uv_idx {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::LOAD_UPVALUE,
|
||||
Operand::Reg(old_reg),
|
||||
Operand::Imm(uv as u16),
|
||||
Operand::None,
|
||||
));
|
||||
} else if let Some((cell_idx, binding_reg)) = ctx.visible_cell(name) {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::CELL_GET,
|
||||
Operand::Reg(old_reg),
|
||||
Operand::Reg(binding_reg),
|
||||
Operand::Imm(cell_idx as u16),
|
||||
));
|
||||
} else {
|
||||
let var_reg = ctx.lookup_or_global(name);
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(old_reg), Operand::Reg(var_reg), Operand::None));
|
||||
}
|
||||
let one_idx = ctx.add_constant(Constant::Int(1));
|
||||
let one_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(one_reg), one_idx));
|
||||
let op = if update.operator == UpdateOperator::Increment {
|
||||
OpCode::ADD
|
||||
} else {
|
||||
OpCode::SUB
|
||||
};
|
||||
let new_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(op, Operand::Reg(new_reg), Operand::Reg(old_reg), Operand::Reg(one_reg)));
|
||||
return Ok(if update.prefix { new_reg } else { old_reg });
|
||||
}
|
||||
}
|
||||
let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name);
|
||||
// 循环 update 段:被捕获绑定走寄存器 INC/DEC——C 风格 for 的 let/const
|
||||
|
||||
@@ -826,6 +826,22 @@ impl Emitter {
|
||||
})
|
||||
.map(|u| u.name.clone())
|
||||
.collect();
|
||||
// 捕获函数名不可写信息快照:与 const 快照同口径,供子函数写路径
|
||||
// non_writable 守卫(sloppy 静默 no-op / strict 抛 TypeError)使用。
|
||||
ctx.upvalue_non_writable_flags = ctx
|
||||
.current_upvalue_captures
|
||||
.iter()
|
||||
.filter(|u| u.parent_uv_idx.is_none())
|
||||
.filter(|u| {
|
||||
parent_ctx
|
||||
.scopes
|
||||
.symbols
|
||||
.lookup_any_binding(u.name.as_str())
|
||||
.map(|(b, _)| b.non_writable)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|u| u.name.clone())
|
||||
.collect();
|
||||
// 类字段 computed key 数组等合成捕获:直接追加 upvalue(cell_idx 由父分配)。
|
||||
for (name, cell_idx) in extra_upvalue_names {
|
||||
if !ctx.own_bindings.contains(*name) && !ctx.current_upvalue_captures.iter().any(|u| u.name == *name) {
|
||||
|
||||
@@ -391,8 +391,9 @@ fn cross_module_aliased_imports_read_live_value() {
|
||||
}
|
||||
|
||||
/// 默认导出具名函数体的自引用重赋:函数名 `fn` 在函数作用域登记为不可写
|
||||
/// 绑定(15.2.10.1 步 m),遮蔽模块级同名绑定;体内容器 `fn = 2` 落在函数
|
||||
/// 作用域名绑定,模块级绑定不被改写,导入方读 default 仍得函数。
|
||||
/// 绑定(15.2.10.1 步 m),遮蔽模块级同名绑定;模块恒为严格模式,体内容器
|
||||
/// `fn = 2` 对不可写绑定抛 TypeError,模块级绑定不被改写,导入方读 default
|
||||
/// 仍得函数。
|
||||
#[test]
|
||||
fn cross_module_default_export_reads_live_value() {
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
@@ -402,12 +403,16 @@ fn cross_module_default_export_reads_live_value() {
|
||||
std::fs::write(
|
||||
dir.join("main.mjs"),
|
||||
"import val from './dep.mjs';\n \
|
||||
const ret = val();\n \
|
||||
globalThis.__ns = [ret, typeof val].join('|');",
|
||||
let threw = false;\n \
|
||||
try { val(); } catch (e) { threw = e instanceof TypeError; }\n \
|
||||
globalThis.__ns = [threw, typeof val].join('|');",
|
||||
)
|
||||
.expect("write main");
|
||||
let _cleanup = Cleanup(dir.clone());
|
||||
|
||||
let result = run_namespace_module(&dir);
|
||||
assert_eq!(result, "1|function", "函数名不可写绑定遮蔽模块级绑定,自引用重赋静默失败");
|
||||
assert_eq!(
|
||||
result, "true|function",
|
||||
"严格模块内函数名不可写绑定自引用重赋抛 TypeError,模块级绑定不被改写"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -132,12 +132,13 @@ fn switch_case_function_captured_by_outer() {
|
||||
|
||||
#[test]
|
||||
fn switch_case_function_binding_independent_of_outer_var() {
|
||||
// 块绑定可变且与外层 var 独立:块内对 f 重新赋值不改写外层 var 绑定。
|
||||
// 函数名不可写绑定(15.2.10.1 步 m):体内容器对 f 重新赋值静默失败(sloppy),
|
||||
// 块内 f 仍为函数对象;外层 var 绑定独立,不被改写。
|
||||
let src = "var r1, r2; (function(){ var initialBV, currentBV; \
|
||||
switch(1){case 1: function f(){ initialBV=f; f=123; currentBV=f; return 'decl'; }} \
|
||||
var varBinding=f; f(); r1 = initialBV() === 'decl'; \
|
||||
r2 = currentBV === 123 && varBinding() === 'decl'; }()); r1 && r2";
|
||||
assert!(eval(src).unwrap().as_bool(), "块绑定应独立于外层 var 绑定");
|
||||
r2 = currentBV === f && varBinding() === 'decl'; }()); r1 && r2";
|
||||
assert!(eval(src).unwrap().as_bool(), "函数名不可写绑定自引用重赋静默失败,外层 var 绑定独立");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user