From 1670978a3a83b15cc4a5f56c8917c16b07b48d30 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 23 Sep 2026 08:55:09 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20TA=20=E5=88=A0=E9=99=A4=E8=87=AA?= =?UTF-8?q?=E8=BA=AB=E5=B1=9E=E6=80=A7=E6=8E=A5=E7=BB=9F=E4=B8=80=E6=95=B0?= =?UTF-8?q?=E5=80=BC=E9=94=AE=E9=97=A8=E4=B8=89=E6=80=81=E8=A3=81=E5=86=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/object.rs | 27 ++- .../tests/builtins_typed_array_tests.rs | 159 ++++++++++++++++++ 2 files changed, 182 insertions(+), 4 deletions(-) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index d24b208f..ab13c418 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -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(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) { diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index fcc25813..37d3a21d 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -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()); +}