mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 11:41:13 +08:00
fix: delete 标识符按 DeleteBinding 三分类发运行期探针,补真删与读侧效应
This commit is contained in:
@@ -126,10 +126,10 @@ impl Emitter {
|
|||||||
/// 删除并返回删除结果;非引用操作数返回 true。
|
/// 删除并返回删除结果;非引用操作数返回 true。
|
||||||
///
|
///
|
||||||
/// # 步骤
|
/// # 步骤
|
||||||
/// 1. 标识符:with 体动态解析、可删全局内置镜像槽先行;未声明名(含隐式
|
/// 1. 标识符:with 体动态解析、可删全局内置镜像槽先行;其余按 DeleteBinding
|
||||||
/// 全局槽,引用不可解析或全局属性可配置)返回 true;其余已声明绑定
|
/// 三分类(见标识符臂注释)——局部绑定与脚本自身顶层已声明名发 false
|
||||||
/// (var/let/函数名/参数,非可配置或属性引用)返回 false。严格模式的
|
/// 常数,未声明名/隐式全局槽/eval 程序自身顶层已声明名发全局对象运行期
|
||||||
/// delete 标识符是早期错误,由语义分析阶段拦截,不在此出现。
|
/// 探针。严格模式的 delete 标识符是早期错误,由语义分析阶段拦截,不在此出现。
|
||||||
/// 2. 静态/计算成员与链式成员:从对象真删属性,结果为删除返回值。
|
/// 2. 静态/计算成员与链式成员:从对象真删属性,结果为删除返回值。
|
||||||
/// 3. 其余形态(字面量/调用/new/this/一元形/序列/嵌套 delete 等):先求值
|
/// 3. 其余形态(字面量/调用/new/this/一元形/序列/嵌套 delete 等):先求值
|
||||||
/// 操作数(副作用与操作数求值期错误不被吞),再发常量 true。
|
/// 操作数(副作用与操作数求值期错误不被吞),再发常量 true。
|
||||||
@@ -187,17 +187,53 @@ impl Emitter {
|
|||||||
ctx.inst(Inst::delete_global_prop_c(Operand::Reg(reg), Operand::Reg(slot_reg), key_idx));
|
ctx.inst(Inst::delete_global_prop_c(Operand::Reg(reg), Operand::Reg(slot_reg), key_idx));
|
||||||
return Ok(reg);
|
return Ok(reg);
|
||||||
}
|
}
|
||||||
// 已声明绑定(upvalue/捕获 cell/局部 var/let/函数名/参数/顶层 var)
|
// DeleteBinding 三分类(13.5.1.2 步5 绑定引用走 base.DeleteBinding):
|
||||||
// 是非属性引用或全局对象上不可配置绑定,返回 false;未声明名
|
// - 局部绑定(捕获 cell/upvalue/lookup 命中函数或块作用域槽)与
|
||||||
// (引用不可解析)与隐式全局槽(全局属性可配置)返回 true。
|
// 非 eval 脚本自身顶层已声明名(脚本 var/函数名 c:false)→ false 常数;
|
||||||
// 严格模式的 delete 标识符由语义分析提前拦截为早期错误。
|
// - 其余——未声明名(引用不可解析)、隐式全局槽、eval 程序自身顶层
|
||||||
let declared = ctx.captured_bindings.contains_key(name)
|
// 已声明名(c:true)——发全局对象运行期探针(与 Reflect.deleteProperty
|
||||||
|| ctx.current_upvalue_captures.iter().any(|u| u.name == name)
|
// 同源 DeleteBinding 语义:缺失 → true;不可配置 → false 且保留;
|
||||||
|| match ctx.lookup(name) {
|
// 可配置 → 真删且 true)。三类全局属性的 c 位已由各自写点物化,
|
||||||
Err(_) => false,
|
// 探针取值即规范值;独立编译的 eval 程序见不到调用方域变量,静态
|
||||||
Ok(reg) => !ctx.is_implicit_global_reg(reg),
|
// "当前程序内是否声明"粗于规范动态判定,故不可解析面一律运行期定值。
|
||||||
};
|
// 严格模式的 delete 标识符由语义分析提前拦截为早期错误,本臂在
|
||||||
let idx = ctx.add_constant(Constant::Boolean(!declared));
|
// strict 代码不可达。
|
||||||
|
let local = ctx.captured_bindings.contains_key(name)
|
||||||
|
|| ctx.current_upvalue_captures.iter().any(|u| u.name == name);
|
||||||
|
let probe = if local {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
match ctx.scopes.symbols.lookup_any_binding(name) {
|
||||||
|
// 未声明名(引用不可解析):运行期按全局属性定值。
|
||||||
|
None => true,
|
||||||
|
Some((binding, scope_idx)) => {
|
||||||
|
if scope_idx == 0 {
|
||||||
|
// 全局作用域:隐式全局槽(未声明名读写登记,属性
|
||||||
|
// c:true)与 eval 程序自身顶层名(c:true)可删;
|
||||||
|
// 非 eval 脚本自身顶层 var/函数名(c:false)保留 false。
|
||||||
|
ctx.is_implicit_global_reg(binding.reg) || ctx.is_eval_script
|
||||||
|
} else {
|
||||||
|
// 函数/块作用域局部绑定:非属性引用,恒 false。
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if probe {
|
||||||
|
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
|
||||||
|
let reg = ctx.alloc_reg();
|
||||||
|
ctx.inst(Inst::delete_global_prop_c(Operand::Reg(reg), Operand::None, key_idx));
|
||||||
|
// 隐式全局槽被真删后,同程序后续裸读该名须走全局对象属性
|
||||||
|
// (A 侧单一真值):缺失属性读抛 ReferenceError,delete 的
|
||||||
|
// 真删效应在读侧可见。
|
||||||
|
if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) {
|
||||||
|
if ctx.is_implicit_global_reg(binding.reg) {
|
||||||
|
ctx.implicit_global_reads.insert(binding.reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(reg);
|
||||||
|
}
|
||||||
|
let idx = ctx.add_constant(Constant::Boolean(false));
|
||||||
let reg = ctx.alloc_reg();
|
let reg = ctx.alloc_reg();
|
||||||
ctx.inst(Inst::load_const(Operand::Reg(reg), idx));
|
ctx.inst(Inst::load_const(Operand::Reg(reg), idx));
|
||||||
Ok(reg)
|
Ok(reg)
|
||||||
@@ -309,8 +345,12 @@ impl Emitter {
|
|||||||
ctx.current_upvalue_captures.iter().any(|u| u.name == name) || ctx.captured_bindings.contains_key(name);
|
ctx.current_upvalue_captures.iter().any(|u| u.name == name) || ctx.captured_bindings.contains_key(name);
|
||||||
// 顶层已声明 var:typeof 读全局对象属性(A 侧单一真值),缺失 → "undefined"
|
// 顶层已声明 var:typeof 读全局对象属性(A 侧单一真值),缺失 → "undefined"
|
||||||
// (非抛,IsUnresolvableReference 语义)。未声明名同走此路(lookup 未命中)。
|
// (非抛,IsUnresolvableReference 语义)。未声明名同走此路(lookup 未命中)。
|
||||||
|
// 隐式全局槽(未声明名读写登记)同走属性路:delete 真删后缺失 → "undefined"
|
||||||
|
// 而非经镜像槽读旧值或抛 ReferenceError(typeof 对 unresolvable 引用不抛)。
|
||||||
|
let binding = ctx.scopes.symbols.lookup_any_binding(name);
|
||||||
|
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 || ctx.scopes.symbols.lookup_any_binding(name).is_none()) {
|
if !in_with_dynamic && !captured && (is_tier || binding.is_none() || implicit_global) {
|
||||||
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(
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
//! delete 标识符 DeleteBinding 面:eval 程序独立编译见不到调用方域变量,静态
|
||||||
|
//! "当前程序内是否声明"粗于规范动态判定,未声明名/隐式全局槽/eval 程序自身
|
||||||
|
//! 顶层已声明名一律发全局对象运行期探针(缺失 → true;不可配置 → false 且保留;
|
||||||
|
//! 可配置 → 真删且 true)。局部绑定与非 eval 脚本自身顶层已声明名保留 false
|
||||||
|
//! 常数。覆盖:direct eval 对调用方脚本 var、eval 自身 var 真删、跨 eval 真删、
|
||||||
|
//! 函数内隐式全局真删、读侧真删可见(删后裸读抛 ReferenceError)、typeof 对
|
||||||
|
//! 删后缺失名不抛、删后重写同步、绿基线(脚本 var/顶层函数名/未声明缺失/builtin
|
||||||
|
//! 槽优先级/strict 调用方 indirect eval)。
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_parser::Allocator;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn eval_truthy(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 result = vm.run(&Arc::new(module)).expect("run");
|
||||||
|
assert!(result.is_bool() && result.as_bool(), "expected true, got: {result:?}\nsource: {source}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── direct eval 对调用方脚本 var(c:false):false 且属性保留 ──
|
||||||
|
#[test]
|
||||||
|
fn eval_delete_caller_script_var_returns_false_and_kept() {
|
||||||
|
eval_truthy("var x = 1; var d = eval('delete x'); d === false && x === 1 && globalThis.x === 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── eval 程序自身顶层 var(c:true):true 且真删 ──
|
||||||
|
#[test]
|
||||||
|
fn eval_delete_own_var_returns_true_and_removed() {
|
||||||
|
eval_truthy("eval('var w = 5; delete w') === true && typeof w === 'undefined'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 跨 eval:前一 eval 建的 var(c:true),后一 eval 真删 ──
|
||||||
|
#[test]
|
||||||
|
fn eval_delete_var_from_previous_eval_returns_true_and_removed() {
|
||||||
|
eval_truthy("eval('var y2 = 5'); var d = eval('delete y2'); d === true && typeof y2 === 'undefined'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 函数内隐式全局(未声明写登记,c:true):true 且真删 ──
|
||||||
|
#[test]
|
||||||
|
fn fn_scope_delete_implicit_global_returns_true_and_removed() {
|
||||||
|
eval_truthy(
|
||||||
|
"q = 2; (function() { var d = delete q; return d === true && (function() { \
|
||||||
|
try { void q; return false; } catch (e) { return e instanceof ReferenceError; } \
|
||||||
|
})(); })() === true",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 未声明缺失名:探针缺失臂 true(非引用目标语义保留) ──
|
||||||
|
#[test]
|
||||||
|
fn delete_undeclaring_missing_name_returns_true() {
|
||||||
|
eval_truthy("delete nomatch_xyz_probe === true");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 绿基线:脚本自身顶层 var(c:false)false 常数,值保留 ──
|
||||||
|
#[test]
|
||||||
|
fn delete_script_top_var_returns_false_and_kept() {
|
||||||
|
eval_truthy("var y = 2; delete y === false && y === 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 绿基线:函数作用域读脚本顶层 var,作用域链判定局部性,false ──
|
||||||
|
#[test]
|
||||||
|
fn delete_top_var_from_function_scope_returns_false() {
|
||||||
|
eval_truthy("var p = 1; (function() { return delete p; })() === false && p === 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 绿基线:strict 调用方 + indirect eval(eval 代码不继承调用方严格性) ──
|
||||||
|
#[test]
|
||||||
|
fn strict_caller_indirect_eval_delete_undeclared_returns_true() {
|
||||||
|
eval_truthy("(function() { \"use strict\"; return (0, eval)(\"delete zqq_probe\"); })() === true");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 可删全局内置镜像槽臂优先:探针臂不吞 builtin 槽(真删 + 清槽) ──
|
||||||
|
#[test]
|
||||||
|
fn deletable_builtin_slot_arm_precedes_probe() {
|
||||||
|
eval_truthy("var m = Math; delete Math === true && globalThis.Math === undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 读侧真删可见:删后同程序裸读抛 ReferenceError(A 侧单一真值) ──
|
||||||
|
#[test]
|
||||||
|
fn bare_read_after_real_delete_throws_reference_error() {
|
||||||
|
eval_truthy(
|
||||||
|
"x = 1; delete x === true && (function() { try { void x; return false; } \
|
||||||
|
catch (e) { return e instanceof ReferenceError; } })() === true",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── typeof 对删后缺失名不抛(IsUnresolvableReference → "undefined") ──
|
||||||
|
#[test]
|
||||||
|
fn typeof_after_real_delete_returns_undefined_not_throw() {
|
||||||
|
eval_truthy("x = 1; delete x; typeof x === 'undefined'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 删后重写:寄存器与全局对象属性重新同步 ──
|
||||||
|
#[test]
|
||||||
|
fn reassign_after_real_delete_resyncs_both_sides() {
|
||||||
|
eval_truthy("x = 1; delete x; x = 5; x === 5 && globalThis.x === 5");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 删前读值不受影响(读集登记仅自探针起) ──
|
||||||
|
#[test]
|
||||||
|
fn read_before_delete_unchanged() {
|
||||||
|
eval_truthy("x = 1; var r = x; delete x; r === 1");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user