From aafd07728c28939e898e5692bfb1b3ccf2087907 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 15 Sep 2026 03:56:18 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20delete=20=E9=9D=9E=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E5=9F=BA=E6=8C=89=20ToObject=20=E8=A3=85=E7=AE=B1=E5=88=A4?= =?UTF-8?q?=E5=88=A0=EF=BC=8C=E4=BB=85=20null/undefined=20=E5=9F=BA?= =?UTF-8?q?=E6=8A=9B=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/dispatch/property.rs | 12 ++- crates/oxide_vm/src/vm.rs | 36 +++++++ .../tests/delete_prop_primitive_base_tests.rs | 93 +++++++++++++++++++ 3 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 crates/oxide_vm/tests/delete_prop_primitive_base_tests.rs diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 87a226aa..699e17ed 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -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 { 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 }; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 25adf68a..c0f72f2d 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1478,6 +1478,42 @@ impl Vm { key.parse::().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 { + 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 { vm_trace!("resolve_property: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si); let length_si = self.length_si; diff --git a/crates/oxide_vm/tests/delete_prop_primitive_base_tests.rs b/crates/oxide_vm/tests/delete_prop_primitive_base_tests.rs new file mode 100644 index 00000000..f9b817ad --- /dev/null +++ b/crates/oxide_vm/tests/delete_prop_primitive_base_tests.rs @@ -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 { + 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"); +}