mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: delete 真正删除属性(shape 链重建 + 数组元素 hole 标记)
字节码 delete 原先只清值为 undefined,属性残留在 shape 链,hasOwnProperty/ keys/枚举仍可见。统一走共享 delete_own_property(与 Reflect.deleteProperty 一致): - 命名属性经 shape 链重建移除,数组重建前保存并恢复元素区 - 数组下标元素标记为 hole(PropMetaEntry.hole),length 不变, 存在性检查(resolve_property/get_own_property_slot/ordinary_get/for-in) 判 hole,重赋值自动清除标记 - 不可配置属性统一返回 false(sloppy 语义,strict 抛错留待 emit 追踪严格模式) - set_or_create_prop_value 数组下标键路由元素区,修复 delete 后重赋值误入 shape 链 test262 Object +212 PASS/-102 FAIL,Array +149 PASS/-60 FAIL,无回归。
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
||||||
use oxide_kernel::string_forge::PermInterner;
|
use oxide_kernel::string_forge::PermInterner;
|
||||||
use oxide_types::object::{JsObject, PropAttributes};
|
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
|
||||||
use oxide_types::private_key::is_private_name_key;
|
use oxide_types::private_key::is_private_name_key;
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
@@ -54,6 +54,126 @@ pub(crate) fn walk_own_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)
|
|||||||
keys
|
keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 字符串键是否为数组下标("0"~"4294967294",无前导零)。
|
||||||
|
fn array_index_of<H: VmHost>(vm: &H, key_si: u32) -> Option<u32> {
|
||||||
|
let key = vm.kernel_core().perm_interner().lookup(key_si)?;
|
||||||
|
if key.is_empty() || (key.len() > 1 && key.starts_with('0')) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
key.parse::<u32>().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除对象自身属性(字节码 delete 与 Reflect.deleteProperty 共用)。
|
||||||
|
///
|
||||||
|
/// 数组下标键在元素区(shape 链外),标记为 hole(值 undefined + hole meta),
|
||||||
|
/// length 不变;命名属性经 shape 链重建移除。属性不可配置时返回 false。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. 数组下标元素:检查 configurable,`mark_hole_at` 标记为 hole
|
||||||
|
/// 2. 命名属性:walk_own_keys 定位槽位,不可配置返回 false
|
||||||
|
/// 3. 数组先保存元素区(值 + meta),重建命名属性后恢复元素区
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 键不在对象自身(含原型链属性)返回 true
|
||||||
|
/// - 非 configurable 属性返回 false
|
||||||
|
/// - `key_si` 须已 intern
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 修改 obj 的 shape_id、属性表与 generation;数组元素区内容不变
|
||||||
|
///
|
||||||
|
/// # 注意事项
|
||||||
|
/// - 数组元素存在性以 `prop_meta_at` 的 hole 标记判定,删除后重新写入元素
|
||||||
|
/// 会自动清除 hole 标记恢复存在
|
||||||
|
pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> bool {
|
||||||
|
// 数组下标元素在元素区,不参与 shape 链,单独删除(保持 length 不变)。
|
||||||
|
if obj.is_array() {
|
||||||
|
if let Some(index) = array_index_of(vm, key_si) {
|
||||||
|
if index < obj.array_prop_count {
|
||||||
|
let meta = obj.prop_meta_at(index);
|
||||||
|
// 已是 hole 视为不存在;非 configurable 不可删。
|
||||||
|
if meta.is_some_and(|m| m.is_hole()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if meta.is_some_and(|m| !m.attributes.configurable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
obj.mark_hole_at(index);
|
||||||
|
obj.bump_generation();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let keys = walk_own_keys(vm, obj);
|
||||||
|
let Some((_, delete_pos)) = keys.iter().find(|(si, _)| *si == key_si).copied() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
// delete_pos 是 shape 槽位;数组对象存储索引 = 元素数 + 槽位。
|
||||||
|
let delete_store = if obj.is_array() { obj.array_prop_count + delete_pos } else { delete_pos };
|
||||||
|
if obj
|
||||||
|
.prop_meta_at(delete_store)
|
||||||
|
.map(|meta| !meta.attributes.configurable())
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数组重建前保存元素区(值 + meta),重建后恢复到命名属性之前。
|
||||||
|
let saved_elements: Vec<JsValue> = if obj.is_array() {
|
||||||
|
(0..obj.array_prop_count).map(|i| obj.get_prop_at(i)).collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
let saved_element_meta: Option<Vec<Option<PropMetaEntry>>> = if obj.is_array() {
|
||||||
|
obj.prop_meta_vec().map(|meta| meta[..obj.array_prop_count as usize].to_vec())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let retained: Vec<(u32, JsValue, Option<PropMetaEntry>)> = keys
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(_, pos)| *pos != delete_pos)
|
||||||
|
.map(|(si, pos)| {
|
||||||
|
let store = if obj.is_array() { obj.array_prop_count + pos } else { pos };
|
||||||
|
(si, obj.get_prop_at(store), obj.prop_meta_at(store))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// 重建 shape 链与属性表(数组先清空,含元素区,随后恢复)。
|
||||||
|
obj.set_shape_id(EMPTY_SHAPE_ID);
|
||||||
|
obj.clear_props();
|
||||||
|
for (si, value, meta) in retained {
|
||||||
|
let shape = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si);
|
||||||
|
obj.set_shape_id(shape);
|
||||||
|
let pos = obj.push_prop(value);
|
||||||
|
if let Some(meta) = meta {
|
||||||
|
if meta.is_accessor {
|
||||||
|
obj.set_accessor_meta(pos, meta.get, meta.set, meta.attributes);
|
||||||
|
} else {
|
||||||
|
obj.set_data_meta(pos, meta.attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obj.is_array() {
|
||||||
|
let n = saved_elements.len();
|
||||||
|
obj.set_prop_count(n);
|
||||||
|
for (i, val) in saved_elements.into_iter().enumerate() {
|
||||||
|
obj.set_prop_at(i, val);
|
||||||
|
}
|
||||||
|
if let Some(saved_meta) = saved_element_meta {
|
||||||
|
let meta = obj.ensure_prop_meta();
|
||||||
|
for (i, entry) in saved_meta.into_iter().enumerate() {
|
||||||
|
if let Some(entry) = entry {
|
||||||
|
meta[i] = Some(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.bump_generation();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// JS `Object()` 构造逻辑:创建空对象(prototype 为 null,由 VM 补装内置原型)。
|
/// JS `Object()` 构造逻辑:创建空对象(prototype 为 null,由 VM 补装内置原型)。
|
||||||
pub fn object_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn object_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
@@ -303,7 +423,8 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
|
|||||||
"accessor descriptor get/set must be callable or undefined",
|
"accessor descriptor get/set must be callable or undefined",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Err(e) = vm.define_accessor_property(obj, si, get, set, PropAttributes::new(false, enumerable, configurable))
|
if let Err(e) =
|
||||||
|
vm.define_accessor_property(obj, si, get, set, PropAttributes::new(false, enumerable, configurable))
|
||||||
{
|
{
|
||||||
return NativeResult::Err(crate::error::create_type_error(vm, &e));
|
return NativeResult::Err(crate::error::create_type_error(vm, &e));
|
||||||
}
|
}
|
||||||
@@ -320,7 +441,8 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
|
|||||||
} else {
|
} else {
|
||||||
writable_field.map(oxide_runtime_api::to_boolean).unwrap_or(false)
|
writable_field.map(oxide_runtime_api::to_boolean).unwrap_or(false)
|
||||||
};
|
};
|
||||||
if let Err(e) = vm.define_data_property(obj, si, value, PropAttributes::new(writable, enumerable, configurable)) {
|
if let Err(e) = vm.define_data_property(obj, si, value, PropAttributes::new(writable, enumerable, configurable))
|
||||||
|
{
|
||||||
return NativeResult::Err(crate::error::create_type_error(vm, &e));
|
return NativeResult::Err(crate::error::create_type_error(vm, &e));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -912,4 +1034,3 @@ pub fn object_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
}
|
}
|
||||||
NativeResult::Ok(JsValue::from_js_object(arr))
|
NativeResult::Ok(JsValue::from_js_object(arr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||||
use oxide_types::mem::P;
|
use oxide_types::mem::P;
|
||||||
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
|
use oxide_types::object::{JsObject, PropAttributes};
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
use crate::object::walk_own_keys;
|
use crate::object::{delete_own_property, walk_own_keys};
|
||||||
|
|
||||||
use oxide_runtime_api::{NativeResult, VmHost};
|
use oxide_runtime_api::{NativeResult, VmHost};
|
||||||
|
|
||||||
@@ -44,7 +44,8 @@ pub fn reflect_construct<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
}
|
}
|
||||||
// 不可构造:箭头函数、native 方法(非构造器,OBJ_TYPE_CONSTRUCTOR 标记的除外)。
|
// 不可构造:箭头函数、native 方法(非构造器,OBJ_TYPE_CONSTRUCTOR 标记的除外)。
|
||||||
let nt = unsafe { &*new_target_ptr };
|
let nt = unsafe { &*new_target_ptr };
|
||||||
if nt.is_arrow() || (nt.native_fn().is_some() && nt.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR) {
|
if nt.is_arrow() || (nt.native_fn().is_some() && nt.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR)
|
||||||
|
{
|
||||||
return type_error(vm, "Reflect.construct newTarget is not a constructor");
|
return type_error(vm, "Reflect.construct newTarget is not a constructor");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,40 +289,3 @@ fn make_string_array<H: VmHost>(vm: &mut H, parts: &[String]) -> JsValue {
|
|||||||
unsafe { &mut *arr }.set_prop_count(parts.len());
|
unsafe { &mut *arr }.set_prop_count(parts.len());
|
||||||
JsValue::from_js_object(arr)
|
JsValue::from_js_object(arr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> bool {
|
|
||||||
let keys = walk_own_keys(vm, obj);
|
|
||||||
let Some((_, delete_pos)) = keys.iter().find(|(si, _)| *si == key_si).copied() else {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
if obj
|
|
||||||
.prop_meta_at(delete_pos)
|
|
||||||
.map(|meta| !meta.attributes.configurable())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let retained: Vec<(u32, JsValue, Option<PropMetaEntry>)> = keys
|
|
||||||
.into_iter()
|
|
||||||
.filter(|(_, pos)| *pos != delete_pos)
|
|
||||||
.map(|(si, pos)| (si, obj.get_prop_at(pos), obj.prop_meta_at(pos)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
obj.set_shape_id(EMPTY_SHAPE_ID);
|
|
||||||
obj.set_prop_count(0usize);
|
|
||||||
for (si, value, meta) in retained {
|
|
||||||
let shape = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si);
|
|
||||||
obj.set_shape_id(shape);
|
|
||||||
let pos = obj.push_prop(value);
|
|
||||||
if let Some(meta) = meta {
|
|
||||||
if meta.is_accessor {
|
|
||||||
obj.set_accessor_meta(pos, meta.get, meta.set, meta.attributes);
|
|
||||||
} else {
|
|
||||||
obj.set_data_meta(pos, meta.attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
obj.bump_generation();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -236,3 +236,42 @@ fn test_property_is_enumerable_own_property() {
|
|||||||
let (_vm, result) = eval("var o = {x: 1}; o.propertyIsEnumerable('x')").unwrap();
|
let (_vm, result) = eval("var o = {x: 1}; o.propertyIsEnumerable('x')").unwrap();
|
||||||
assert!(result.is_bool() && result.as_bool());
|
assert!(result.is_bool() && result.as_bool());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eval_str(source: &str) -> String {
|
||||||
|
let (_vm, result) = eval(source).unwrap();
|
||||||
|
assert!(result.is_string(), "expected string result, got {result:?}");
|
||||||
|
unsafe { &*result.as_string_ptr() }.as_str().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_prop_removes_own_property() {
|
||||||
|
// 删除命名属性后 hasOwnProperty / keys / 读取一致反映删除。
|
||||||
|
assert_eq!(
|
||||||
|
eval_str("var o={a:1,b:2}; delete o.a; JSON.stringify([o.hasOwnProperty('a'), Object.keys(o).join(',')])"),
|
||||||
|
"[false,\"b\"]"
|
||||||
|
);
|
||||||
|
// 删除数组元素:length 不变,槽位变 hole。
|
||||||
|
assert_eq!(
|
||||||
|
eval_str("var a=[1,2,3]; delete a[1]; JSON.stringify([a.length, a.hasOwnProperty('1'), a[1] === undefined])"),
|
||||||
|
"[3,false,true]"
|
||||||
|
);
|
||||||
|
// 值为 undefined 的元素不是 hole,hasOwnProperty 仍为 true。
|
||||||
|
assert_eq!(
|
||||||
|
eval_str("var a=[undefined,1]; JSON.stringify([a.hasOwnProperty('0'), 0 in a])"),
|
||||||
|
"[true,true]"
|
||||||
|
);
|
||||||
|
// 删除数组命名属性:元素区保留。
|
||||||
|
assert_eq!(
|
||||||
|
eval_str("var a=[1,2]; a.custom=9; delete a.custom; JSON.stringify([a.hasOwnProperty('custom'), a[0]])"),
|
||||||
|
"[false,1]"
|
||||||
|
);
|
||||||
|
// 不可配置属性返回 false 且保留。
|
||||||
|
assert_eq!(
|
||||||
|
eval_str("var o={}; Object.defineProperty(o,'x',{value:1,configurable:false}); JSON.stringify([delete o.x, o.hasOwnProperty('x')])"),
|
||||||
|
"[false,true]"
|
||||||
|
);
|
||||||
|
// 原型链属性删除为 no-op true。
|
||||||
|
assert_eq!(eval_str("var o={}; JSON.stringify(delete o.hasOwnProperty)"), "true");
|
||||||
|
// 删除后重新赋值数组元素回到元素区。
|
||||||
|
assert_eq!(eval_str("var a=[1,2,3]; delete a[1]; a[1]=9; JSON.stringify([a[1], a.length])"), "[9,3]");
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,13 +160,15 @@ impl PropAttributes {
|
|||||||
/// 单个属性的元数据条目。
|
/// 单个属性的元数据条目。
|
||||||
///
|
///
|
||||||
/// 数据属性仅用 `attributes`;访问器属性额外携带 getter / setter
|
/// 数据属性仅用 `attributes`;访问器属性额外携带 getter / setter
|
||||||
/// 的 [`JsValue`] 与 `is_accessor = true` 标记。
|
/// 的 [`JsValue`] 与 `is_accessor = true` 标记。`hole` 标记数组元素被删除
|
||||||
|
/// 后保留的稀疏空洞(数组元素区存在性判定依据)。
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct PropMetaEntry {
|
pub struct PropMetaEntry {
|
||||||
pub attributes: PropAttributes,
|
pub attributes: PropAttributes,
|
||||||
pub get: JsValue,
|
pub get: JsValue,
|
||||||
pub set: JsValue,
|
pub set: JsValue,
|
||||||
pub is_accessor: bool,
|
pub is_accessor: bool,
|
||||||
|
pub hole: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PropMetaEntry {
|
impl PropMetaEntry {
|
||||||
@@ -177,6 +179,7 @@ impl PropMetaEntry {
|
|||||||
get: JsValue::undefined(),
|
get: JsValue::undefined(),
|
||||||
set: JsValue::undefined(),
|
set: JsValue::undefined(),
|
||||||
is_accessor: false,
|
is_accessor: false,
|
||||||
|
hole: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +190,25 @@ impl PropMetaEntry {
|
|||||||
get,
|
get,
|
||||||
set,
|
set,
|
||||||
is_accessor: true,
|
is_accessor: true,
|
||||||
|
hole: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 构造数组元素删除后的 hole 标记条目。
|
||||||
|
pub fn hole() -> Self {
|
||||||
|
Self {
|
||||||
|
attributes: PropAttributes::DEFAULT_DATA,
|
||||||
|
get: JsValue::undefined(),
|
||||||
|
set: JsValue::undefined(),
|
||||||
|
is_accessor: false,
|
||||||
|
hole: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 是否数组元素 hole 标记(删除后保留的稀疏空洞)。
|
||||||
|
pub fn is_hole(&self) -> bool {
|
||||||
|
self.hole
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 可转换为 dense 属性下标的值。
|
/// 可转换为 dense 属性下标的值。
|
||||||
@@ -772,6 +792,48 @@ impl JsObject {
|
|||||||
meta[pos] = Some(entry);
|
meta[pos] = Some(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 把数组元素槽标记为 hole(删除语义):值置 undefined 并写入 hole 标记,
|
||||||
|
/// `array_prop_count` 与元素区大小不变(length 保持不变)。
|
||||||
|
pub fn mark_hole_at(&mut self, position: impl PropIndex) {
|
||||||
|
let pos = position.to_u32() as usize;
|
||||||
|
if pos >= self.prop_vec_len() {
|
||||||
|
self.set_prop_count(pos + 1);
|
||||||
|
}
|
||||||
|
self.set_prop_at(pos, JsValue::undefined());
|
||||||
|
let meta = self.ensure_prop_meta();
|
||||||
|
while meta.len() <= pos {
|
||||||
|
meta.push(None);
|
||||||
|
}
|
||||||
|
meta[pos] = Some(PropMetaEntry::hole());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 若指定下标是 hole 标记则清除(元素被重新写入时恢复为存在)。
|
||||||
|
fn clear_hole_marker(&mut self, pos: usize) {
|
||||||
|
if let Some(meta) = self.prop_meta_vec_mut() {
|
||||||
|
if meta.get(pos).is_some_and(|entry| entry.is_some_and(|e| e.is_hole())) {
|
||||||
|
meta[pos] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清空全部属性与数组元素区,`array_prop_count` 归零。
|
||||||
|
///
|
||||||
|
/// 供 shape 链重建(如 delete 重排属性表)使用:清空后以 `push_prop` /
|
||||||
|
/// `set_prop_count` 按新形状重填。不清除形状 ID,调用方自行处理。
|
||||||
|
pub fn clear_props(&mut self) {
|
||||||
|
if !self.hash_props.is_null() {
|
||||||
|
// SAFETY: hash_props 在 ensure_hash_props/new_array 中由 Box<Vec<JsValue>> 创建。
|
||||||
|
let vec = unsafe { &mut *(self.hash_props as *mut Vec<JsValue>) };
|
||||||
|
vec.clear();
|
||||||
|
}
|
||||||
|
if !self.prop_meta.is_null() {
|
||||||
|
// SAFETY: prop_meta 在 ensure_prop_meta 中由 Box<Vec<Option<PropMetaEntry>>> 创建。
|
||||||
|
let meta = unsafe { &mut *(self.prop_meta as *mut Vec<Option<PropMetaEntry>>) };
|
||||||
|
meta.clear();
|
||||||
|
}
|
||||||
|
self.array_prop_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// 是否数组(header bit 29)。
|
/// 是否数组(header bit 29)。
|
||||||
pub fn is_array(&self) -> bool {
|
pub fn is_array(&self) -> bool {
|
||||||
(self.header >> 29) & 1 != 0
|
(self.header >> 29) & 1 != 0
|
||||||
@@ -915,6 +977,7 @@ impl JsObject {
|
|||||||
}
|
}
|
||||||
let vec = self.ensure_hash_props();
|
let vec = self.ensure_hash_props();
|
||||||
vec[pos] = val;
|
vec[pos] = val;
|
||||||
|
self.clear_hole_marker(pos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -963,6 +1026,7 @@ impl JsObject {
|
|||||||
vec.push(JsValue::undefined());
|
vec.push(JsValue::undefined());
|
||||||
}
|
}
|
||||||
vec[idx] = val;
|
vec[idx] = val;
|
||||||
|
self.clear_hole_marker(idx);
|
||||||
if let Some(meta) = self.prop_meta_vec_mut() {
|
if let Some(meta) = self.prop_meta_vec_mut() {
|
||||||
while meta.len() <= idx {
|
while meta.len() <= idx {
|
||||||
meta.push(None);
|
meta.push(None);
|
||||||
@@ -1367,4 +1431,3 @@ mod tests {
|
|||||||
assert!(obj.prop_meta_at(2).is_some());
|
assert!(obj.prop_meta_at(2).is_some());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,8 @@ impl Vm {
|
|||||||
let obj = unsafe { &*obj_ptr };
|
let obj = unsafe { &*obj_ptr };
|
||||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||||
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc);
|
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc);
|
||||||
if obj.has_prop_meta() { let val = self.ordinary_get_with_target(obj, prop_name_si, val, a as u8)?;
|
if obj.has_prop_meta() {
|
||||||
|
let val = self.ordinary_get_with_target(obj, prop_name_si, val, a as u8)?;
|
||||||
if self.accessor_frame_target_reg.take().is_none() {
|
if self.accessor_frame_target_reg.take().is_none() {
|
||||||
self.regs[a] = val;
|
self.regs[a] = val;
|
||||||
}
|
}
|
||||||
@@ -412,17 +413,13 @@ impl Vm {
|
|||||||
let key_val = self.immutables().get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
|
let key_val = self.immutables().get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
|
||||||
let prop_name_si = self.property_key_si(key_val);
|
let prop_name_si = self.property_key_si(key_val);
|
||||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
||||||
return Ok(true);
|
self.regs[rd] = JsValue::bool(true);
|
||||||
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
let obj = unsafe { &mut *obj_ptr };
|
||||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
// 统一走共享删除逻辑(与 Reflect.deleteProperty 一致);不可配置返回 false。
|
||||||
let configurable = obj.prop_meta_at(pos).map(|m| m.attributes.configurable()).unwrap_or(true);
|
let deleted = oxide_builtins::object::delete_own_property(self, obj, prop_name_si);
|
||||||
if !configurable {
|
self.regs[rd] = JsValue::bool(deleted);
|
||||||
return self.raise_type_error("cannot delete non-configurable property").map(|()| false);
|
|
||||||
}
|
|
||||||
obj.set_prop_shape(pos, JsValue::undefined());
|
|
||||||
}
|
|
||||||
self.regs[rd] = JsValue::bool(true);
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,18 +427,13 @@ impl Vm {
|
|||||||
vm_trace!("DELETE_PROP_DYNAMIC rd={}", rd);
|
vm_trace!("DELETE_PROP_DYNAMIC rd={}", rd);
|
||||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
||||||
return Ok(true);
|
self.regs[rd] = JsValue::bool(true);
|
||||||
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
let obj = unsafe { &mut *obj_ptr };
|
||||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
// 统一走共享删除逻辑(与 Reflect.deleteProperty 一致);不可配置返回 false。
|
||||||
let configurable = obj.prop_meta_at(pos).map(|m| m.attributes.configurable()).unwrap_or(true);
|
let deleted = oxide_builtins::object::delete_own_property(self, obj, prop_name_si);
|
||||||
if !configurable {
|
self.regs[rd] = JsValue::bool(deleted);
|
||||||
return self.raise_type_error("cannot delete non-configurable property").map(|()| false);
|
|
||||||
}
|
|
||||||
obj.set_prop_shape(pos, JsValue::undefined());
|
|
||||||
}
|
|
||||||
self.regs[rd] = JsValue::bool(true);
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -663,7 +663,8 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
if obj.is_array() {
|
if obj.is_array() {
|
||||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||||
if index < obj.prop_vec_len() as u32 {
|
// 数组元素区:hole(删除标记)视为不存在。
|
||||||
|
if index < obj.array_prop_count && !obj.prop_meta_at(index).is_some_and(|m| m.is_hole()) {
|
||||||
return Some(obj.get_prop_at(index));
|
return Some(obj.get_prop_at(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,7 +702,8 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
if obj.is_array() {
|
if obj.is_array() {
|
||||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||||
if index < obj.prop_vec_len() as u32 {
|
// 数组元素区:hole(删除标记)视为不存在。
|
||||||
|
if index < obj.array_prop_count && !obj.prop_meta_at(index).is_some_and(|m| m.is_hole()) {
|
||||||
return Some(index);
|
return Some(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1840,4 +1842,3 @@ mod tests {
|
|||||||
assert_eq!(vm.reg(255), JsValue::int(13));
|
assert_eq!(vm.reg(255), JsValue::int(13));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
// native 方法(非构造器)不可 new。
|
// native 方法(非构造器)不可 new。
|
||||||
if ctor_obj.native_fn().is_some() && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR {
|
if ctor_obj.native_fn().is_some() && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR {
|
||||||
return self
|
return self.raise_type_error("object is not a constructor").map(|_| true);
|
||||||
.raise_type_error("object is not a constructor")
|
|
||||||
.map(|_| true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext = self.bytecode[self.pc];
|
let ext = self.bytecode[self.pc];
|
||||||
@@ -321,10 +319,14 @@ impl Vm {
|
|||||||
|
|
||||||
// 数组把整型下标元素存在 prop_vec,而 prop_vec 不属于 shape 链。
|
// 数组把整型下标元素存在 prop_vec,而 prop_vec 不属于 shape 链。
|
||||||
// 单独枚举它们(ES:数组下标是可枚举字符串键,按升序排在其它自有键之前)。
|
// 单独枚举它们(ES:数组下标是可枚举字符串键,按升序排在其它自有键之前)。
|
||||||
|
// hole(删除标记)跳过——for-in 不枚举数组稀疏空洞。
|
||||||
if current.is_object() {
|
if current.is_object() {
|
||||||
let arr = unsafe { &*current.as_js_object_ptr() };
|
let arr = unsafe { &*current.as_js_object_ptr() };
|
||||||
if arr.is_array() {
|
if arr.is_array() {
|
||||||
for i in 0..arr.prop_vec_len() {
|
for i in 0..arr.array_prop_count {
|
||||||
|
if arr.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let is_enum = arr
|
let is_enum = arr
|
||||||
.prop_meta_at(i)
|
.prop_meta_at(i)
|
||||||
.map(|m| m.attributes.enumerable())
|
.map(|m| m.attributes.enumerable())
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
if obj.is_array() {
|
if obj.is_array() {
|
||||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||||
if index < obj.prop_vec_len() as u32 {
|
// 数组元素区:hole(删除标记)视为不存在,落到原型链。
|
||||||
|
if index < obj.array_prop_count && !obj.prop_meta_at(index).is_some_and(|m| m.is_hole()) {
|
||||||
// 数组元素为访问器属性(defineProperty getter)时须触发
|
// 数组元素为访问器属性(defineProperty getter)时须触发
|
||||||
// getter,而非直接读数据槽。
|
// getter,而非直接读数据槽。
|
||||||
if let Some(meta) = obj.prop_meta_at(index) {
|
if let Some(meta) = obj.prop_meta_at(index) {
|
||||||
@@ -309,6 +310,13 @@ impl Vm {
|
|||||||
|
|
||||||
pub(crate) fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
|
pub(crate) fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
|
||||||
vm_trace!("set_or_create_prop_value: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
|
vm_trace!("set_or_create_prop_value: shape_id={} prop_name_si={}", obj.shape_id(), prop_name_si);
|
||||||
|
// 数组下标键写入元素区(维护 array_prop_count),不进入 shape 链。
|
||||||
|
if obj.is_array() {
|
||||||
|
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||||
|
obj.set_prop_at(index, val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||||
obj.set_prop_shape(pos, val);
|
obj.set_prop_shape(pos, val);
|
||||||
} else {
|
} else {
|
||||||
@@ -328,7 +336,15 @@ impl Vm {
|
|||||||
if obj.is_array() {
|
if obj.is_array() {
|
||||||
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
if let Some(index) = self.array_index_from_property_key(prop_name_si) {
|
||||||
// 数组索引属性存元素区并维护 array_prop_count(元素数随索引增长)。
|
// 数组索引属性存元素区并维护 array_prop_count(元素数随索引增长)。
|
||||||
return self.define_array_index_element(obj, index, val, attributes, false, JsValue::undefined(), JsValue::undefined());
|
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) {
|
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||||
@@ -411,14 +427,8 @@ impl Vm {
|
|||||||
/// 数组索引属性(`"0"`~`"4294967294"`)的 define 路径:存入元素区并维护
|
/// 数组索引属性(`"0"`~`"4294967294"`)的 define 路径:存入元素区并维护
|
||||||
/// `array_prop_count`(length 随最高索引增长),meta 与元素槽对齐。
|
/// `array_prop_count`(length 随最高索引增长),meta 与元素槽对齐。
|
||||||
fn define_array_index_element(
|
fn define_array_index_element(
|
||||||
&mut self,
|
&mut self, obj: &mut JsObject, index: u32, val: JsValue, attributes: PropAttributes, is_accessor: bool,
|
||||||
obj: &mut JsObject,
|
get: JsValue, set: JsValue,
|
||||||
index: u32,
|
|
||||||
val: JsValue,
|
|
||||||
attributes: PropAttributes,
|
|
||||||
is_accessor: bool,
|
|
||||||
get: JsValue,
|
|
||||||
set: JsValue,
|
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let pos = index as usize;
|
let pos = index as usize;
|
||||||
if pos > oxide_types::object::MAX_DENSE_PROPS {
|
if pos > oxide_types::object::MAX_DENSE_PROPS {
|
||||||
|
|||||||
Reference in New Issue
Block a user