diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index bef854c7..33dbda65 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -658,6 +658,15 @@ impl JsObject { vec.drain(target..old); } self.array_prop_count = target as u32; + if let Some(meta) = self.prop_meta_vec_mut() { + if target > old { + for _ in old..target { + meta.insert(old, None); + } + } else if target < old { + meta.drain(target..old); + } + } } else { let vec = self.ensure_hash_props(); if target < vec.len() { @@ -667,13 +676,13 @@ impl JsObject { vec.push(JsValue::undefined()); } } - } - if let Some(meta) = self.prop_meta_vec_mut() { - if target < meta.len() { - meta.truncate(target); - } else { - while meta.len() < target { - meta.push(None); + if let Some(meta) = self.prop_meta_vec_mut() { + if target < meta.len() { + meta.truncate(target); + } else { + while meta.len() < target { + meta.push(None); + } } } } @@ -899,6 +908,15 @@ impl JsObject { if pos > MAX_DENSE_PROPS { return; } + if self.is_array() { + // 元素写入越过元素区:先搬移属性区到 pos+1 之后,保持元素/属性分界不变。 + if pos >= self.array_prop_count as usize { + self.set_prop_count(pos + 1); + } + let vec = self.ensure_hash_props(); + vec[pos] = val; + return; + } { let vec = self.ensure_hash_props(); if pos < vec.len() { @@ -915,9 +933,6 @@ impl JsObject { meta.push(None); } } - if self.is_array() && pos >= self.array_prop_count as usize { - self.array_prop_count = (pos + 1) as u32; - } } /// 数组对象(shape 槽位 → 存储索引 = `array_prop_count + 槽位`)的 @@ -940,6 +955,21 @@ impl JsObject { } } + /// 按绝对存储索引写入属性,不触发数组元素区搬移(属性区已在元素之后)。 + /// 用于调用方已知属性存储位置(如 `get_own_property_slot` 返回的索引)的场景。 + pub fn set_prop_storage(&mut self, idx: usize, val: JsValue) { + let vec = self.ensure_hash_props(); + while vec.len() <= idx { + vec.push(JsValue::undefined()); + } + vec[idx] = val; + if let Some(meta) = self.prop_meta_vec_mut() { + while meta.len() <= idx { + meta.push(None); + } + } + } + /// 数组对象属性读取(shape 槽位 → 存储索引 = `array_prop_count + 槽位`); /// 普通对象等价 `get_prop_at`。越界返回 undefined。 pub fn get_prop_shape(&self, shape_pos: u32) -> JsValue { @@ -1299,5 +1329,42 @@ mod tests { obj.push_prop(JsValue::int(4)); assert_eq!(obj.prop_meta_vec().expect("meta").len(), obj.prop_vec_len()); } + + #[test] + fn array_element_write_preserves_props_after_element_growth() { + // 先写属性再 push 元素:元素区增长必须整体搬移属性区,不覆盖属性。 + let bump = bumpalo::Bump::new(); + let mut obj = JsObject::new_array(EMPTY_SHAPE_ID, JsValue::null(), 3, &bump); + obj.set_prop_shape(0, JsValue::int(99)); + obj.set_prop_at(3, JsValue::int(4)); + assert_eq!(obj.prop_count(), 4); + assert_eq!(obj.get_prop_at(3), JsValue::int(4)); + assert_eq!(obj.get_prop_shape(0), JsValue::int(99)); + } + + #[test] + fn array_element_write_beyond_count_relocates_prop_zone() { + // 稀疏写入(越界索引)把属性区推到新元素之后,属性读取仍命中。 + let bump = bumpalo::Bump::new(); + let mut obj = JsObject::new_array(EMPTY_SHAPE_ID, JsValue::null(), 2, &bump); + obj.set_prop_shape(0, JsValue::int(7)); + obj.set_prop_at(5, JsValue::int(50)); + assert_eq!(obj.prop_count(), 6); + assert_eq!(obj.get_prop_at(5), JsValue::int(50)); + assert_eq!(obj.get_prop_shape(0), JsValue::int(7)); + } + + #[test] + fn array_prop_count_truncate_keeps_prop_zone() { + // pop 截断元素区时属性区不得被删(meta 同步 insert/drain 对齐)。 + let bump = bumpalo::Bump::new(); + let mut obj = JsObject::new_array(EMPTY_SHAPE_ID, JsValue::null(), 3, &bump); + obj.set_prop_shape(0, JsValue::int(5)); + obj.set_data_meta(3, PropAttributes::new(true, false, true)); + obj.set_prop_count_fast(2); + assert_eq!(obj.prop_count(), 2); + assert_eq!(obj.get_prop_shape(0), JsValue::int(5)); + assert!(obj.prop_meta_at(2).is_some()); + } } diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 3dc46082..6fcb6d88 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -114,7 +114,7 @@ impl Vm { }; let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]); let obj = unsafe { &mut *obj_ptr }; - obj.set_prop_at(pos, value); + obj.set_prop_shape(pos, value); Ok(()) } @@ -128,7 +128,7 @@ impl Vm { let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]); let obj = unsafe { &mut *obj_ptr }; if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key) { - obj.set_prop_at(pos, value); + obj.set_prop_shape(pos, value); } else { self.set_or_create_prop_value(obj, private_key, value); } @@ -277,7 +277,7 @@ impl Vm { } else if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { self.profiling.record_ic_miss(); prop_cache_miss(); - obj.set_prop_at(pos, value); + obj.set_prop_shape(pos, value); ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0); ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos); } else { @@ -420,7 +420,7 @@ impl Vm { if !configurable { return self.raise_type_error("cannot delete non-configurable property").map(|()| false); } - obj.set_prop_at(pos, JsValue::undefined()); + obj.set_prop_shape(pos, JsValue::undefined()); } self.regs[rd] = JsValue::bool(true); Ok(false) @@ -438,7 +438,7 @@ impl Vm { if !configurable { return self.raise_type_error("cannot delete non-configurable property").map(|()| false); } - obj.set_prop_at(pos, JsValue::undefined()); + obj.set_prop_shape(pos, JsValue::undefined()); } self.regs[rd] = JsValue::bool(true); Ok(false) diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index d011c37d..05a5b2a4 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -704,7 +704,6 @@ impl Vm { // 数组属性存储索引 = array_prop_count + shape 槽位(与元素区分)。 let idx = obj.array_prop_count as usize + pos as usize; let val = obj.get_prop_at(idx); - eprintln!("[DBG] gos array prop pos={} apc={} idx={} val={:?}", pos, obj.array_prop_count, idx, val); if !val.is_undefined() { Some(idx as u32) } else { diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 2478381f..3dab2494 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -175,7 +175,8 @@ impl Vm { return self.raise_type_error("cannot assign to read-only property"); } } - obj.set_prop_at(pos, val); + // pos 是存储索引(get_own_property_slot 对数组已加元素区偏移)。 + obj.set_prop_storage(pos as usize, val); return Ok(()); } @@ -248,7 +249,7 @@ impl Vm { self.proto_chain_ic_get(obj, prop_name_si, receiver)? } else if template.position < obj.prop_vec_len() as u32 { crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), template.position, 0); - obj.get_prop_at(template.position) + obj.get_prop_shape(template.position) } else { self.proto_chain_ic_get(obj, prop_name_si, receiver)? } @@ -298,7 +299,7 @@ impl Vm { self.ordinary_set(obj, prop_name_si, val, receiver)?; return Ok(()); } - obj.set_prop_at(pos, val); + obj.set_prop_shape(pos, val); crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0); } else { self.ordinary_set(obj, prop_name_si, val, receiver)?; @@ -324,6 +325,12 @@ impl Vm { ) -> Result<(), String> { vm_trace!("define_data_property: shape={} prop_si={}", obj.shape_id(), prop_name_si); let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val); + if obj.is_array() { + if let Some(index) = self.array_index_from_property_key(prop_name_si) { + // 数组索引属性存元素区并维护 array_prop_count(元素数随索引增长)。 + return self.define_array_index_element(obj, index, val, attributes, false, JsValue::undefined(), JsValue::undefined()); + } + } let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { // shape 槽位 → 存储索引(数组属性在元素区之后)。 if obj.is_array() { @@ -354,7 +361,7 @@ impl Vm { } } } - obj.set_prop_at(pos, val); + obj.set_prop_storage(pos, val); obj.set_data_meta(pos, attributes); obj.bump_generation(); Ok(()) @@ -367,12 +374,22 @@ impl Vm { let target_ptr = obj as *mut JsObject; let get = self.promote_if_needed_for_write_ptr(target_ptr, get); let set = self.promote_if_needed_for_write_ptr(target_ptr, set); + if obj.is_array() { + if let Some(index) = self.array_index_from_property_key(prop_name_si) { + return self.define_array_index_element(obj, index, JsValue::undefined(), attributes, true, get, set); + } + } let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) { - pos + // shape 槽位 → 存储索引(数组属性在元素区之后)。 + if obj.is_array() { + obj.array_prop_count as usize + pos as usize + } else { + pos as usize + } } else { let new_shape_id = self.kernel_core.shape_forge().make_shape(obj.shape_id(), prop_name_si); obj.set_shape_id(new_shape_id); - obj.push_prop(JsValue::undefined()) + obj.push_prop(JsValue::undefined()) as usize }; if let Some(current) = obj.prop_meta_at(pos) { if !current.attributes.configurable() @@ -385,9 +402,58 @@ impl Vm { return self.raise_type_error("cannot redefine non-configurable property"); } } - obj.set_prop_at(pos, JsValue::undefined()); + obj.set_prop_storage(pos, JsValue::undefined()); obj.set_accessor_meta(pos, get, set, attributes); obj.bump_generation(); Ok(()) } + + /// 数组索引属性(`"0"`~`"4294967294"`)的 define 路径:存入元素区并维护 + /// `array_prop_count`(length 随最高索引增长),meta 与元素槽对齐。 + fn define_array_index_element( + &mut self, + obj: &mut JsObject, + index: u32, + val: JsValue, + attributes: PropAttributes, + is_accessor: bool, + get: JsValue, + set: JsValue, + ) -> Result<(), String> { + let pos = index as usize; + if pos > oxide_types::object::MAX_DENSE_PROPS { + return self.raise_type_error("array index out of dense range"); + } + let pos = pos as u32; + if let Some(current) = obj.prop_meta_at(pos) { + if !current.attributes.configurable() { + if current.is_accessor != is_accessor { + return self.raise_type_error("cannot redefine non-configurable property"); + } + if current.attributes.enumerable() != attributes.enumerable() { + return self.raise_type_error("cannot redefine non-configurable property"); + } + if current.attributes.configurable() != attributes.configurable() { + return self.raise_type_error("cannot redefine non-configurable property"); + } + if is_accessor { + if current.get != get || current.set != set { + return self.raise_type_error("cannot redefine non-configurable property"); + } + } else if !current.attributes.writable() + && (attributes.writable() || !coercion::same_value(obj.get_prop_at(pos), val)) + { + return self.raise_type_error("cannot redefine non-configurable property"); + } + } + } + obj.set_prop_at(pos, val); + if is_accessor { + obj.set_accessor_meta(pos, get, set, attributes); + } else { + obj.set_data_meta(pos, attributes); + } + obj.bump_generation(); + Ok(()) + } }