From c0f8970912b2f58bdd3a8aba32bda50544be38c2 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 23 Sep 2026 10:42:53 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20TA=20=E6=9E=9A=E4=B8=BE=E9=9D=A2?= =?UTF-8?q?=E6=8E=A5=20live=20=E9=95=BF=E5=85=83=E7=B4=A0=E9=94=AE?= =?UTF-8?q?=E4=B8=8E=E5=AE=8C=E6=95=B4=E6=80=A7=E5=AE=B6=E6=97=8F=E8=B7=B3?= =?UTF-8?q?=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/object.rs | 46 +++- crates/oxide_vm/src/vm_dispatch_misc.rs | 10 +- .../tests/builtins_typed_array_tests.rs | 244 +++++++++++++++++- 3 files changed, 282 insertions(+), 18 deletions(-) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 8c64b08c..bedb61a6 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -3,7 +3,7 @@ use oxide_kernel::string_forge::PermInterner; use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry}; use oxide_types::private_key::{ int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, make_well_known_symbol_key, - symbol_index_from_key, well_known_symbol_id_from_key, + symbol_index_from_key, well_known_symbol_id_from_key, INT_KEY_COUNT, }; use oxide_types::value::JsValue; @@ -22,9 +22,19 @@ fn is_integer_index(key: &str) -> bool { /// 收集对象全部自身属性(数组元素区 + shape 链),按规范顺序排列:整数索引在前 /// 升序,其余保持插入序。si 指 interned 字符串键编号(PermInterner 分配的 u32 id)。 /// 返回 `(属性键 si, 绝对存储索引)`:数组对象元素区索引即绝对下标,命名属性 = -/// `array_prop_count + shape 槽位`;普通对象即 shape 槽位。 +/// `array_prop_count + shape 槽位`;普通对象即 shape 槽位。TA 元素键(0..live +/// 长)以哨兵存储索引 `u32::MAX` 推入(无槽位),越界(含 detach)live 长 0 键集空。 pub fn walk_own_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { let mut keys: Vec<(u32, u32)> = Vec::new(); + // TA 元素键:整数下标 0..live 长是可枚举自身属性(元素住 buffer,不在形状 + // 链);越界(含 detach)live 长 0,键集自然空。哨兵存储下标 u32::MAX = + // 无槽位(meta 读一律 None,取值消费方按键路由元素读)。 + if obj.is_typed_array_obj() { + let len = crate::typed_array::ta_view_length(vm, obj) as u32; + for i in 0..len.min(INT_KEY_COUNT) { + keys.push((make_int_key(i), u32::MAX)); + } + } // 数组元素区:整数下标是可枚举自身属性(hole 视为不存在),排在命名属性之前。 if obj.is_array() { for i in 0..obj.array_prop_count { @@ -537,10 +547,17 @@ fn namespace_export_get(obj: &JsObject, key_si: u32) -> Result, /// values/entries 族单自身属性值读(EnumerableOwnProperties 的 Get):模块命名空间 /// 导出走活值查询(未初始化抛 ReferenceError);访问器属性触发 getter(this = 对象 -/// 自身),异常传播原始抛出值;数据属性直读存储槽。 +/// 自身),异常传播原始抛出值;数据属性直读存储槽。TA 元素键无存储槽:live 界判 +/// (含 detach/收缩)直读元素值,访问器语义不适用。 fn own_property_value( vm: &mut H, obj: &JsObject, obj_val: JsValue, si: u32, offset: u32, ) -> Result { + // TA 元素键无存储槽(元素住 buffer):live 界判含 detach/收缩,直读元素值。 + if obj.is_typed_array_obj() && is_int_key(si) { + return Ok( + crate::typed_array::typed_array_element_get(vm, obj, int_key_value(si)).unwrap_or(JsValue::undefined()) + ); + } match namespace_export_get(obj, si) { Ok(Some(value)) => return Ok(value), Ok(None) => {} @@ -1227,6 +1244,7 @@ fn seal_own_prop_meta(obj: &mut JsObject, store: u32) { /// # 副作用 /// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续 /// 写/define 一律回落 ordinary_set / define 检查(writable/configurable 判定) +/// - TA 元素键无 meta 槽,循环跳键(元素零修改) pub fn object_freeze(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Err(crate::error::create_type_error(vm, "Object.freeze called on non-object")); @@ -1248,7 +1266,12 @@ pub fn object_freeze(vm: &mut H, args: &[u8]) -> NativeResult { let obj = unsafe { &mut *obj_ptr }; // 命名属性:walk_own_keys 返回绝对存储索引(数组含元素区偏移)。 let keys = walk_own_keys(vm, obj); - for (_si, pos) in keys { + for (si, pos) in keys { + // TA 元素键无 meta 槽(元素住 buffer):完整性处理不触元素,跳键免 + // set_meta_at 越界扩属性表。 + if obj.is_typed_array_obj() && is_int_key(si) { + continue; + } freeze_own_prop_meta(obj, pos); } // 数组元素区独立于 shape 链(hole 非 own 属性,跳过)。 @@ -1271,6 +1294,7 @@ pub fn object_freeze(vm: &mut H, args: &[u8]) -> NativeResult { /// # 副作用 /// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续 /// define 回落 non-configurable 检查(已有属性写仍可正常进行) +/// - TA 元素键无 meta 槽,循环跳键(元素零修改) pub fn object_seal(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Err(crate::error::create_type_error(vm, "Object.seal called on non-object")); @@ -1283,7 +1307,12 @@ pub fn object_seal(vm: &mut H, args: &[u8]) -> NativeResult { { let obj = unsafe { &mut *obj_ptr }; let keys = walk_own_keys(vm, obj); - for (_si, pos) in keys { + for (si, pos) in keys { + // TA 元素键无 meta 槽(元素住 buffer):完整性处理不触元素,跳键免 + // set_meta_at 越界扩属性表。 + if obj.is_typed_array_obj() && is_int_key(si) { + continue; + } seal_own_prop_meta(obj, pos); } if obj.is_array() { @@ -1373,7 +1402,12 @@ pub fn object_is_sealed(vm: &mut H, args: &[u8]) -> NativeResult { return NativeResult::Ok(JsValue::bool(false)); } let keys = walk_own_keys(vm, obj); - for (_si, offset) in keys { + for (si, offset) in keys { + // 同款跳键:元素键描述符 configurable:true,PE/sealed 的 TA 判 sealed + // (V8 同口径)。 + if obj.is_typed_array_obj() && is_int_key(si) { + continue; + } if let Some(meta) = obj.prop_meta_at(offset) { if meta.attributes.configurable() { return NativeResult::Ok(JsValue::bool(false)); diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 3a931d0a..520a11aa 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -4,7 +4,7 @@ use oxide_runtime_api::{push_units_to, to_boolean, to_units_full}; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::{ int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, make_well_known_symbol_key, - WELL_KNOWN_SYMBOL_HAS_INSTANCE, + INT_KEY_COUNT, WELL_KNOWN_SYMBOL_HAS_INSTANCE, }; use oxide_types::value::JsValue; @@ -453,6 +453,14 @@ impl Vm { } } } + // TA 元素键:整数下标 0..live 长升序排前(尾稳定排序负责序); + // 越界(含 detach)live 长 0 零枚;元素住 buffer,不在形状链。 + if arr.is_typed_array_obj() { + let len = oxide_builtins::typed_array::ta_view_length(self, arr) as u32; + for i in 0..len.min(INT_KEY_COUNT) { + keys_vec.push((self.new_string(&i.to_string()), make_int_key(i))); + } + } } let mut depth = 0usize; diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index 8f117d54..fb8792d2 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -1102,7 +1102,7 @@ fn ta_dop_constraint_false() { for (var i = 0; i < shapes.length; i++) { \ if (Reflect.defineProperty(ta, '0', shapes[i]) !== false) return false; \ if (ta[0] !== 9) return false; } \ - return Object.getOwnPropertyNames(ta).length === 0; })()", + return Object.getOwnPropertyNames(ta).join(',') === '0,1'; })()", ) .unwrap(); assert!(result.as_bool()); @@ -1121,7 +1121,7 @@ fn ta_dop_constraint_throws() { var t2 = 0; \ try { Object.defineProperty(ta, '0', { get: function () { return 1; } }); } \ catch (e) { if (!(e instanceof TypeError)) return false; t2 = 1; } \ - return t1 === 1 && t2 === 1 && ta[0] === 9 && Object.getOwnPropertyNames(ta).length === 0; })()", + return t1 === 1 && t2 === 1 && ta[0] === 9 && Object.getOwnPropertyNames(ta).join(',') === '0,1'; })()", ) .unwrap(); assert!(result.as_bool()); @@ -1139,7 +1139,7 @@ fn ta_dop_numeric_invalid_false() { var d = { value: 1, writable: true, enumerable: true, configurable: true }; \ if (Reflect.defineProperty(ta, keys[i], d) !== false) return false; \ if (ta[0] !== 5) return false; } \ - return Object.getOwnPropertyNames(ta).length === 0; })()", + return Object.getOwnPropertyNames(ta).join(',') === '0,1'; })()", ) .unwrap(); assert!(result.as_bool()); @@ -1218,7 +1218,7 @@ fn ta_dop_value_throws_original() { try { Object.defineProperty(ta, '0', { value: v, writable: true, enumerable: true, configurable: true }); \ return false; } \ catch (e) { caught = e; } \ - return caught === marker && caught.tag === 'marker' && Object.getOwnPropertyNames(ta).length === 0; })()", + return caught === marker && caught.tag === 'marker' && Object.getOwnPropertyNames(ta).join(',') === '0,1'; })()", ) .unwrap(); assert!(result.as_bool()); @@ -1656,13 +1656,235 @@ fn ta_gopd_real_prop_attrs_passthrough() { let result = eval( &mut vm, "(function () { var ta = new Int8Array([42]); \ - Object.defineProperty(ta, '1.0', { \ - value: 7, writable: true, enumerable: true, configurable: false }); \ - var d = Object.getOwnPropertyDescriptor(ta, '1.0'); \ - if (!d || d.value !== 7) return false; \ - if (d.writable !== true || d.enumerable !== true || d.configurable !== false) return false; \ - return ta.hasOwnProperty('1.0') === true \ - && ta.propertyIsEnumerable('1.0') === true; })()", + Object.defineProperty(ta, '1.0', { \ + value: 7, writable: true, enumerable: true, configurable: false }); \ + var d = Object.getOwnPropertyDescriptor(ta, '1.0'); \ + if (!d || d.value !== 7) return false; \ + if (d.writable !== true || d.enumerable !== true || d.configurable !== false) return false; \ + return ta.hasOwnProperty('1.0') === true \ + && ta.propertyIsEnumerable('1.0') === true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_ownkeys_integer_indexes() { + // ownKeys 元素键 = 0..live 长升序:len-3 三键、len-4 四键、subarray 视图 + // 相对下标、len-0 空、BigInt 族同形。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Uint8Array([1, 2, 3]); \ + if (Reflect.ownKeys(ta).join(',') !== '0,1,2') return false; \ + if (Reflect.ownKeys(new Uint8Array(4)).join(',') !== '0,1,2,3') return false; \ + var base = new Int8Array(4); var sub = base.subarray(2); \ + if (Reflect.ownKeys(sub).join(',') !== '0,1') return false; \ + if (Reflect.ownKeys(new Uint8Array(0)).length !== 0) return false; \ + var b = new BigInt64Array([1n, 2n, 3n]); \ + return Reflect.ownKeys(b).join(',') === '0,1,2'; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_ownkeys_order_indexes_strings_symbols() { + // 三段序:整数下标升序 → 真串键创建序 → 符号创建序;原型同键(含整数 + // 索引形态)不入 ownKeys。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Uint8Array([1, 2]); \ + ta.test262 = 'a'; ta.ecma262 = 'b'; \ + var s1 = Symbol('s1'), s2 = Symbol('s2'); \ + ta[s1] = 1; ta[s2] = 2; \ + Uint8Array.prototype[3] = 'x'; Uint8Array.prototype.bar = 'y'; \ + var ks = Reflect.ownKeys(ta); \ + return ks.length === 6 && ks[0] === '0' && ks[1] === '1' \ + && ks[2] === 'test262' && ks[3] === 'ecma262' && ks[4] === s1 && ks[5] === s2; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_keys_values_entries_strings_only() { + // keys = 下标 + 可枚举真串键(符号不入、e:false 真属性不入);values/entries + // 同序含元素值与真属性值。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Uint8Array([1, 2]); \ + ta.foo = 42; \ + Object.defineProperty(ta, 'bar', { value: 7, enumerable: false }); \ + if (Object.keys(ta).join(',') !== '0,1,foo') return false; \ + var vs = Object.values(ta); \ + if (vs.length !== 3 || vs[0] !== 1 || vs[1] !== 2 || vs[2] !== 42) return false; \ + var es = Object.entries(ta); \ + return es.length === 3 && es[0][0] === '0' && es[0][1] === 1 \ + && es[1][0] === '1' && es[1][1] === 2 && es[2][0] === 'foo' && es[2][1] === 42; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_gopn_gopds_include_indexes() { + // getOwnPropertyNames = 下标 + 真串键;getOwnPropertyDescriptors 下标描述符 + // 常量四字段(value + w/e/c 全真),真属性原样。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Uint8Array([1, 2]); \ + ta.foo = 42; \ + if (Object.getOwnPropertyNames(ta).join(',') !== '0,1,foo') return false; \ + var ds = Object.getOwnPropertyDescriptors(ta); \ + var d0 = ds['0']; \ + if (!d0 || d0.value !== 1 || d0.writable !== true \ + || d0.enumerable !== true || d0.configurable !== true) return false; \ + var dfoo = ds['foo']; \ + return !!dfoo && dfoo.value === 42 \ + && dfoo.writable === true && dfoo.enumerable === true && dfoo.configurable === true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_for_in_indexes_then_strings() { + // for-in = 下标升序 + 真串键(符号不入);len-0 与 detach 形仅真属性、无下标。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Uint8Array([1, 2]); \ + ta.foo = 42; var s = Symbol('s'); ta[s] = 1; \ + var ks = []; for (var k in ta) ks.push(k); \ + if (ks.length !== 3 || ks[0] !== '0' || ks[1] !== '1' || ks[2] !== 'foo') return false; \ + var e = new Uint8Array(0); \ + var ke = 0; for (var k2 in e) ke++; \ + if (ke !== 0) return false; \ + var ab = new ArrayBuffer(8); var d = new Uint8Array(ab); d.foo = 1; \ + ab.transfer(); \ + var kd = []; for (var k3 in d) kd.push(k3); \ + return kd.length === 1 && kd[0] === 'foo'; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_assign_spread_rest_copy_indexes() { + // Object.assign / 展开 / 解构 rest 均拷下标值 + 可枚举真属性(rest 排除 + // 已绑定键)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Uint8Array([1, 2]); \ + ta.foo = 9; \ + var o1 = Object.assign({}, ta); \ + if (Object.keys(o1).sort().join(',') !== '0,1,foo') return false; \ + if (o1['0'] !== 1 || o1['1'] !== 2 || o1.foo !== 9) return false; \ + var o2 = { ...ta }; \ + if (o2['0'] !== 1 || o2['1'] !== 2 || o2.foo !== 9) return false; \ + var { foo, ...rest } = ta; \ + return Object.keys(rest).sort().join(',') === '0,1' \ + && rest['0'] === 1 && rest['1'] === 2 && rest.foo === undefined; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_resize_auto_follows_live_length() { + // auto 视图 ownKeys 跟随 live 长:grow/shrink/边界(maxByteLength)四步; + // offset 超 buffer 越界形零枚。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(4, { maxByteLength: 12 }); \ + var ta = new Uint8Array(ab); \ + if (Reflect.ownKeys(ta).join(',') !== '0,1,2,3') return false; \ + ab.resize(8); \ + if (Reflect.ownKeys(ta).join(',') !== '0,1,2,3,4,5,6,7') return false; \ + ab.resize(2); \ + if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \ + ab.resize(12); \ + if (Reflect.ownKeys(ta).join(',') !== '0,1,2,3,4,5,6,7,8,9,10,11') return false; \ + var v = new Uint8Array(ab, 12); \ + return Reflect.ownKeys(v).length === 0; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_resize_fixed_clip_oob_empty() { + // fixed 视图(offset 2 len 2):grow 与窗口内 shrink 键集保持 0,1; + // buffer 收缩裁出窗口即越界零枚。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(8, { maxByteLength: 16 }); \ + var ta = new Uint8Array(ab, 2, 2); \ + if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \ + ab.resize(16); \ + if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \ + ab.resize(4); \ + if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \ + ab.resize(3); \ + return Reflect.ownKeys(ta).length === 0; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_detached_empty_enum() { + // detach 后 live 长 0:ownKeys/keys/values/for-in 仅真属性(序不变、无下标), + // 真属性值不动。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(8); \ + var ta = new Uint8Array(ab); \ + ta.foo = 42; var s = Symbol('s'); ta[s] = 5; \ + ab.transfer(); \ + var ks = Reflect.ownKeys(ta); \ + if (ks.length !== 2 || ks[0] !== 'foo' || ks[1] !== s) return false; \ + if (Object.keys(ta).join(',') !== 'foo') return false; \ + var vs = Object.values(ta); \ + if (vs.length !== 1 || vs[0] !== 42) return false; \ + var kf = []; for (var k in ta) kf.push(k); \ + return kf.length === 1 && kf[0] === 'foo' && ta.foo === 42; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_integrity_freeze_seal_no_element_touch() { + // 完整性家族跳键:seal 后 isSealed 真、元素写仍通、加真属性失败、键集不变、 + // 无损坏(读/长/缓冲不动);freeze len-0 → isFrozen 真;fresh len>0 非冻结; + // PE 的 TA 判 sealed(元素键描述符 c:true 口径)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int8Array([1, 2]); \ + Object.seal(ta); \ + if (Object.isSealed(ta) !== true) return false; \ + ta[0] = 9; \ + if (ta[0] !== 9 || ta[1] !== 2 || ta.length !== 2) return false; \ + if (Reflect.defineProperty(ta, 'x', { value: 1, configurable: true }) !== false) return false; \ + if (Reflect.ownKeys(ta).join(',') !== '0,1') return false; \ + var f = new Int8Array(0); \ + Object.freeze(f); \ + if (Object.isFrozen(f) !== true) return false; \ + var t2 = new Int8Array([5, 6]); \ + if (Object.isFrozen(t2) !== false || Object.isSealed(t2) !== false) return false; \ + var t3 = new Int8Array(2); \ + Object.preventExtensions(t3); \ + return Object.isSealed(t3) === true; })()", ) .unwrap(); assert!(result.as_bool());