mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 删后复合逻辑赋值与 update 表达式 LHS 旧值读抛 ReferenceError
This commit is contained in:
Generated
+18
-18
@@ -1095,7 +1095,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_builtins"
|
name = "oxide_builtins"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"fast-float",
|
"fast-float",
|
||||||
@@ -1120,7 +1120,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_bytecode"
|
name = "oxide_bytecode"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"oxide_log",
|
"oxide_log",
|
||||||
@@ -1128,7 +1128,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_cfg"
|
name = "oxide_cfg"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_emit",
|
"oxide_emit",
|
||||||
@@ -1139,7 +1139,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_cli"
|
name = "oxide_cli"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
@@ -1159,7 +1159,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_code_cache"
|
name = "oxide_code_cache"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lru",
|
"lru",
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
@@ -1168,7 +1168,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_compiler"
|
name = "oxide_compiler"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1187,7 +1187,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_dce"
|
name = "oxide_dce"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1201,7 +1201,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_emit"
|
name = "oxide_emit"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"oxc_allocator",
|
"oxc_allocator",
|
||||||
@@ -1216,7 +1216,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_ir"
|
name = "oxide_ir"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_log",
|
"oxide_log",
|
||||||
@@ -1225,7 +1225,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_kernel"
|
name = "oxide_kernel"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -1240,7 +1240,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_liveness"
|
name = "oxide_liveness"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1252,7 +1252,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_log"
|
name = "oxide_log"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
@@ -1261,7 +1261,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_parser"
|
name = "oxide_parser"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxc_allocator",
|
"oxc_allocator",
|
||||||
"oxc_ast",
|
"oxc_ast",
|
||||||
@@ -1276,7 +1276,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_regalloc"
|
name = "oxide_regalloc"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1292,7 +1292,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_runtime_api"
|
name = "oxide_runtime_api"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@@ -1304,7 +1304,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_test262"
|
name = "oxide_test262"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_compiler",
|
"oxide_compiler",
|
||||||
@@ -1321,7 +1321,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_types"
|
name = "oxide_types"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
@@ -1333,7 +1333,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_vm"
|
name = "oxide_vm"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
|
|||||||
@@ -99,12 +99,15 @@ impl Emitter {
|
|||||||
}
|
}
|
||||||
let is_tier = self.is_global_tier_name(ctx, name);
|
let is_tier = self.is_global_tier_name(ctx, name);
|
||||||
let is_implicit = ctx.is_implicit_global_reg(var_reg);
|
let is_implicit = ctx.is_implicit_global_reg(var_reg);
|
||||||
|
let is_builtin = ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name);
|
||||||
if is_implicit && ctx.is_strict {
|
if is_implicit && ctx.is_strict {
|
||||||
// 严格模式未声明复合写:未解析引用不可 put,值无关抛 ReferenceError。
|
// 严格模式未声明复合写:未解析引用不可 put,值无关抛 ReferenceError。
|
||||||
return self.emit_strict_undeclared_write(name, ctx);
|
return self.emit_strict_undeclared_write(name, ctx);
|
||||||
}
|
}
|
||||||
// RMW 前从全局对象属性取旧值:tier 名(顶层已声明 var)与未声明名同,
|
// RMW 前从全局对象属性取旧值:tier 名(顶层已声明 var)与未声明名同,
|
||||||
// 属性缺失按 undefined(GetBaseValue 语义,不抛)。
|
// 属性缺失按 undefined(GetBaseValue 语义,不抛);已知 builtin 名(非局部
|
||||||
|
// 遮蔽)读 A 侧属性,属性缺失(delete 真删后)抛 ReferenceError,与裸读
|
||||||
|
// 同形——镜像槽不反映删除,无法自区分缺位与在位 undefined。
|
||||||
if is_tier || is_implicit {
|
if is_tier || is_implicit {
|
||||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||||
ctx.inst(Inst::new(
|
ctx.inst(Inst::new(
|
||||||
@@ -113,6 +116,14 @@ impl Emitter {
|
|||||||
Operand::Const(key_idx),
|
Operand::Const(key_idx),
|
||||||
Operand::None,
|
Operand::None,
|
||||||
));
|
));
|
||||||
|
} else if is_builtin {
|
||||||
|
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||||
|
ctx.inst(Inst::new(
|
||||||
|
OpCode::LOAD_GLOBAL,
|
||||||
|
Operand::Reg(var_reg),
|
||||||
|
Operand::Const(key_idx),
|
||||||
|
Operand::None,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
ctx.inst(Inst::new(op, Operand::Reg(var_reg), Operand::Reg(rhs), Operand::None));
|
ctx.inst(Inst::new(op, Operand::Reg(var_reg), Operand::Reg(rhs), Operand::None));
|
||||||
if is_tier {
|
if is_tier {
|
||||||
@@ -389,7 +400,10 @@ impl Emitter {
|
|||||||
} else {
|
} else {
|
||||||
let var_reg = ctx.lookup_or_global(name);
|
let var_reg = ctx.lookup_or_global(name);
|
||||||
// 顶层已声明 var(tier)与未声明名同:旧值读前从全局对象
|
// 顶层已声明 var(tier)与未声明名同:旧值读前从全局对象
|
||||||
// 属性取当前值,缺失按 undefined(不抛)。
|
// 属性取当前值,缺失按 undefined(不抛);已知 builtin 名
|
||||||
|
// (非局部遮蔽)读 A 侧属性,属性缺失(delete 真删后)抛
|
||||||
|
// ReferenceError,与裸读同形——镜像槽不反映删除,无法自
|
||||||
|
// 区分缺位与在位 undefined。
|
||||||
if self.is_global_tier_name(ctx, name) || ctx.is_implicit_global_reg(var_reg) {
|
if self.is_global_tier_name(ctx, name) || ctx.is_implicit_global_reg(var_reg) {
|
||||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||||
ctx.inst(Inst::new(
|
ctx.inst(Inst::new(
|
||||||
@@ -398,6 +412,14 @@ impl Emitter {
|
|||||||
Operand::Const(key_idx),
|
Operand::Const(key_idx),
|
||||||
Operand::None,
|
Operand::None,
|
||||||
));
|
));
|
||||||
|
} else if ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name) {
|
||||||
|
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||||
|
ctx.inst(Inst::new(
|
||||||
|
OpCode::LOAD_GLOBAL,
|
||||||
|
Operand::Reg(var_reg),
|
||||||
|
Operand::Const(key_idx),
|
||||||
|
Operand::None,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
ctx.inst(Inst::new(
|
ctx.inst(Inst::new(
|
||||||
OpCode::LOAD_VAR,
|
OpCode::LOAD_VAR,
|
||||||
|
|||||||
@@ -830,12 +830,16 @@ impl Emitter {
|
|||||||
}
|
}
|
||||||
let is_tier = self.is_global_tier_name(ctx, name);
|
let is_tier = self.is_global_tier_name(ctx, name);
|
||||||
let is_implicit = ctx.is_implicit_global_reg(var_reg);
|
let is_implicit = ctx.is_implicit_global_reg(var_reg);
|
||||||
|
let is_builtin = ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name);
|
||||||
if is_implicit && ctx.is_strict {
|
if is_implicit && ctx.is_strict {
|
||||||
// 严格模式未声明更新写:值无关抛 ReferenceError。
|
// 严格模式未声明更新写:值无关抛 ReferenceError。
|
||||||
return self.emit_strict_undeclared_write(name, ctx);
|
return self.emit_strict_undeclared_write(name, ctx);
|
||||||
}
|
}
|
||||||
// RMW 前从全局对象属性取旧值:tier 名(顶层已声明 var)与未声明名同,
|
// RMW 前从全局对象属性取旧值:tier 名(顶层已声明 var)与未声明名同,
|
||||||
// 属性缺失按 undefined(update 旧值角落,GetBaseValue 语义,不抛)。
|
// 属性缺失按 undefined(update 旧值角落,GetBaseValue 语义,不抛);
|
||||||
|
// 已知 builtin 名(非局部遮蔽)读 A 侧属性,属性缺失(delete 真删后)
|
||||||
|
// 抛 ReferenceError,与裸读同形——镜像槽不反映删除,无法自区分缺位
|
||||||
|
// 与在位 undefined。
|
||||||
if is_tier || is_implicit {
|
if is_tier || is_implicit {
|
||||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||||
ctx.inst(Inst::new(
|
ctx.inst(Inst::new(
|
||||||
@@ -844,6 +848,14 @@ impl Emitter {
|
|||||||
Operand::Const(key_idx),
|
Operand::Const(key_idx),
|
||||||
Operand::None,
|
Operand::None,
|
||||||
));
|
));
|
||||||
|
} else if is_builtin {
|
||||||
|
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||||
|
ctx.inst(Inst::new(
|
||||||
|
OpCode::LOAD_GLOBAL,
|
||||||
|
Operand::Reg(var_reg),
|
||||||
|
Operand::Const(key_idx),
|
||||||
|
Operand::None,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let op = match (update.operator, update.prefix) {
|
let op = match (update.operator, update.prefix) {
|
||||||
(UpdateOperator::Increment, true) => OpCode::INC_PRE,
|
(UpdateOperator::Increment, true) => OpCode::INC_PRE,
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
//! delete 已知 builtin 名后的复合/逻辑赋值与 update 表达式 LHS 旧值读:
|
||||||
|
//! 旧值读须经 A 侧全局对象属性在位判定,缺位(delete 真删后)抛
|
||||||
|
//! ReferenceError,与裸读同形;在位时语义不变(A 侧读 + 写回)。
|
||||||
|
//!
|
||||||
|
//! 镜像槽无法自区分「A 侧缺位」与「A 侧在位值 undefined」(两者槽值皆
|
||||||
|
//! undefined),delete 真删后旧值读走镜像槽会不抛且以计算值复活属性。
|
||||||
|
//! 本面只改旧值读路由(三读点前插 LOAD_GLOBAL),写回路径零改动。
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_parser::Allocator;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn eval_js(source: &str) -> Result<(Vm, JsValue), String> {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("parse: {e:?}"))?;
|
||||||
|
let module = Arc::new(Compiler::new().compile(&program).map_err(|e| format!("compile: {e}"))?);
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let val = vm.run(&module)?;
|
||||||
|
Ok((vm, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_str(source: &str) -> Result<String, String> {
|
||||||
|
let (vm, val) = eval_js(source)?;
|
||||||
|
Ok(vm.lookup_str(val).unwrap_or_else(|| format!("{val:?}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_truthy(source: &str) {
|
||||||
|
let (_vm, val) = eval_js(source).expect("run");
|
||||||
|
assert!(val.is_bool() && val.as_bool(), "expected true, got: {val:?}\nsource: {source}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删后 LHS 旧值读抛 ReferenceError 钉:运行期错误文本须含种类与名字。
|
||||||
|
fn expect_re(source: &str, name: &str) {
|
||||||
|
let err = match eval_js(source) {
|
||||||
|
Err(e) => e,
|
||||||
|
Ok(_) => panic!("expected ReferenceError, got ok: {source}"),
|
||||||
|
};
|
||||||
|
let lower = err.to_lowercase();
|
||||||
|
assert!(lower.contains("referenceerror"), "expected ReferenceError, got: {err}\nsource: {source}");
|
||||||
|
assert!(lower.contains(name), "expected name in message, got: {err}\nsource: {source}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 红族:删后 LHS 旧值读抛 ReferenceError(修前不抛且以计算值复活属性)──
|
||||||
|
|
||||||
|
// 复合主形态:顶层 delete 后复合赋值旧值读。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_compound_add_reference_error() {
|
||||||
|
expect_re("delete Array; Array += 1", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复合换算符:减。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_compound_sub_reference_error() {
|
||||||
|
expect_re("delete Array; Array -= 1", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复合换算符:乘。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_compound_mul_reference_error() {
|
||||||
|
expect_re("delete Array; Array *= 2", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逻辑主形态:||= 旧值读。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_logical_or_reference_error() {
|
||||||
|
expect_re("delete Array; Array ||= 2", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逻辑短路面:&&= 旧值读(短路测试前抛)。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_logical_and_reference_error() {
|
||||||
|
expect_re("delete Array; Array &&= 2", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逻辑空值面:??= 旧值读。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_logical_nullish_reference_error() {
|
||||||
|
expect_re("delete Array; Array ??= 2", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// update 前缀主形态。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_update_prefix_increment_reference_error() {
|
||||||
|
expect_re("delete Array; Array++", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// update 前缀换算符:递减。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_update_prefix_decrement_reference_error() {
|
||||||
|
expect_re("delete Array; --Array", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// update 后缀面。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_update_postfix_reference_error() {
|
||||||
|
expect_re("delete Array; Array--", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 换名族:Promise 单名复合。
|
||||||
|
#[test]
|
||||||
|
fn delete_promise_compound_reference_error() {
|
||||||
|
expect_re("delete Promise; Promise += 1", "promise");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帧读面:delete 在外层,嵌套函数内复合赋值旧值读缺失。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_nested_fn_compound_reference_error() {
|
||||||
|
expect_re("delete Array; (function(){Array += 1})()", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成员复合守卫钉:成员面旧值读走对象属性(非镜像槽),零涉本面;
|
||||||
|
// 基引用 Math 裸读经 221.1 路由抛 ReferenceError,成员复合路径不改变该抛点。
|
||||||
|
#[test]
|
||||||
|
fn delete_math_member_compound_base_read_reference_error() {
|
||||||
|
expect_re("delete Math; Math.max += 1", "math");
|
||||||
|
}
|
||||||
|
|
||||||
|
// with 复合回退面:with 对象无该属性时回退静态复合路径,旧值读缺失抛。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_with_compound_fallback_reference_error() {
|
||||||
|
expect_re("delete Array; with(globalThis){Array += 1}", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// with update 回退面:同复合回退面,update 表达式。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_with_update_fallback_reference_error() {
|
||||||
|
expect_re("delete Array; with(globalThis){Array++}", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// with 逻辑赋值无对象回退:with 体自由标识符逻辑赋值走静态路径,旧值读缺失抛。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_with_logical_fallback_reference_error() {
|
||||||
|
expect_re("delete Array; with({x: 1}){Array ||= 2}", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 绿族:修后须保真 ──
|
||||||
|
|
||||||
|
// 未删在位复合:A 侧读 + 写回,语义不变。
|
||||||
|
#[test]
|
||||||
|
fn builtin_compound_add_present_value() {
|
||||||
|
assert_eq!(eval_str("Array = 1; Array += 1; String(Array)").unwrap(), "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 局部 var 零涉:非 builtin 名复合赋值。
|
||||||
|
#[test]
|
||||||
|
fn local_var_compound_add_value() {
|
||||||
|
assert_eq!(eval_str("var x = 1; x += 1; String(x)").unwrap(), "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遮蔽守卫:局部 var Array 遮蔽 builtin 槽,复合走局部槽。
|
||||||
|
#[test]
|
||||||
|
fn local_var_shadow_array_compound_value() {
|
||||||
|
assert_eq!(eval_str("function g(){var Array=1; Array += 1; return Array}; String(g())").unwrap(), "2",);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只读三常量零涉:c:false 拒删保值,复合旧值读不抛。
|
||||||
|
#[test]
|
||||||
|
fn delete_nan_compound_no_throw() {
|
||||||
|
eval_truthy("delete NaN === false && Number.isNaN(NaN += 1)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删后重写再复合:A 侧在位,旧值读见新值。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_rewrite_compound_value() {
|
||||||
|
assert_eq!(eval_str("delete Array; Array = 42; Array += 1; String(Array)").unwrap(), "43");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成员面零涉:对象属性 delete 后成员复合不抛(GetBaseValue 缺位按 undefined)。
|
||||||
|
#[test]
|
||||||
|
fn member_compound_after_delete_no_throw() {
|
||||||
|
eval_truthy("var obj = {p: 1}; delete obj.p; obj.p += 1; Number.isNaN(obj.p)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 221.1 保真:typeof 不抛。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_typeof_undefined() {
|
||||||
|
assert_eq!(eval_str("delete Array; typeof Array").unwrap(), "undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 221.1 保真:裸读抛 ReferenceError。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_bare_read_reference_error() {
|
||||||
|
expect_re("delete Array; Array", "array");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user