feat: TA 删除自身属性接统一数值键门三态裁决

This commit is contained in:
2026-09-23 08:55:09 +00:00
parent 18b80f2f41
commit 1670978a3a
2 changed files with 182 additions and 4 deletions
+23 -4
View File
@@ -313,23 +313,42 @@ pub enum DeleteOutcome {
/// 参与删除判定,原型链属性不影响结果。
///
/// # 步骤
/// 1. 数组 length 虚拟属性:不可配置,返回 `NonConfigurable`
/// 2. 数组下标元素:检查 configurable,`mark_hole_at` 标记为 hole
/// 3. 命名属性:walk_own_keys 定位槽位,不可配置返回 `NonConfigurable`
/// 4. 数组先保存元素区(值 + meta),重建命名属性后恢复元素区
/// 1. TA 数值键:界内索引返回 `NonConfigurable`(元素零修改);数字无效键
/// 返回 `Missing`;非数字串 / symbol 键落下方普通路径
/// 2. 数组 length 虚拟属性:不可配置,返回 `NonConfigurable`
/// 3. 数组下标元素:检查 configurable,`mark_hole_at` 标记为 hole
/// 4. 命名属性:walk_own_keys 定位槽位,不可配置返回 `NonConfigurable`
/// 5. 数组先保存元素区(值 + meta),重建命名属性后恢复元素区
///
/// # 边界与前提
/// - 键不在对象自身(含原型链属性)返回 `Missing`
/// - 非 configurable 属性返回 `NonConfigurable`
/// - TA 键 live 长 0(detach / 收缩越界)时全数值键归数字无效,删除成功
/// - `key_si` 须已 intern
///
/// # 副作用
/// - 修改 obj 的 shape_id、属性表与 generation;数组元素区内容不变
/// - TA 数字无效臂同步全局内置镜像槽为 undefined(非全局内置 TA 上为 no-op)
///
/// # 注意事项
/// - 数组元素存在性以 `prop_meta_at` 的 hole 标记判定,删除后重新写入元素
/// 会自动清除 hole 标记恢复存在
pub fn delete_own_property_outcome<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> DeleteOutcome {
// TA 数值键:界内索引不可配置(删除失败,元素零修改、镜像槽不同步);
// 数字无效键视为缺失(删除成功,镜像槽同步 undefined,对非全局内置 TA
// 为 no-op);非数字串 / symbol 键落下方普通属性路径。live 长 0
// (detach / 收缩越界)时全数值键自然归数字无效,与读 / 写 / 定义面同口径。
if obj.is_typed_array_obj() {
match crate::typed_array::ta_index_gate(vm, obj, key_si) {
crate::typed_array::TaIndexGate::NumericValid(_) => return DeleteOutcome::NonConfigurable,
crate::typed_array::TaIndexGate::NumericInvalid => {
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
return DeleteOutcome::Missing;
}
crate::typed_array::TaIndexGate::Ordinary => {}
}
}
// 数组下标元素在元素区,不参与 shape 链,单独删除(保持 length 不变)。
if obj.is_array() {
if let Some(index) = array_index_of(vm, key_si) {
@@ -1296,3 +1296,162 @@ fn ta_dop_get_dir_arm4() {
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_delete_valid_in_range_false() {
// 界内索引删除失败(sloppy false):元素与 length 不变,原型同键抛 getter
// 不被触达(界内臂不查自身命名属性也不走原型链)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array([42, 43]); \
Object.defineProperty(Int8Array.prototype, '0', { \
get: function () { throw new Error('proto getter must not fire'); }, \
configurable: true }); \
var r0 = delete ta[0]; \
var r1 = delete ta['0']; \
var r2 = delete ta[1]; \
return r0 === false && r1 === false && r2 === false \
&& ta[0] === 42 && ta[1] === 43 && ta.length === 2; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_delete_valid_strict_typeerror() {
// 严格模式界内索引删除抛 TypeError(构造器断言);元素不动、length 不变。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"'use strict'; (function () { var ta = new Int8Array([42, 43]); \
var thrown = false; \
try { delete ta[1]; } \
catch (e) { if (!(e instanceof TypeError)) return false; thrown = true; } \
return thrown && ta[0] === 42 && ta[1] === 43 && ta.length === 2; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_delete_int_minus_zero() {
// -0 两相分臂:int 相(ta[-0] → 键 "0" 界内)删除失败;串相("-0" 数字
// 无效)删除成功且不建属性;元素不动。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array([42]); \
var rInt = delete ta[-0]; \
var rStr = delete ta['-0']; \
return rInt === false && rStr === true \
&& ta[0] === 42 \
&& !Object.prototype.hasOwnProperty.call(ta, '-0'); })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_delete_oob_true() {
// 越界 / 负索引删除成功(true):元素不动、无自身属性、严格模式不抛。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"'use strict'; (function () { var ta = new Int8Array([42]); \
var ok = delete ta[-1] && delete ta[1] && delete ta['-1'] && delete ta['1'] && delete ta['4294967295']; \
var keys = ['-1', '1', '4294967295']; \
for (var i = 0; i < keys.length; i++) { \
if (Object.prototype.hasOwnProperty.call(ta, keys[i])) return false; } \
return ok && ta[0] === 42 && ta.length === 1; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_delete_numeric_invalid_strings() {
// 数字无效串(非整数 / NaN / ±Infinity / "-0" 特例)删除恒成功:不建自身
// 属性,原型同键抛 getter 不被触达(数字无效臂不走原型链)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array([42]); \
var keys = ['1.1', '0.000001', '-0', 'Infinity', 'NaN']; \
for (var i = 0; i < keys.length; i++) { \
Object.defineProperty(Int8Array.prototype, keys[i], { \
get: function () { throw new Error('OrdinaryDelete was called'); }, \
configurable: true }); } \
for (var i = 0; i < keys.length; i++) { \
if (!delete ta[keys[i]]) return false; \
if (Object.prototype.hasOwnProperty.call(ta, keys[i])) return false; } \
return ta[0] === 42; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_delete_ordinary_own_property() {
// 非 round-trip 串(Ordinary 臂)落真实自身属性路径:不可配置 accessor
// 删除失败且 getter 保留可读;可配置数据属性删除成功、hasOwn 转假。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array(2); \
var rDef = Object.defineProperty(ta, '1.0', { \
get: function () { return 'g'; }, configurable: false }); \
var d1 = delete ta['1.0']; \
var d2 = delete ta['+1']; \
Object.defineProperty(ta, '+1', { value: 9, configurable: true }); \
var d3 = delete ta['+1']; \
return rDef === ta && d1 === false && ta['1.0'] === 'g' \
&& d2 === true && d3 === true \
&& !Object.prototype.hasOwnProperty.call(ta, '+1'); })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_delete_symbol_and_reflect() {
// symbol 键缺失删除成功、建自身属性后删除成功;Reflect.deleteProperty
// 界内索引 false 不抛、越界 true(恒不抛契约)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ta = new Int8Array([42]); \
var s = Symbol('s'); \
var r1 = delete ta[s]; \
ta[s] = 'x'; \
var r2 = delete ta[s]; \
var r3 = Reflect.deleteProperty(ta, 0); \
var r4 = Reflect.deleteProperty(ta, 9); \
return r1 === true && r2 === true && r3 === false && r4 === true \
&& ta[0] === 42; })()",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn ta_delete_live_zero_all_true() {
// detach 后 live 长 0:全数值键归数字无效,成员形与 Reflect 删除恒成功、
// 无自身属性、不抛。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var ab = new ArrayBuffer(8); var ta = new Int8Array(ab); \
ta[0] = 7; \
ab.transfer(); \
var r1 = delete ta[0]; \
var r2 = delete ta['-0']; \
var r3 = delete ta['1.1']; \
var r4 = delete ta['1']; \
var r5 = Reflect.deleteProperty(ta, 0); \
return r1 === true && r2 === true && r3 === true && r4 === true && r5 === true \
&& !Object.prototype.hasOwnProperty.call(ta, '0'); })()",
)
.unwrap();
assert!(result.as_bool());
}