fix: with 内 delete 回退臂解析外层绑定按可删除性定值

This commit is contained in:
2026-10-06 04:48:01 +00:00
parent 7ab8a0587d
commit ee3eb1892a
4 changed files with 181 additions and 88 deletions
Generated
+18 -18
View File
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.8.19" version = "0.8.20"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1120,7 +1120,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.8.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1168,7 +1168,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.8.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1240,7 +1240,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.8.19" version = "0.8.20"
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.19" version = "0.8.20"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1261,7 +1261,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.8.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
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.19" version = "0.8.20"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.8.19" version = "0.8.20"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+101 -69
View File
@@ -138,7 +138,7 @@ impl Emitter {
Expression::Identifier(ident) => { Expression::Identifier(ident) => {
let name = ident.name.as_str(); let name = ident.name.as_str();
// with 体内自由标识符:对象有该属性则删除对象属性(返回删除结果), // with 体内自由标识符:对象有该属性则删除对象属性(返回删除结果),
// 否则非严格语义返回 true。 // 对象无该属性则回退外层绑定,按其可删除性定值。
if !ctx.with_stack.is_empty() && !ctx.is_with_internal_binding(name) { if !ctx.with_stack.is_empty() && !ctx.is_with_internal_binding(name) {
let obj_reg = ctx.innermost_with_obj().expect("with stack non-empty"); let obj_reg = ctx.innermost_with_obj().expect("with stack non-empty");
let key_idx = ctx.add_constant(Constant::String(name.to_string())); let key_idx = ctx.add_constant(Constant::String(name.to_string()));
@@ -173,77 +173,19 @@ impl Emitter {
)); ));
ctx.inst(Inst::jmp(end_label)); ctx.inst(Inst::jmp(end_label));
ctx.labels.set_label_pos(fallback_label, ctx.insts.len()); ctx.labels.set_label_pos(fallback_label, ctx.insts.len());
let true_idx = ctx.add_constant(Constant::Boolean(true)); // with 对象无该属性:引用解析到外层绑定,按外层绑定的可删除性
ctx.inst(Inst::load_const(Operand::Reg(result_reg), true_idx)); // 定值(声明式绑定 → false;全局属性 → 运行期探针)。
let binding_result = self.emit_delete_binding_result(name, ctx)?;
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(result_reg),
Operand::Reg(binding_result),
Operand::None,
));
ctx.labels.set_label_pos(end_label, ctx.insts.len()); ctx.labels.set_label_pos(end_label, ctx.insts.len());
return Ok(result_reg); return Ok(result_reg);
} }
// 可删全局内置(可写全局名除宿主名 $262):运行期真删 self.emit_delete_binding_result(name, ctx)
// 全局对象属性(c:true 数据描述符)并返 true;删除成功时清镜像
// 槽,裸读与 globalThis 反射不失步。
if let Some(slot_reg) = ctx.global_builtin_delete_slot(name) {
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::Reg(slot_reg), key_idx));
return Ok(reg);
}
// DeleteBinding 三分类(13.5.1.2 步5 绑定引用走 base.DeleteBinding):
// - 局部绑定(捕获 cell/upvalue/lookup 命中函数或块作用域槽)、
// 非 eval 脚本自身顶层已声明名(脚本 var/函数名 c:false)、eval
// 程序自身顶层 let/const(落 eval 自身 lexical 环境,declarative
// 环境记录 DeleteBinding 恒 false 且不物化全局属性)→ false 常数;
// - 其余——未声明名(引用不可解析)、隐式全局槽、eval 程序自身顶层
// var/函数名(物化 c:true 全局属性)——发全局对象运行期探针(与
// Reflect.deleteProperty 同源 DeleteBinding 语义:缺失 → true;
// 不可配置 → false 且保留;可配置 → 真删且 true)。三类全局属性的
// c 位已由各自写点物化,探针取值即规范值;独立编译的 eval 程序见
// 不到调用方域变量,静态"当前程序内是否声明"粗于规范动态判定,故
// 不可解析面一律运行期定值。严格模式的 delete 标识符由语义分析
// 提前拦截为早期错误,本臂在 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 程序自身顶层 var/函数名(顶层
// var 名集 ∪ 顶层函数声明名,物化 c:true 全局属性)
// 可删;eval 顶层 let/const 不在名集内(自身 lexical
// 环境不可删),与非 eval 脚本自身顶层 var/函数名
// (c:false)同保留 false。eval 起源随嵌套函数继承,
// 子 ctx 内 delete 同一名同样判可删。
ctx.is_implicit_global_reg(binding.reg)
|| (ctx.is_eval_origin && ctx.global_tier_names.contains(name))
} 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));
// 隐式全局槽被真删后,同程序后续裸读该名须走全局对象属性(顶层 var
// 的唯一存储,引擎侧不保留镜像副本):缺失属性读抛 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();
ctx.inst(Inst::load_const(Operand::Reg(reg), idx));
Ok(reg)
} }
Expression::StaticMemberExpression(member) if matches!(&member.object, Expression::Super(_)) => { Expression::StaticMemberExpression(member) if matches!(&member.object, Expression::Super(_)) => {
// super 属性引用不可删除:在求值引用之前直接抛 ReferenceError。 // super 属性引用不可删除:在求值引用之前直接抛 ReferenceError。
@@ -337,6 +279,96 @@ impl Emitter {
} }
} }
/// delete 标识符回退外层绑定的定值:可删全局内置镜像槽先行(真删加清槽),
/// 其余按 DeleteBinding 三分类发运行期探针(未声明名/隐式全局槽/eval 自身
/// 顶层 var/函数名)或 false 常数(局部绑定/脚本自身顶层 var/函数名/eval
/// 自身顶层 let/const)。返回结果寄存器。
///
/// # 步骤
/// 1. 可删全局内置镜像槽命中:发 DELETE_GLOBAL_PROP_C(真删加清槽)返结果寄存器
/// 2. DeleteBinding 三分类:局部绑定与脚本自身顶层已声明名发 false 常数;
/// 未声明名/隐式全局槽/eval 自身顶层 var/函数名发全局对象运行期探针
///
/// # 边界与前提
/// - 严格模式的 delete 标识符由语义分析提前拦截为早期错误,本臂在 strict 代码不可达
///
/// # 副作用
/// - 探针臂真删隐式全局槽后登记 `implicit_global_reads`(同程序后续裸读该名
/// 走全局对象属性,缺失抛 ReferenceError)
fn emit_delete_binding_result(&self, name: &str, ctx: &mut CompileCtx) -> Result<u32, String> {
// 可删全局内置(可写全局名除宿主名 $262):运行期真删全局对象属性
// (c:true 数据描述符)并返 true;删除成功时清镜像槽,裸读与
// globalThis 反射不失步。
if let Some(slot_reg) = ctx.global_builtin_delete_slot(name) {
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::Reg(slot_reg), key_idx));
return Ok(reg);
}
// DeleteBinding 三分类(13.5.1.2 步5 绑定引用走 base.DeleteBinding):
// - 局部绑定(捕获 cell/upvalue/lookup 命中函数或块作用域槽)、
// 非 eval 脚本自身顶层已声明名(脚本 var/函数名 c:false)、eval
// 程序自身顶层 let/const(落 eval 自身 lexical 环境,declarative
// 环境记录 DeleteBinding 恒 false 且不物化全局属性)→ false 常数;
// - 其余——未声明名(引用不可解析)、隐式全局槽、eval 程序自身顶层
// var/函数名(物化 c:true 全局属性)——发全局对象运行期探针(与
// Reflect.deleteProperty 同源 DeleteBinding 语义:缺失 → true;
// 不可配置 → false 且保留;可配置 → 真删且 true)。三类全局属性的
// c 位已由各自写点物化,探针取值即规范值;独立编译的 eval 程序见
// 不到调用方域变量,静态"当前程序内是否声明"粗于规范动态判定,故
// 不可解析面一律运行期定值。
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 程序自身顶层 var/函数名(顶层
// var 名集 ∪ 顶层函数声明名,物化 c:true 全局属性)
// 可删;eval 顶层 let/const 不在名集内(自身 lexical
// 环境不可删),与非 eval 脚本自身顶层 var/函数名
// (c:false)同保留 false。eval 起源随嵌套函数继承,
// 子 ctx 内 delete 同一名同样判可删。
ctx.is_implicit_global_reg(binding.reg)
|| (ctx.is_eval_origin && ctx.global_tier_names.contains(name))
} else {
// 函数/块作用域局部绑定:非属性引用,恒 false。
false
}
}
}
};
// 探针臂:全局对象运行期定值(缺失 → true;不可配置 → false 且保留;
// 可配置 → 真删且 true)。
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));
// 隐式全局槽被真删后,同程序后续裸读该名须走全局对象属性(顶层 var
// 的唯一存储,引擎侧不保留镜像副本):缺失属性读抛 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);
}
// 声明式绑定与脚本自身顶层已声明名:恒 false。
let idx = ctx.add_constant(Constant::Boolean(false));
let reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(reg), idx));
Ok(reg)
}
/// typeof 操作数求值:未声明标识符特判为 "undefined"。 /// typeof 操作数求值:未声明标识符特判为 "undefined"。
/// ///
/// # 步骤 /// # 步骤
@@ -0,0 +1,61 @@
//! with 内 delete 标识符结果值回归测试。
//!
//! `with` 对象有该属性时删对象属性(返回删除结果);对象无该属性时引用
//! 解析到外层绑定,按 DeleteBinding 定值(var/let/const 绑定恒 false;
//! 未声明名/隐式全局槽/可删全局内置走运行期探针真删)。覆盖:var 回退
//! false、let 回退 false、属性删除分支保真、未声明名探针、可删全局内置
//! 真删加清槽、with 内声明分支。
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}");
}
// ── var 回退:with 对象无该属性,外层 var c:false 恒 false ──
#[test]
fn with_delete_outer_var_returns_false() {
eval_truthy("var o = {}; o.x = 1; var d; with(o) { d = delete o; } d === false && o.x === 1");
}
// ── let 回退:外层 lexical 绑定 DeleteBinding 恒 false ──
#[test]
fn with_delete_outer_let_returns_false() {
eval_truthy("let l = 1; var o = {}; var d; with(o) { d = delete l; } d === false && l === 1");
}
// ── 属性删除分支保真:with 对象有该属性,真删返 true ──
#[test]
fn with_delete_object_property_returns_true() {
eval_truthy("var o = {p: 1}; var d; with(o) { d = delete p; } d === true && !('p' in o)");
}
// ── 未声明名回退:探针缺失臂返 true ──
#[test]
fn with_delete_undeclared_name_returns_true() {
eval_truthy("var o = {}; var d; with(o) { d = delete nomatch_w1; } d === true");
}
// ── 可删全局内置回退:真删全局属性并清镜像槽 ──
#[test]
fn with_delete_deletable_builtin_returns_true_and_removed() {
eval_truthy(
"var m = Math; var o = {}; var d; with(o) { d = delete Math; } \
d === true && globalThis.Math === undefined",
);
}
// ── with 内声明分支:with 内 let 优先于对象属性,落局部绑定 false ──
#[test]
fn with_delete_internal_let_returns_false() {
eval_truthy("var o = {}; var d; with(o) { let p = 1; d = delete p; } d === false");
}