From d4cd52ba90c7b71b0e6daa3fb2b38503141ca4e0 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 12 Aug 2026 12:52:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=95=B0=E7=BB=84=20length=20=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=20ArraySetLength=20=E8=AF=AD=E4=B9=89=E4=B8=8E?= =?UTF-8?q?=E8=B6=85=E9=95=BF=E9=80=BB=E8=BE=91=E9=95=BF=E5=BA=A6=EF=BC=88?= =?UTF-8?q?a.length=20=E8=AF=BB=E5=8F=96/=E6=94=B6=E7=BC=A9=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C/=E7=A9=BA=E6=B4=9E=E5=90=8C=E6=AD=A5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_types/src/object.rs | 39 +++++++++++++++++++++++++++++++ crates/oxide_vm/src/vm.rs | 2 +- crates/oxide_vm/src/vm_props.rs | 40 +++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 03f12f93..7407109b 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -399,6 +399,10 @@ pub struct JsObject { /// 数组元素数(数组对象)。普通对象恒 0。命名属性存储在 `hash_props` /// (与元素区分,JS 数组命名属性不影响 length)。 pub array_prop_count: u32, + /// 数组逻辑长度覆盖:仅当 length 超出 dense 存储上限 `MAX_DENSE_PROPS` 时非 0。 + /// `arr.length = 4294967295` 等超大长度时,元素区仍以 `MAX_DENSE_PROPS` 封顶, + /// 超出部分视为稀疏空洞,逻辑长度单独记录供 `a.length` 读取。 + array_len_override: u32, native_fn: Option, sub_module_index: u32, _pad3: [u8; 4], @@ -576,6 +580,7 @@ impl JsObject { proto, generation: 1, array_prop_count: 0, + array_len_override: 0, native_fn: None, sub_module_index: 0, _pad3: [0; 4], @@ -601,6 +606,7 @@ impl JsObject { proto, generation: 1, array_prop_count: 0, + array_len_override: 0, native_fn: None, sub_module_index: 0, _pad3: [0; 4], @@ -611,6 +617,8 @@ impl JsObject { let vec = Box::new(vec![JsValue::undefined(); n_elements.min(MAX_DENSE_PROPS)]); obj.array_elements = Box::into_raw(vec) as *mut u8; obj.array_prop_count = n_elements.min(MAX_DENSE_PROPS) as u32; + // 逻辑长度单独记录(超过 dense 上限时),供 a.length 读取。 + obj.array_len_override = if n_elements > MAX_DENSE_PROPS { n_elements as u32 } else { 0 }; obj } @@ -681,6 +689,7 @@ impl JsObject { proto: self.proto, generation: self.generation, array_prop_count: self.array_prop_count, + array_len_override: self.array_len_override, native_fn: self.native_fn, sub_module_index: self.sub_module_index, _pad3: self._pad3, @@ -837,6 +846,36 @@ impl JsObject { } } + /// 数组逻辑长度:`a.length` 读取语义(含超出 dense 上限的覆盖值)。 + #[inline] + pub fn logical_len(&self) -> u32 { + if self.array_len_override != 0 { + self.array_len_override + } else { + self.array_prop_count + } + } + + /// 设置数组逻辑长度覆盖(仅在 length > `MAX_DENSE_PROPS` 时使用)。 + pub fn set_array_len_override(&mut self, len: u32) { + self.array_len_override = len; + } + + /// 清除数组逻辑长度覆盖,恢复以 `array_prop_count` 为 length。 + pub fn clear_array_len_override(&mut self) { + self.array_len_override = 0; + } + + /// 数组 length 属性值(`a.length` 读取结果)。 + pub fn logical_len_value(&self) -> JsValue { + let l = self.logical_len(); + if l <= i32::MAX as u32 { + JsValue::int(l as i32) + } else { + JsValue::float(l as f64) + } + } + /// 设置数组元素数 / 命名属性向量长度。截断或补 undefined 扩展。 /// 数组对象只调整独立元素区(`array_elements` + `array_prop_count`),命名属性区 /// (`hash_props`)零搬移——push 为摊销 O(1) 的 `Vec::push`。 diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 545ab46a..e0625256 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -934,7 +934,7 @@ impl Vm { vm_trace!("resolve_property: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si); let length_si = self.kernel_core.perm_interner().intern("length").0; if obj.is_array() && prop_name_si == length_si { - return Some(JsValue::int(obj.prop_count() as i32)); + return Some(obj.logical_len_value()); } if obj.is_array() { if let Some(index) = self.array_index_from_property_key(prop_name_si) { diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 8c9e38e1..b1a77846 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -26,7 +26,7 @@ impl Vm { let mut depth = 0usize; while let Some(obj) = current { if obj.is_array() && prop_name_si == length_si { - return Ok(JsValue::int(obj.prop_count() as i32)); + return Ok(obj.logical_len_value()); } if obj.is_array() { if let Some(index) = self.array_index_from_property_key(prop_name_si) { @@ -177,6 +177,44 @@ impl Vm { return oxide_builtins::typed_array::typed_array_element_set(self, obj, index, val); } } + // 数组 length 赋值:ArraySetLength 语义(ToUint32 + 调整元素区)。 + // 旧行为会把 length 存成影子命名属性,导致 `arr.length = N` 后 + // prop_count/迭代/内置方法看到的长度不一致。 + let length_si = self.kernel_core.perm_interner().intern("length").0; + if obj.is_array() && prop_name_si == length_si { + let number_len = self.coerce_number_bounded(val)?; + let raw_new_len = if number_len == 0.0 || !number_len.is_finite() { + 0 + } else { + number_len.trunc().rem_euclid(4_294_967_296.0) as u32 as usize + }; + // ToUint32 != ToNumber (e.g. 1.5 / NaN / Infinity / negative / 2**32) -> RangeError. + if raw_new_len as f64 != number_len { + return self.raise_error_kind("RangeError", "Invalid array length"); + } + let old_count = obj.array_prop_count as usize; + // Dense storage caps at MAX_DENSE_PROPS; larger lengths stay at the cap. + let new_len_u = raw_new_len.min(oxide_types::object::MAX_DENSE_PROPS); + obj.set_prop_count(new_len_u); + // Grown slots are sparse holes: HasProperty / prototype reads must treat + // them as absent. + for idx in old_count..new_len_u { + obj.mark_hole_at(idx); + } + // Logical length override: lengths beyond the dense cap are recorded + // separately so `a.length` reads return the real value. + if raw_new_len > oxide_types::object::MAX_DENSE_PROPS { + obj.set_array_len_override(raw_new_len as u32); + } else { + obj.clear_array_len_override(); + } + // 同步旧版可能残留的影子 length 属性(shape 槽),保证 IC 快路径读到新值。 + if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), length_si) { + let store_idx = obj.array_prop_count as usize + pos as usize; + obj.set_prop_storage(store_idx, obj.logical_len_value()); + } + return Ok(()); + } if let Some(pos) = self.get_own_property_slot(obj, prop_name_si) { if let Some(meta) = obj.prop_meta_at(pos) { if meta.is_accessor {