diff --git a/crates/oxide_vm/src/vm/props.rs b/crates/oxide_vm/src/vm/props.rs index 4843d16b..e0c425f6 100644 --- a/crates/oxide_vm/src/vm/props.rs +++ b/crates/oxide_vm/src/vm/props.rs @@ -214,11 +214,16 @@ impl Vm { /// 存在性判定(规范 HasProperty):自身与原型链逐级检查,任一层 P 为自有 /// 属性即存在。数组 length 虚拟属性恒存在;数组元素区 hole 视同缺失; - /// TypedArray 整数索引按视图长度判在界;其余按 shape 槽判定。 + /// 其余按 shape 槽判定。 /// - /// 与 `resolve_property` 的差异:原型链上每层都检查数组元素区与 TypedArray - /// 元素(`resolve_property` 只在顶层检查元素区,链上仅走 shape 槽), - /// 继承自父数组/TypedArray 的索引属性在此判存在。 + /// 顶层 TypedArray 经统一数值键门(exotic [[HasProperty]]):界内整数索引 + /// 判存在;数字无效键(负/分数/±Infinity/NaN/越界,含 "-0" 特例)立即 + /// false,不查自身命名属性也不走原型链;非规范数字串落普通路径。原型链上 + /// 的 TA 按普通对象查 shape 槽。 + /// + /// 与 `resolve_property` 的差异:原型链上每层都检查数组元素区 + /// (`resolve_property` 只在顶层检查元素区,链上仅走 shape 槽), + /// 继承自父数组的索引属性在此判存在。 pub(crate) fn has_property(&self, obj: &JsObject, prop_name_si: u32) -> bool { let length_si = self.length_si; let mut current = Some(obj); @@ -227,13 +232,13 @@ impl Vm { if obj.is_array() && prop_name_si == length_si { return true; } - if obj.is_typed_array_obj() { - if let Some((index, len)) = - oxide_builtins::typed_array::typed_array_integer_index(self, obj, prop_name_si) - { - if index < len { - return true; - } + // 统一数值键门(exotic [[HasProperty]]):同 `ordinary_get_inner` + // 的口径,门只落在顶层对象。 + if obj.is_typed_array_obj() && depth == 0 { + match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) { + oxide_builtins::typed_array::TaIndexGate::NumericValid(_) => return true, + oxide_builtins::typed_array::TaIndexGate::NumericInvalid => return false, + oxide_builtins::typed_array::TaIndexGate::Ordinary => {} } } if self.get_own_property_slot(obj, prop_name_si).is_some() { diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index af41c7ea..0e903b08 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -12,7 +12,8 @@ impl Vm { /// # 步骤 /// 1. 数组 length 键返回逻辑长度值,不落属性存储。 /// 2. 数组整数索引在元素区命中且非 hole 时返回元素值;访问器元素触发 getter。 - /// 3. TypedArray 整数索引读底层 buffer。 + /// 3. 顶层 TypedArray 经统一数值键门:界内整数读底层 buffer,数字无效键 + /// 立即 undefined,非规范数字串落普通路径。 /// 4. 命名属性槽命中返回槽值;accessor 触发 getter。 /// 5. 全部 miss 时沿原型链逐层查找,深度以 `MAX_PROTO_CHAIN_DEPTH` 为界。 /// @@ -99,9 +100,19 @@ impl Vm { } } } - if obj.is_typed_array_obj() { - if let Some(index) = self.array_index_from_property_key(prop_name_si) { - return oxide_builtins::typed_array::typed_array_element_get(self, obj, index); + // 统一数值键门(exotic [[Get]]):界内整数读底层 buffer;数字无效 + // 键(负/分数/±Infinity/NaN/越界,含 "-0" 特例)立即 undefined 不 + // 查自身命名属性也不走原型链;非规范数字串落下方普通属性路径。 + // 门只落在顶层对象:原型链上的 TA 维持普通 shape 槽查找。 + if obj.is_typed_array_obj() && depth == 0 { + match oxide_builtins::typed_array::ta_index_gate(self, obj, prop_name_si) { + oxide_builtins::typed_array::TaIndexGate::NumericValid(index) => { + return oxide_builtins::typed_array::typed_array_element_get(self, obj, index); + } + oxide_builtins::typed_array::TaIndexGate::NumericInvalid => { + return Ok(JsValue::undefined()); + } + oxide_builtins::typed_array::TaIndexGate::Ordinary => {} } } if let Some(pos) = self.get_own_property_slot(obj, prop_name_si) { diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index dfdd780d..73629f4d 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -750,3 +750,91 @@ fn typed_array_plain_member_call_in_class_ctor_not_construct() { .unwrap(); assert!(result.as_bool()); } + +#[test] +fn ta_gate_get_numeric_invalid_undefined() { + // 数字无效键(分数/负/"-0"/越界整数)读 undefined:exotic 数字臂不走 + // 原型链,原型同键抛 getter 不被触达。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int8Array([42, 43]); \ + var keys = ['1.1', '-1', '-0', '2']; \ + for (var i = 0; i < keys.length; i++) { \ + Object.defineProperty(Int8Array.prototype, keys[i], { \ + get: function () { throw new Error('OrdinaryGet was called'); } }); } \ + for (var i = 0; i < keys.length; i++) { \ + if (ta[keys[i]] !== undefined) return false; } \ + return true; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_gate_has_numeric_invalid_false() { + // 数字无效键 in 判 false:exotic 数字臂不走原型链,原型同键数据属性 + // 不被触达(防假阳)。length-1 样本覆盖越界整数键。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int8Array([42]); \ + var keys = ['1.1', '0.000001', '-1', '-0']; \ + for (var i = 0; i < keys.length; i++) { \ + Int8Array.prototype[keys[i]] = 'test262'; } \ + Int8Array.prototype[1] = 'test262'; \ + for (var i = 0; i < keys.length; i++) { \ + if (keys[i] in ta) return false; } \ + return !(1 in ta); })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_gate_ordinary_arm_proto_walk() { + // 非规范数字串(round-trip 不成)落普通属性路径:缺失读 undefined, + // 原型预置后继承读值且 in 判 true;"+1" 与 "1" 独立成键互不干扰。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ta = new Int8Array([42]); \ + var keys = ['1.0', '+1', '1000000000000000000000', '0.0000001']; \ + for (var i = 0; i < keys.length; i++) { \ + if (ta[keys[i]] !== undefined) return false; } \ + for (var i = 0; i < keys.length; i++) { \ + Int8Array.prototype[keys[i]] = 'test262'; \ + if (ta[keys[i]] !== 'test262') return false; \ + if (!(keys[i] in ta)) return false; } \ + return ta['1'] === undefined; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_gate_valid_arm_no_drift() { + // 界内整数臂回归钉:读元素值、in 判 true、length 不变(Valid 臂零漂移)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ta = new Int8Array([42, 7]); \ + ta[0] === 42 && (0 in ta) && (1 in ta) && ta.length === 2", + ) + .unwrap(); + assert!(result.as_bool()); +} + +#[test] +fn ta_gate_detached_numeric_invalid() { + // detach 后 live 长 0:界内整数键归数字无效,读 undefined、in 判 false。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "(function () { var ab = new ArrayBuffer(8); var ta = new Int8Array(ab); \ + ta[0] = 9; ab.transfer(); \ + return ta[0] === undefined && !(0 in ta); })()", + ) + .unwrap(); + assert!(result.as_bool()); +}