fix: 数组 length 赋值 ArraySetLength 语义与超长逻辑长度(a.length 读取/收缩校验/空洞同步)

This commit is contained in:
2026-08-12 12:52:02 +08:00
parent e87515a27f
commit d4cd52ba90
3 changed files with 79 additions and 2 deletions
+39
View File
@@ -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<NativeFnPtr>,
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`。
+1 -1
View File
@@ -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) {
+39 -1
View File
@@ -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 {