mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 数组元素写入越过元素区时搬移属性区 + defineProperty 数组索引走元素区
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user