mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 删后裸读已知全局内置名经 A 侧抛 ReferenceError
This commit is contained in:
@@ -376,9 +376,10 @@ define_opcodes! {
|
|||||||
LOAD_CONST = 0x32 => "LOAD_CONST",
|
LOAD_CONST = 0x32 => "LOAD_CONST",
|
||||||
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [],
|
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [],
|
||||||
pure = true, jump = false, term = false, ic = false,
|
pure = true, jump = false, term = false, ic = false,
|
||||||
// 未声明标识符读:rd=目标,a/b=常量池 key 下标(imm16,与 LOAD_CONST 同构),
|
// 未声明/已删标识符读:rd=目标,a/b=常量池 key 下标(imm16,与 LOAD_CONST 同构),
|
||||||
// 运行期查 global object 属性存在性,缺失抛 ReferenceError(热路径零开销:
|
// 运行期查 global object 属性存在性,缺失抛 ReferenceError(未声明名与已知 builtin
|
||||||
// 已声明/内置标识符仍走 LOAD_VAR,本指令只命中真正未声明的读)。
|
// 名非局部遮蔽读均命中本指令:delete 后 A 侧缺失须经属性在位判定,镜像槽无法
|
||||||
|
// 自区分缺位与在位 undefined)。
|
||||||
LOAD_GLOBAL = 0x95 => "LOAD_GLOBAL",
|
LOAD_GLOBAL = 0x95 => "LOAD_GLOBAL",
|
||||||
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [],
|
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [],
|
||||||
pure = false, jump = false, term = false, ic = false,
|
pure = false, jump = false, term = false, ic = false,
|
||||||
|
|||||||
@@ -21,9 +21,12 @@ impl Emitter {
|
|||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - `kind` 必须是已注册的全局构造器名(如 "ReferenceError"/"TypeError")。
|
/// - `kind` 必须是已注册的全局构造器名(如 "ReferenceError"/"TypeError")。
|
||||||
pub(crate) fn emit_throw_error(&self, kind: &str, msg: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
|
pub(crate) fn emit_throw_error(&self, kind: &str, msg: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
|
||||||
let ctor_reg = ctx.lookup_or_builtin(kind)?;
|
// 构造器读 A 侧全局对象属性(冷路径,与已知 builtin 名读路由同形):
|
||||||
|
// delete 构造器后缺失抛 ReferenceError,与 V8 同形。仅留登记副作用。
|
||||||
|
let _ = ctx.lookup_or_builtin(kind)?;
|
||||||
let ctor = ctx.alloc_reg();
|
let ctor = ctx.alloc_reg();
|
||||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(ctor), Operand::Reg(ctor_reg), Operand::None));
|
let key_idx = ctx.add_constant(Constant::String(kind.to_string()));
|
||||||
|
ctx.inst(Inst::new(OpCode::LOAD_GLOBAL, Operand::Reg(ctor), Operand::Const(key_idx), Operand::None));
|
||||||
let msg_reg = ctx.alloc_reg();
|
let msg_reg = ctx.alloc_reg();
|
||||||
let msg_idx = ctx.add_constant(Constant::String(msg.to_string()));
|
let msg_idx = ctx.add_constant(Constant::String(msg.to_string()));
|
||||||
ctx.inst(Inst::load_const(Operand::Reg(msg_reg), msg_idx));
|
ctx.inst(Inst::load_const(Operand::Reg(msg_reg), msg_idx));
|
||||||
|
|||||||
@@ -65,7 +65,13 @@ impl Emitter {
|
|||||||
// 隐式全局槽(读侧登记与写侧登记同属一个槽)同样读全局对象属性:属性是
|
// 隐式全局槽(读侧登记与写侧登记同属一个槽)同样读全局对象属性:属性是
|
||||||
// 隐式全局值的唯一真源,嵌套函数内 delete 真删后外层裸读据此可见,引擎侧
|
// 隐式全局值的唯一真源,嵌套函数内 delete 真删后外层裸读据此可见,引擎侧
|
||||||
// 镜像槽不反映删除。
|
// 镜像槽不反映删除。
|
||||||
if self.is_global_tier_name(ctx, name) || ctx.is_implicit_global_reg(var_reg) {
|
// 已知 builtin 名(非局部遮蔽)同走全局对象属性:镜像槽无法自区分「A 侧
|
||||||
|
// 缺位」与「A 侧在位值 undefined」(两者槽值皆 undefined),delete 真删后
|
||||||
|
// 裸读须经属性在位判定抛 ReferenceError。
|
||||||
|
if self.is_global_tier_name(ctx, name)
|
||||||
|
|| ctx.is_implicit_global_reg(var_reg)
|
||||||
|
|| (ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name))
|
||||||
|
{
|
||||||
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(OpCode::LOAD_GLOBAL, Operand::Reg(r), Operand::Const(key_idx), Operand::None));
|
ctx.inst(Inst::new(OpCode::LOAD_GLOBAL, Operand::Reg(r), Operand::Const(key_idx), Operand::None));
|
||||||
} else {
|
} else {
|
||||||
@@ -145,7 +151,19 @@ impl Emitter {
|
|||||||
Operand::None,
|
Operand::None,
|
||||||
));
|
));
|
||||||
} else if let Some(reg) = ctx.scopes.symbols.lookup_any(name) {
|
} else if let Some(reg) = ctx.scopes.symbols.lookup_any(name) {
|
||||||
|
// 已知 builtin 名(非局部遮蔽)读 A 侧全局对象属性,与静态读臂同形;
|
||||||
|
// 局部遮蔽绑定保持 LOAD_VAR 读局部槽。
|
||||||
|
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(result_reg),
|
||||||
|
Operand::Const(key_idx),
|
||||||
|
Operand::None,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(reg), Operand::None));
|
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(reg), Operand::None));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||||
ctx.inst(Inst::load_const(Operand::Reg(result_reg), undef_idx));
|
ctx.inst(Inst::load_const(Operand::Reg(result_reg), undef_idx));
|
||||||
|
|||||||
@@ -372,7 +372,10 @@ impl Emitter {
|
|||||||
// ReferenceError(typeof 对 unresolvable 引用不抛)。
|
// ReferenceError(typeof 对 unresolvable 引用不抛)。
|
||||||
let implicit_global = binding.is_some_and(|(b, _)| ctx.is_implicit_global_reg(b.reg));
|
let implicit_global = binding.is_some_and(|(b, _)| ctx.is_implicit_global_reg(b.reg));
|
||||||
let is_tier = self.is_global_tier_name(ctx, name);
|
let is_tier = self.is_global_tier_name(ctx, name);
|
||||||
if !in_with_dynamic && !captured && (is_tier || binding.is_none() || implicit_global) {
|
// 已知 builtin 名(非局部遮蔽)同走属性路:delete 真删后缺失 →
|
||||||
|
// "undefined" 而非经镜像槽读 undefined 同值巧合。
|
||||||
|
let is_builtin_slot = ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name);
|
||||||
|
if !in_with_dynamic && !captured && (is_tier || binding.is_none() || implicit_global || is_builtin_slot) {
|
||||||
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||||
let r = ctx.alloc_reg();
|
let r = ctx.alloc_reg();
|
||||||
ctx.inst(Inst::new(
|
ctx.inst(Inst::new(
|
||||||
|
|||||||
@@ -282,8 +282,9 @@ impl Vm {
|
|||||||
/// 缺位写 undefined。
|
/// 缺位写 undefined。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 缺位臂语义为"写 undefined",各入口(run / 帧 / inline / 恢复)统一,
|
/// - 缺位臂语义为"写 undefined",各入口(run / 帧 / inline / 恢复)统一;
|
||||||
/// 使成员形删除后的裸读与入口预载看到同一值。
|
/// 裸读已路由 A 侧全局对象属性,槽为写侧/不变式维护(槽 = A 侧原始存储),
|
||||||
|
/// 不再被读消费。
|
||||||
/// - `pack_end` 为 native pack 实参区上界(独占):落 [0, pack_end) 的镜像
|
/// - `pack_end` 为 native pack 实参区上界(独占):落 [0, pack_end) 的镜像
|
||||||
/// 槽跳过,实参值只存在于寄存器、无刷新源,窗口拷回负责其还原。
|
/// 槽跳过,实参值只存在于寄存器、无刷新源,窗口拷回负责其还原。
|
||||||
/// # 副作用
|
/// # 副作用
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
//! delete 已知 builtin 名后的读侧路由:裸读/typeof/调用/new/成员基/with 读/
|
||||||
|
//! eval 读须经 A 侧全局对象属性在位判定。
|
||||||
|
//!
|
||||||
|
//! 镜像槽无法自区分「A 侧缺位」与「A 侧在位值 undefined」(两者槽值皆
|
||||||
|
//! undefined),故 delete 真删后读缺位抛 ReferenceError、typeof 求值
|
||||||
|
//! "undefined";在位 undefined 不抛(判别钉 U1)。删除侧(裸名/成员形/
|
||||||
|
//! with 形/Reflect)已收口,本面零改动,仅读路由。
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use oxide_bytecode::module::CompiledModule;
|
||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_parser::Allocator;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn compile_module(source: &str) -> Arc<CompiledModule> {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||||
|
Arc::new(Compiler::new().compile(&program).expect("compile"))
|
||||||
|
}
|
||||||
|
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删后读抛 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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 红族:删后读抛 ReferenceError(修前全部回 undefined/TypeError 种类错)──
|
||||||
|
|
||||||
|
// 主形态:顶层 delete 后裸读。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_bare_read_reference_error() {
|
||||||
|
expect_re("delete Array; Array", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帧读面:删后嵌套函数读外层 builtin 名。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_nested_fn_read_reference_error() {
|
||||||
|
expect_re("delete Array; (function(){return Array})()", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帧内删/外层读:delete 在帧内真删,外层裸读可见缺失。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_in_fn_outer_read_reference_error() {
|
||||||
|
expect_re("(function(){delete Array;return 1})(); Array", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eval 面:eval 程序读 A 侧缺失名。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_eval_read_reference_error() {
|
||||||
|
expect_re("delete Array; eval(\"Array\")", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 种类面:删后调用应抛 ReferenceError 而非 TypeError(修前 CALL_NATIVE 种类错)。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_call_reference_error_not_type_error() {
|
||||||
|
expect_re("delete Array; Array()", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 种类面:删后构造同抛 ReferenceError(修前 NEW_EXPRESSION 种类错)。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_new_expression_reference_error() {
|
||||||
|
expect_re("delete Array; new Array()", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 换名族:Promise 单名。
|
||||||
|
#[test]
|
||||||
|
fn delete_promise_bare_read_reference_error() {
|
||||||
|
expect_re("delete Promise; Promise", "promise");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 换名族:Math 成员基形态。
|
||||||
|
#[test]
|
||||||
|
fn delete_math_member_base_reference_error() {
|
||||||
|
expect_re("delete Math; Math.max(1, 2)", "math");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 换名族:Object 主形态。
|
||||||
|
#[test]
|
||||||
|
fn delete_object_bare_read_reference_error() {
|
||||||
|
expect_re("delete Object; Object", "object");
|
||||||
|
}
|
||||||
|
|
||||||
|
// with 读面:with 对象有该属性命中对象读,缺失回退 A 侧。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_with_read_reference_error() {
|
||||||
|
expect_re("delete Array; with(globalThis){Array}", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// with 删面:with 对象属性经 DELETE_PROP_DYNAMIC 真删后外层读缺失。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_with_delete_then_read_reference_error() {
|
||||||
|
expect_re("with(globalThis){delete Array} Array", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reflect 面:Reflect.deleteProperty 真删后裸读缺失。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_reflect_then_read_reference_error() {
|
||||||
|
expect_re("Reflect.deleteProperty(globalThis, \"Array\"); Array", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重删后读:删→重写→再删,读仍缺失。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_rewrite_redelete_read_reference_error() {
|
||||||
|
expect_re("delete Array; Array = 42; delete Array; Array", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选链面:?. 基引用解析抛 ReferenceError。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_optional_chain_reference_error() {
|
||||||
|
expect_re("delete Array; Array?.length", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// in LHS 面:in 操作数按引用解析抛 ReferenceError。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_in_lhs_reference_error() {
|
||||||
|
expect_re("delete Array; Array in globalThis", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较操作数面:同根主形态的另一钉。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_comparison_operand_reference_error() {
|
||||||
|
expect_re("delete Array; Array === undefined", "array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跨 run 面:同一 VM 两轮(run 入口重载镜像槽 + A 侧读),第二轮裸读缺失。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_across_runs_reference_error() {
|
||||||
|
let (mut vm, _first) = eval_js("delete Array").expect("first run should succeed");
|
||||||
|
let module = compile_module("Array");
|
||||||
|
let err = vm.run(&module).expect_err("expected ReferenceError");
|
||||||
|
assert!(err.to_lowercase().contains("referenceerror"), "expected ReferenceError, got: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 绿族:修后须保真(17 钉)──
|
||||||
|
|
||||||
|
// 严格模式 delete 标识符是早期错误(解析期,双侧同文)。
|
||||||
|
#[test]
|
||||||
|
fn strict_delete_unqualified_syntax_error() {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let res = oxide_parser::parse(&allocator, "\"use strict\"; function f() { delete Array; }");
|
||||||
|
let err = res.expect_err("expected parse-time SyntaxError");
|
||||||
|
let text = format!("{err:?}").to_lowercase();
|
||||||
|
assert!(text.contains("strict"), "expected strict-mode syntax error, got: {err:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeof 面:删后 typeof 求值 "undefined"(不抛)。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_typeof_undefined() {
|
||||||
|
assert_eq!(eval_str("delete Array; typeof Array").unwrap(), "undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeof 帧面:删后嵌套函数内 typeof 同 "undefined"。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_nested_typeof_undefined() {
|
||||||
|
assert_eq!(eval_str("delete Array; (function(){return typeof Array})()").unwrap(), "undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// in 面:in 走 A 侧,删后 false。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_in_global_this_false() {
|
||||||
|
eval_truthy("delete Array; !(\"Array\" in globalThis)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 二次 delete:属性缺失仍返 true。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_second_delete_true() {
|
||||||
|
eval_truthy("delete Array; delete Array");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反射面:globalThis.Array 成员读回 undefined。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_global_this_reflection_undefined() {
|
||||||
|
eval_truthy("delete Array; globalThis.Array === undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删后重写:DEFINE 缺失分支新建 c:true 属性,裸读见新值。
|
||||||
|
#[test]
|
||||||
|
fn delete_array_rewrite_reads_new_value() {
|
||||||
|
assert_eq!(eval_str("delete Array; Array = 42; String(Array)").unwrap(), "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三常量 c:false:拒删且保值。
|
||||||
|
#[test]
|
||||||
|
fn delete_three_constants_refused_values_kept() {
|
||||||
|
eval_truthy(
|
||||||
|
"delete NaN === false && delete Infinity === false \
|
||||||
|
&& String(NaN) === \"NaN\" && Number.isNaN(NaN) && !Number.isFinite(Infinity)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顶层 var c:false:拒删保值。
|
||||||
|
#[test]
|
||||||
|
fn delete_top_level_var_refused_value_kept() {
|
||||||
|
assert_eq!(eval_str("var x = 1; delete x; String(x)").unwrap(), "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未声明名面(既有绿):删后读抛 ReferenceError。
|
||||||
|
#[test]
|
||||||
|
fn delete_undeclared_name_bare_read_reference_error() {
|
||||||
|
expect_re("globalThis.y = 1; delete globalThis.y; y", "y");
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeof 换名族:JSON 多名。
|
||||||
|
#[test]
|
||||||
|
fn delete_json_typeof_undefined() {
|
||||||
|
assert_eq!(eval_str("delete JSON; typeof JSON").unwrap(), "undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遮蔽守卫:局部 var Array 遮蔽 builtin 槽,遮蔽读不受路由影响。
|
||||||
|
#[test]
|
||||||
|
fn local_var_shadow_array_read_value() {
|
||||||
|
assert_eq!(eval_str("function g(){var Array=1;return Array}; String(g())").unwrap(), "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遮蔽 typeof 守卫。
|
||||||
|
#[test]
|
||||||
|
fn local_var_shadow_array_typeof_number() {
|
||||||
|
assert_eq!(eval_str("function g(){var Array=1;return typeof Array}; g()").unwrap(), "number");
|
||||||
|
}
|
||||||
|
|
||||||
|
// globalThis c:true 族:typeof 预删前形态。
|
||||||
|
#[test]
|
||||||
|
fn global_this_typeof_object() {
|
||||||
|
assert_eq!(eval_str("typeof globalThis").unwrap(), "object");
|
||||||
|
}
|
||||||
|
|
||||||
|
// globalThis 真删返 true。
|
||||||
|
#[test]
|
||||||
|
fn delete_global_this_returns_true() {
|
||||||
|
eval_truthy("delete globalThis");
|
||||||
|
}
|
||||||
|
|
||||||
|
// globalThis 删后 typeof 求值 "undefined"。
|
||||||
|
#[test]
|
||||||
|
fn delete_global_this_typeof_undefined() {
|
||||||
|
assert_eq!(eval_str("delete globalThis; typeof globalThis").unwrap(), "undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键判别钉:在位 undefined 不抛(防「槽==undefined 即抛」误修)。
|
||||||
|
#[test]
|
||||||
|
fn builtin_assigned_undefined_bare_read_no_throw() {
|
||||||
|
eval_truthy("Array = undefined; Array === undefined");
|
||||||
|
}
|
||||||
@@ -21,6 +21,19 @@ fn eval_truthy(source: &str) {
|
|||||||
assert!(result.is_bool() && result.as_bool(), "expected true, got: {result:?}\nsource: {source}");
|
assert!(result.is_bool() && result.as_bool(), "expected true, got: {result:?}\nsource: {source}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 运行期应抛 ReferenceError 的钉:删后裸读经 A 侧属性在位判定抛缺位引用错误。
|
||||||
|
fn expect_reference_error(source: &str) {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||||
|
let module = Compiler::new().compile(&program).expect("compile");
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let err = vm.run(&Arc::new(module)).expect_err("expected ReferenceError");
|
||||||
|
assert!(
|
||||||
|
err.to_lowercase().contains("referenceerror"),
|
||||||
|
"expected ReferenceError, got: {err}\nsource: {source}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ── 删除返值与属性真删 ──
|
// ── 删除返值与属性真删 ──
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_math_returns_true_and_property_removed() {
|
fn delete_math_returns_true_and_property_removed() {
|
||||||
@@ -35,18 +48,15 @@ fn delete_writable_builtin_names() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 镜像槽清:删除后裸读回 undefined ──
|
// ── 删后读:A 侧缺失抛 ReferenceError(裸读路由全局对象属性)──
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_math_then_bare_read_via_mirror() {
|
fn delete_math_then_bare_read_reference_error() {
|
||||||
eval_truthy("var m = Math; delete Math; Math === undefined");
|
expect_reference_error("var m = Math; delete Math; Math");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fn_body_delete_clears_frame_mirror() {
|
fn fn_body_delete_bare_read_reference_error() {
|
||||||
eval_truthy(
|
expect_reference_error("function f() { var m = Math; delete Math; return Math; } f()");
|
||||||
"function f() { var m = Math; delete Math; \
|
|
||||||
return Math === undefined && globalThis.Math === undefined; } f() === true",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 删后重写:两侧再同步 ──
|
// ── 删后重写:两侧再同步 ──
|
||||||
@@ -75,9 +85,11 @@ fn delete_three_constants_refused_property_kept() {
|
|||||||
// ── globalThis c:true:真删且返 true,镜像槽清 ──
|
// ── globalThis c:true:真删且返 true,镜像槽清 ──
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_global_this_returns_true_and_property_removed() {
|
fn delete_global_this_returns_true_and_property_removed() {
|
||||||
|
// 裸读 globalThis 删后抛 ReferenceError(见 delete_builtin_read_tests);
|
||||||
|
// 此钉保删除返值与 A 侧成员反射。
|
||||||
eval_truthy(
|
eval_truthy(
|
||||||
"var g = globalThis; var d = delete globalThis; \
|
"var g = globalThis; var d = delete globalThis; \
|
||||||
d === true && g.globalThis === undefined && globalThis === undefined",
|
d === true && g.globalThis === undefined",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,17 +59,19 @@ fn member_write_across_call() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn member_delete_across_call_bare_read_undefined() {
|
fn member_delete_across_call_reflection_undefined() {
|
||||||
|
// 删后裸读经 A 侧抛 ReferenceError(见 delete_builtin_read_tests);
|
||||||
|
// 此钉保成员删的 A 侧真删与保存引用判别。
|
||||||
eval_truthy(
|
eval_truthy(
|
||||||
"var m = Math; (function(){ delete globalThis.Math; })(); \
|
"var m = Math; (function(){ delete globalThis.Math; })(); \
|
||||||
Math === undefined && Math !== m",
|
globalThis.Math === undefined && m !== undefined",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 成员删同 run:delete 成员形后裸读 undefined ──
|
// ── 成员删同 run:delete 成员形后 A 侧反射 undefined ──
|
||||||
#[test]
|
#[test]
|
||||||
fn member_delete_same_run_bare_read_undefined() {
|
fn member_delete_same_run_reflection_undefined() {
|
||||||
eval_truthy("var m = Math; delete globalThis.Math; Math === undefined && Math !== m");
|
eval_truthy("var m = Math; delete globalThis.Math; globalThis.Math === undefined && m !== undefined");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── defineProperty 数据描述符形同收口 ──
|
// ── defineProperty 数据描述符形同收口 ──
|
||||||
|
|||||||
Reference in New Issue
Block a user