mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 逻辑赋值标识符目标接入不可写全局内置拦截,目标位置内置预登记稳定槽解析
This commit is contained in:
@@ -399,21 +399,29 @@ impl Emitter {
|
||||
let is_const = ctx.lookup_const_flag(name);
|
||||
let const_flag = if is_const { 1 } else { 0 };
|
||||
let var_reg = ctx.lookup_or_global(name);
|
||||
// 隐式全局判定以寄存器集合为准:读侧(旧值解析)已登记绑定,
|
||||
// 写侧二次解析命中集合而非"新登记"。
|
||||
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
|
||||
if is_implicit && ctx.is_strict {
|
||||
// 严格模式未声明写:抛 ReferenceError,后续 LOAD_VAR 不可达。
|
||||
let _ = self.emit_strict_undeclared_write(name, ctx)?;
|
||||
} else {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::STORE_VAR,
|
||||
Operand::Reg(var_reg),
|
||||
Operand::Reg(val_reg),
|
||||
Operand::Imm(const_flag),
|
||||
));
|
||||
if is_implicit {
|
||||
self.emit_implicit_global_write(name, var_reg, ctx);
|
||||
// 全局不可写内置槽:strict 抛 TypeError(put 失败);sloppy 跳过槽写,
|
||||
// 表达式值 = RHS(尾部结果装载取 val_reg)。短路时序与 PutValue 一致:
|
||||
// 短路未通过不到达此处、不抛错,通过后才在写点拦截。
|
||||
let readonly_builtin = ctx.targets_readonly_builtin(name, var_reg);
|
||||
if readonly_builtin && ctx.is_strict {
|
||||
let _ = self.emit_throw_error("TypeError", "cannot assign to read-only property", ctx)?;
|
||||
} else if !readonly_builtin {
|
||||
// 隐式全局判定以寄存器集合为准:读侧(旧值解析)已登记绑定,
|
||||
// 写侧二次解析命中集合而非"新登记"。
|
||||
let is_implicit = ctx.implicit_global_writes.contains(&var_reg);
|
||||
if is_implicit && ctx.is_strict {
|
||||
// 严格模式未声明写:抛 ReferenceError,后续 LOAD_VAR 不可达。
|
||||
let _ = self.emit_strict_undeclared_write(name, ctx)?;
|
||||
} else {
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::STORE_VAR,
|
||||
Operand::Reg(var_reg),
|
||||
Operand::Reg(val_reg),
|
||||
Operand::Imm(const_flag),
|
||||
));
|
||||
if is_implicit {
|
||||
self.emit_implicit_global_write(name, var_reg, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +268,14 @@ impl Emitter {
|
||||
|
||||
fn pre_scan_builtin_target(&self, target: &oxide_parser::SimpleAssignmentTarget, ctx: &mut CompileCtx) {
|
||||
match target {
|
||||
oxide_parser::SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => {
|
||||
// 标识符目标与表达式位置标识符引用同口径预登记:目标为某内置名的
|
||||
// 唯一出现处(无读侧引用)时缺预登记会解析成隐式全局新槽,旧值读
|
||||
// 与短路判定拿到未预载值。
|
||||
if CompileCtx::is_known_builtin(id.name.as_str()) {
|
||||
let _ = ctx.lookup_or_builtin(id.name.as_str());
|
||||
}
|
||||
}
|
||||
oxide_parser::SimpleAssignmentTarget::StaticMemberExpression(member) => {
|
||||
self.pre_scan_builtin_expr(&member.object, ctx);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! 不可写全局内置(undefined/NaN/Infinity)写路径拦截测试:写命中全局不可写
|
||||
//! 内置绑定时编译期拦截——sloppy 静默 no-op(后续读回原值,全局对象不变),
|
||||
//! strict 抛 TypeError。覆盖简单/复合/更新赋值、声明(var 无初始化与带初始化)、
|
||||
//! 解构赋值、for-in/for-of 标识符目标与嵌套函数形态;局部遮蔽绑定(var/let/
|
||||
//! 参数)与其他可写内置(Math)不受影响。
|
||||
//! strict 抛 TypeError。覆盖简单/复合/更新/逻辑赋值、声明(var 无初始化与
|
||||
//! 带初始化)、解构赋值、for-in/for-of 标识符目标与嵌套函数形态;局部遮蔽
|
||||
//! 绑定(var/let/参数)与其他可写内置(Math)不受影响。逻辑赋值额外钉住
|
||||
//! 短路时序:短路未通过不发生 put(strict 不抛),通过才在写点拦截。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_types::value::JsValue;
|
||||
@@ -160,6 +161,68 @@ fn postfix_update_returns_old_value() {
|
||||
assert!(r.is_bool() && !r.as_bool(), "Infinity-- 后缀形式应返回旧值 Infinity,实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── 逻辑赋值(||=/&&=/??=)标识符目标:短路时序与 PutValue 对齐——短路未通过
|
||||
// 不发生 put(strict 不抛、表达式值 = LHS 旧值),通过才在写点拦截 ──
|
||||
#[test]
|
||||
fn logical_assign_sloppy_matrix_keeps_value() {
|
||||
// 三常量 × 三逻辑符:短路通过者静默 no-op 读回原值,短路未通过者不写。
|
||||
let cases: &[(&str, &str)] = &[
|
||||
("undefined ||= 5; '' + undefined", "undefined"),
|
||||
("undefined &&= 5; '' + undefined", "undefined"),
|
||||
("undefined ??= 5; '' + undefined", "undefined"),
|
||||
("NaN ||= 1; '' + NaN", "NaN"),
|
||||
("NaN &&= 1; '' + NaN", "NaN"),
|
||||
("NaN ??= 1; '' + NaN", "NaN"),
|
||||
("Infinity ||= 2; '' + Infinity", "Infinity"),
|
||||
("Infinity &&= 2; '' + Infinity", "Infinity"),
|
||||
("Infinity ??= 2; '' + Infinity", "Infinity"),
|
||||
];
|
||||
for &(source, expected) in cases {
|
||||
let s = eval_str(source);
|
||||
assert_eq!(s, expected, "sloppy 逻辑赋值命中不可写内置应静默 no-op,读回原值,源码: {source}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logical_assign_strict_write_path_throws() {
|
||||
// 短路通过(||=/??= 遇 falsy/nullish、&&= 遇 truthy)到达写点:put 失败抛 TypeError。
|
||||
let sources = [
|
||||
"'use strict'; undefined ||= 5",
|
||||
"'use strict'; undefined ??= 5",
|
||||
"'use strict'; NaN ||= 1",
|
||||
"'use strict'; Infinity &&= 2",
|
||||
];
|
||||
for source in sources {
|
||||
let result = eval(source);
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logical_assign_strict_short_circuit_no_throw() {
|
||||
// 短路未通过不发生 put:strict 不抛错,表达式值 = LHS 旧值。
|
||||
let cases: &[(&str, &str)] = &[
|
||||
("'use strict'; '' + (undefined &&= 5)", "undefined"),
|
||||
("'use strict'; '' + (NaN &&= 1)", "NaN"),
|
||||
("'use strict'; '' + (NaN ??= 1)", "NaN"),
|
||||
("'use strict'; '' + (Infinity ||= 2)", "Infinity"),
|
||||
("'use strict'; '' + (Infinity ??= 2)", "Infinity"),
|
||||
];
|
||||
for &(source, expected) in cases {
|
||||
let s = eval_str(source);
|
||||
assert_eq!(
|
||||
s, expected,
|
||||
"逻辑赋值短路未通过不 put,strict 不应抛错且表达式值 = LHS 旧值,源码: {source}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logical_or_assign_expression_value_is_rhs() {
|
||||
let s = eval_str("(undefined ||= 5) + ':' + undefined");
|
||||
assert_eq!(s, "5:undefined", "逻辑赋值写点表达式值应为 RHS,槽值保持原值");
|
||||
}
|
||||
|
||||
// ── 解构赋值目标:命中项静默跳过,strict 抛错 ──
|
||||
#[test]
|
||||
fn destructure_assign_keeps_value() {
|
||||
@@ -192,6 +255,25 @@ fn for_of_target_strict_empty_no_throw() {
|
||||
assert!(r.is_bool() && r.as_bool(), "空集合 for-of 不发生 put,strict 不应抛错,实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── for-in 标识符目标:键写不污染槽值,写仅在 NEXT 产生键后发生 ──
|
||||
#[test]
|
||||
fn for_in_target_keeps_value() {
|
||||
let s = eval_str("for (undefined in {a: 1}) {} '' + undefined");
|
||||
assert_eq!(s, "undefined", "sloppy for-in 目标 undefined 应静默 no-op,读回原值");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_target_strict_throws_type_error() {
|
||||
let result = eval("'use strict'; for (undefined in {a: 1}) {}");
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_in_target_strict_empty_no_throw() {
|
||||
let r = eval("'use strict'; for (undefined in {}) {} true").unwrap();
|
||||
assert!(r.is_bool() && r.as_bool(), "空对象 for-in 不发生 put,strict 不应抛错,实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── for 头 var 无初始化:不抹预载槽值(NaN 保留) ──
|
||||
#[test]
|
||||
fn for_header_var_no_init_keeps_slot_value() {
|
||||
|
||||
Reference in New Issue
Block a user