fix: delete 非对象基按 ToObject 装箱判删,仅 null/undefined 基抛错

This commit is contained in:
2026-09-15 03:56:18 +00:00
parent 320c660620
commit aafd07728c
3 changed files with 139 additions and 2 deletions
+10 -2
View File
@@ -640,8 +640,12 @@ impl Vm {
self.pc += 1;
let key_val = self.immutables().get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
let prop_name_si = self.property_key_si(key_val)?;
if !self.regs[rd].is_object() {
// 非对象基:ToObject 基求值——null/undefined 抛 TypeError,其余原始
// 基装箱后按自身属性面判删除成败。
return self.delete_prop_non_object_base(self.regs[rd], rd, prop_name_si);
}
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
self.regs[rd] = JsValue::bool(true);
return Ok(false);
};
let obj = unsafe { &mut *obj_ptr };
@@ -654,8 +658,12 @@ impl Vm {
pub(crate) fn dispatch_delete_prop_dynamic(&mut self, rd: usize, b: usize) -> Result<bool, String> {
vm_trace!("DELETE_PROP_DYNAMIC rd={}", rd);
let prop_name_si = self.property_key_si(self.regs[b])?;
if !self.regs[rd].is_object() {
// 非对象基:ToObject 基求值——null/undefined 抛 TypeError,其余原始
// 基装箱后按自身属性面判删除成败。
return self.delete_prop_non_object_base(self.regs[rd], rd, prop_name_si);
}
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
self.regs[rd] = JsValue::bool(true);
return Ok(false);
};
let obj = unsafe { &mut *obj_ptr };
+36
View File
@@ -1478,6 +1478,42 @@ impl Vm {
key.parse::<u32>().ok()
}
/// 删除属性引用基非对象面(delete 成员表达式的 ToObject 基求值):
/// null/undefined 基 ToObject 抛 TypeError(规范 12.5.3.2 步 5.b);其余原始
/// 基装箱后按自身属性集判删除成败——字符串装箱体自身属性恰为规范下标
/// 0..len-1 与 "length"(均不可配置,删除失败返回 false),其它键与其它
/// 原始装箱体(number/boolean/BigInt/Symbol 无自身属性)删除成功返回
/// true。
///
/// # 步骤
/// 1. null/undefined 基抛 TypeError(调用方经 `?` 传播异常)
/// 2. 字符串基按下标键区间判自身属性;非字符串基恒真
/// 3. 布尔结果写 `rd` 槽(与对象基臂同槽位)
///
/// # 边界与前提
/// - 调用点已证基非对象;对象基走既有对象臂,不进本函数
/// - 字符串长度口径按 UTF-16 码元(与字符串自身属性面一致);非规范数字串
/// 键(前导零等)与 symbol 键走"无自身属性"面
pub(crate) fn delete_prop_non_object_base(
&mut self, base: JsValue, rd: usize, key_si: u32,
) -> Result<bool, String> {
if base.is_null() || base.is_undefined() {
self.raise_error_kind("TypeError", "delete on non-object")?;
}
let deleted = if base.is_string() {
let len = unsafe { (*base.as_string_ptr()).units().len() };
match self.array_index_from_property_key(key_si) {
Some(i) if (i as usize) < len => false,
Some(_) => true,
None => key_si != self.length_si,
}
} else {
true
};
self.regs[rd] = JsValue::bool(deleted);
Ok(false)
}
pub(crate) fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue> {
vm_trace!("resolve_property: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
let length_si = self.length_si;
@@ -0,0 +1,93 @@
//! delete 成员表达式基为原始值:ToObject 基求值——null/undefined 抛
//! TypeError,其余原始基装箱后按自身属性面判删除成败(字符串自身属性为
//! 规范下标 0..len-1 与 "length",均不可配置;其它原始装箱体无自身属性)。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?;
let mut vm = Vm::new();
vm.run(&Arc::new(module))
}
fn truthy(source: &str) {
let result = eval(source).unwrap_or_else(|e| panic!("{source} -> {e}"));
assert!(result.as_bool(), "{source} -> 期望 true,得 {:?}", result);
}
fn falsy(source: &str) {
let result = eval(source).unwrap_or_else(|e| panic!("{source} -> {e}"));
assert!(!result.as_bool(), "{source} -> 期望 false,得 {:?}", result);
}
#[test]
fn delete_string_index_beyond_length_is_true() {
// 计算面:字符串基越界下标无自身属性,删除成功。
truthy("delete 'Test262'[100]");
}
#[test]
fn delete_string_index_within_length_is_false() {
// 计算面:字符串自身下标属性不可配置,删除失败。
falsy("delete 'ab'[0]");
falsy("delete 'ab'[1]");
truthy("delete 'ab'[2]");
}
#[test]
fn delete_string_length_is_false() {
// 静态面:字符串 "length" 自身属性不可配置。
falsy("delete 'abc'.length");
}
#[test]
fn delete_number_base_keys_are_true() {
// number 装箱体无自身属性(原型链属性不算),静态/计算键均成功。
truthy("delete (2).c");
truthy("delete (2).toString");
truthy("delete (2).constructor");
truthy("delete (0)['0']");
}
#[test]
fn delete_boolean_base_keys_are_true() {
truthy("delete true.foo");
}
#[test]
fn delete_bigint_base_keys_are_true() {
truthy("delete (1n).x");
truthy("delete (1n)[0]");
}
#[test]
fn delete_symbol_base_keys_are_true() {
truthy("delete Symbol().a");
}
#[test]
fn delete_non_object_base_symbol_key_is_true() {
// 计算面 symbol 键:字符串装箱体无 symbol 自身属性。
truthy("delete 'ab'[Symbol.iterator]");
}
#[test]
fn delete_null_base_throws_type_error() {
truthy("try { delete null.a; false } catch (e) { e instanceof TypeError }");
}
#[test]
fn delete_undefined_base_throws_type_error() {
truthy("try { delete undefined.a; false } catch (e) { e instanceof TypeError }");
}
#[test]
fn delete_nan_base_keys_are_true() {
truthy("delete NaN.x");
}