mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 实现 Object.getOwnPropertyDescriptors 内建
This commit is contained in:
@@ -883,6 +883,72 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
|
|||||||
NativeResult::Ok(obj_val)
|
NativeResult::Ok(obj_val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 取单个自身属性的描述符对象(gOPD / gOPDs 共享核):按 ToPropertyDescriptor
|
||||||
|
/// 形态构造描述符对象,proto = %Object.prototype%。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 键不在对象自身返回 `Ok(None)`
|
||||||
|
/// - 模块命名空间未初始化导出返回 `Err`(ReferenceError,原值传播)
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 在 epoch 分配一个描述符对象
|
||||||
|
fn own_descriptor_of<H: VmHost>(vm: &mut H, obj: &JsObject, key_si: u32) -> Result<Option<JsValue>, JsValue> {
|
||||||
|
// 数组 length 是虚拟属性(无 shape 槽,ordinary_get 直接返回逻辑长度):
|
||||||
|
// 描述符 {value: len, writable: !frozen && 非显式收窄, enumerable: false,
|
||||||
|
// configurable: false}。冻结数组与经 defineProperty 收窄的数组 writable=false。
|
||||||
|
let length_si = vm.kernel_core().perm_interner().intern("length").0;
|
||||||
|
if obj.is_array() && key_si == length_si {
|
||||||
|
let desc = alloc_desc_object(vm);
|
||||||
|
let sh_ptr = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge;
|
||||||
|
let sf_ptr = vm.kernel_core().perm_interner().as_ref() as *const PermInterner;
|
||||||
|
let d: &mut JsObject = unsafe { &mut *desc };
|
||||||
|
let sh = unsafe { &*sh_ptr };
|
||||||
|
let sf = unsafe { &*sf_ptr };
|
||||||
|
push_desc_prop(d, sh, sf.intern("value").0, obj.logical_len_value());
|
||||||
|
push_desc_prop(d, sh, sf.intern("writable").0, JsValue::bool(obj.is_length_writable()));
|
||||||
|
push_desc_prop(d, sh, sf.intern("enumerable").0, JsValue::bool(false));
|
||||||
|
push_desc_prop(d, sh, sf.intern("configurable").0, JsValue::bool(false));
|
||||||
|
return Ok(Some(JsValue::from_js_object(desc)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(offset) = vm.get_own_property_slot(obj, key_si) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
// 模块命名空间字符串导出:`? [[Get]]` 读条目活值,未初始化抛 ReferenceError。
|
||||||
|
let found_value = match namespace_export_get(obj, key_si) {
|
||||||
|
Ok(Some(value)) => value,
|
||||||
|
Ok(None) => obj.get_prop_at(offset),
|
||||||
|
Err(msg) => return Err(crate::error::create_reference_error(vm, msg)),
|
||||||
|
};
|
||||||
|
let found_meta = obj.prop_meta_at(offset);
|
||||||
|
|
||||||
|
let desc = alloc_desc_object(vm);
|
||||||
|
let sh_ptr = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge;
|
||||||
|
let sf_ptr = vm.kernel_core().perm_interner().as_ref() as *const PermInterner;
|
||||||
|
let d: &mut JsObject = unsafe { &mut *desc };
|
||||||
|
let sh = unsafe { &*sh_ptr };
|
||||||
|
let sf = unsafe { &*sf_ptr };
|
||||||
|
let meta = found_meta.unwrap_or_else(|| oxide_types::object::PropMetaEntry::data(PropAttributes::DEFAULT_DATA));
|
||||||
|
if meta.is_accessor {
|
||||||
|
push_desc_prop(d, sh, sf.intern("get").0, meta.get);
|
||||||
|
push_desc_prop(d, sh, sf.intern("set").0, meta.set);
|
||||||
|
push_desc_prop(d, sh, sf.intern("enumerable").0, JsValue::bool(meta.attributes.enumerable()));
|
||||||
|
push_desc_prop(d, sh, sf.intern("configurable").0, JsValue::bool(meta.attributes.configurable()));
|
||||||
|
} else {
|
||||||
|
push_desc_prop(d, sh, sf.intern("value").0, found_value);
|
||||||
|
push_desc_prop(d, sh, sf.intern("writable").0, JsValue::bool(meta.attributes.writable()));
|
||||||
|
push_desc_prop(d, sh, sf.intern("enumerable").0, JsValue::bool(meta.attributes.enumerable()));
|
||||||
|
push_desc_prop(d, sh, sf.intern("configurable").0, JsValue::bool(meta.attributes.configurable()));
|
||||||
|
}
|
||||||
|
Ok(Some(JsValue::from_js_object(desc)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 分配空描述符对象(proto = %Object.prototype%)。
|
||||||
|
fn alloc_desc_object<H: VmHost>(vm: &mut H) -> *mut JsObject {
|
||||||
|
let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||||
|
vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto)))
|
||||||
|
}
|
||||||
|
|
||||||
/// `Object.getOwnPropertyDescriptor(obj, key)`:返回自身属性的描述符对象;
|
/// `Object.getOwnPropertyDescriptor(obj, key)`:返回自身属性的描述符对象;
|
||||||
/// 不存在返回 undefined。
|
/// 不存在返回 undefined。
|
||||||
pub fn object_get_own_property_descriptor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn object_get_own_property_descriptor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
@@ -899,59 +965,58 @@ pub fn object_get_own_property_descriptor<H: VmHost>(vm: &mut H, args: &[u8]) ->
|
|||||||
let obj_ptr = obj_val.as_js_object_ptr();
|
let obj_ptr = obj_val.as_js_object_ptr();
|
||||||
let key = vm.property_key_si(vm.reg(args[2]));
|
let key = vm.property_key_si(vm.reg(args[2]));
|
||||||
let obj = unsafe { &*obj_ptr };
|
let obj = unsafe { &*obj_ptr };
|
||||||
|
match own_descriptor_of(vm, obj, key) {
|
||||||
// 数组 length 是虚拟属性(无 shape 槽,ordinary_get 直接返回逻辑长度):
|
Ok(Some(desc)) => NativeResult::Ok(desc),
|
||||||
// 描述符 {value: len, writable: !frozen && 非显式收窄, enumerable: false,
|
Ok(None) => NativeResult::Ok(JsValue::undefined()),
|
||||||
// configurable: false}。冻结数组与经 defineProperty 收窄的数组 writable=false。
|
Err(exc) => NativeResult::Err(exc),
|
||||||
let length_si = vm.kernel_core().perm_interner().intern("length").0;
|
|
||||||
if obj.is_array() && key == length_si {
|
|
||||||
let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
|
||||||
let desc = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto)));
|
|
||||||
let sh = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge;
|
|
||||||
let sf = vm.kernel_core().perm_interner().as_ref() as *const PermInterner;
|
|
||||||
let d: &mut JsObject = unsafe { &mut *desc };
|
|
||||||
let sh = unsafe { &*sh };
|
|
||||||
let sf = unsafe { &*sf };
|
|
||||||
push_desc_prop(d, sh, sf.intern("value").0, obj.logical_len_value());
|
|
||||||
push_desc_prop(d, sh, sf.intern("writable").0, JsValue::bool(obj.is_length_writable()));
|
|
||||||
push_desc_prop(d, sh, sf.intern("enumerable").0, JsValue::bool(false));
|
|
||||||
push_desc_prop(d, sh, sf.intern("configurable").0, JsValue::bool(false));
|
|
||||||
return NativeResult::Ok(JsValue::from_js_object(desc));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let Some(offset) = vm.get_own_property_slot(obj, key) else {
|
/// `Object.getOwnPropertyDescriptors(obj)`:返回全部自身属性描述符集合对象
|
||||||
return NativeResult::Ok(JsValue::undefined());
|
/// (键 = 自身属性名,值 = 对应描述符对象)。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. ToObject 装箱接收者(null/undefined 抛 TypeError)
|
||||||
|
/// 2. 按规范三段序枚举自身键:整数索引数值升序 + 非整数串键插入序
|
||||||
|
/// (walk_own_keys),Symbol 键插入序(walk_own_symbol_keys)随后
|
||||||
|
/// 3. 逐键取描述符(缺失键跳过),以数据属性定义到结果对象
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 结果对象 proto = %Object.prototype%,各键属性恒 writable/enumerable/
|
||||||
|
/// configurable 全真(fresh 普通对象的 CreateDataPropertyOrThrow 形态)
|
||||||
|
/// - 描述符取值与 gOPD 同核;模块命名空间未初始化导出抛 ReferenceError 原值
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 分配结果对象与每个描述符对象
|
||||||
|
pub fn object_get_own_property_descriptors<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let obj_ptr = match require_obj_arg(vm, args, "getOwnPropertyDescriptors") {
|
||||||
|
Ok(ptr) => ptr,
|
||||||
|
Err(err) => return NativeResult::Err(err),
|
||||||
};
|
};
|
||||||
// 模块命名空间字符串导出:`? [[Get]]` 读条目活值,未初始化抛 ReferenceError。
|
let obj = unsafe { &*obj_ptr };
|
||||||
let found_value = match namespace_export_get(obj, key) {
|
|
||||||
Ok(Some(value)) => value,
|
|
||||||
Ok(None) => obj.get_prop_at(offset),
|
|
||||||
Err(msg) => return NativeResult::Err(crate::error::create_reference_error(vm, msg)),
|
|
||||||
};
|
|
||||||
let found_meta = obj.prop_meta_at(offset);
|
|
||||||
|
|
||||||
let sf_ptr = vm.kernel_core().perm_interner().as_ref() as *const PermInterner;
|
// 三段自身键序:字符串/整数键(整键升序在前)后接 Symbol 键插入序。
|
||||||
let sh_ptr = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge;
|
let mut keys = walk_own_keys(vm, obj);
|
||||||
|
keys.extend(walk_own_symbol_keys(vm, obj));
|
||||||
|
|
||||||
|
// 结果对象:proto = %Object.prototype%。
|
||||||
let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||||
let desc = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto)));
|
let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto)));
|
||||||
let sf = unsafe { &*sf_ptr };
|
let result_val = JsValue::from_js_object(result);
|
||||||
let sh = unsafe { &*sh_ptr };
|
|
||||||
|
|
||||||
let d: &mut JsObject = unsafe { &mut *desc };
|
for (si, _pos) in keys {
|
||||||
let meta = found_meta.unwrap_or_else(|| oxide_types::object::PropMetaEntry::data(PropAttributes::DEFAULT_DATA));
|
// 描述符核:缺失键跳过;未初始化导出原值传播。
|
||||||
if meta.is_accessor {
|
let desc = match own_descriptor_of(vm, obj, si) {
|
||||||
push_desc_prop(d, sh, sf.intern("get").0, meta.get);
|
Ok(Some(desc)) => desc,
|
||||||
push_desc_prop(d, sh, sf.intern("set").0, meta.set);
|
Ok(None) => continue,
|
||||||
push_desc_prop(d, sh, sf.intern("enumerable").0, JsValue::bool(meta.attributes.enumerable()));
|
Err(exc) => return NativeResult::Err(exc),
|
||||||
push_desc_prop(d, sh, sf.intern("configurable").0, JsValue::bool(meta.attributes.configurable()));
|
};
|
||||||
} else {
|
// CreateDataPropertyOrThrow:结果对象为 fresh 普通对象,属性恒全真。
|
||||||
push_desc_prop(d, sh, sf.intern("value").0, found_value);
|
if let Err(err) = vm.define_data_property(unsafe { &mut *result }, si, desc, PropAttributes::DEFAULT_DATA) {
|
||||||
push_desc_prop(d, sh, sf.intern("writable").0, JsValue::bool(meta.attributes.writable()));
|
return NativeResult::Err(crate::error::create_type_error(vm, &err));
|
||||||
push_desc_prop(d, sh, sf.intern("enumerable").0, JsValue::bool(meta.attributes.enumerable()));
|
}
|
||||||
push_desc_prop(d, sh, sf.intern("configurable").0, JsValue::bool(meta.attributes.configurable()));
|
|
||||||
}
|
}
|
||||||
|
NativeResult::Ok(result_val)
|
||||||
NativeResult::Ok(JsValue::from_js_object(desc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经
|
/// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ impl BuiltinWorld {
|
|||||||
("is", methods.is, 2),
|
("is", methods.is, 2),
|
||||||
("defineProperty", methods.define_property, 3),
|
("defineProperty", methods.define_property, 3),
|
||||||
("getOwnPropertyDescriptor", methods.get_own_property_descriptor, 2),
|
("getOwnPropertyDescriptor", methods.get_own_property_descriptor, 2),
|
||||||
|
("getOwnPropertyDescriptors", methods.get_own_property_descriptors, 1),
|
||||||
("freeze", methods.freeze, 1),
|
("freeze", methods.freeze, 1),
|
||||||
("seal", methods.seal, 1),
|
("seal", methods.seal, 1),
|
||||||
("preventExtensions", methods.prevent_extensions, 1),
|
("preventExtensions", methods.prevent_extensions, 1),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub struct ObjectMethods {
|
|||||||
pub is: *const (),
|
pub is: *const (),
|
||||||
pub define_property: *const (),
|
pub define_property: *const (),
|
||||||
pub get_own_property_descriptor: *const (),
|
pub get_own_property_descriptor: *const (),
|
||||||
|
pub get_own_property_descriptors: *const (),
|
||||||
pub freeze: *const (),
|
pub freeze: *const (),
|
||||||
pub seal: *const (),
|
pub seal: *const (),
|
||||||
pub prevent_extensions: *const (),
|
pub prevent_extensions: *const (),
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
|||||||
define_property: oxide_builtins::object::object_define_property::<crate::vm::Vm> as *const (),
|
define_property: oxide_builtins::object::object_define_property::<crate::vm::Vm> as *const (),
|
||||||
get_own_property_descriptor: oxide_builtins::object::object_get_own_property_descriptor::<crate::vm::Vm>
|
get_own_property_descriptor: oxide_builtins::object::object_get_own_property_descriptor::<crate::vm::Vm>
|
||||||
as *const (),
|
as *const (),
|
||||||
|
get_own_property_descriptors: oxide_builtins::object::object_get_own_property_descriptors::<crate::vm::Vm>
|
||||||
|
as *const (),
|
||||||
freeze: oxide_builtins::object::object_freeze::<crate::vm::Vm> as *const (),
|
freeze: oxide_builtins::object::object_freeze::<crate::vm::Vm> as *const (),
|
||||||
seal: oxide_builtins::object::object_seal::<crate::vm::Vm> as *const (),
|
seal: oxide_builtins::object::object_seal::<crate::vm::Vm> as *const (),
|
||||||
prevent_extensions: oxide_builtins::object::object_prevent_extensions::<crate::vm::Vm> as *const (),
|
prevent_extensions: oxide_builtins::object::object_prevent_extensions::<crate::vm::Vm> as *const (),
|
||||||
|
|||||||
@@ -375,3 +375,60 @@ fn entries_values_with_interleaved_symbol_key() {
|
|||||||
assert_eq!(s0, "[[\"a\",1],[\"b\",3]]");
|
assert_eq!(s0, "[[\"a\",1],[\"b\",3]]");
|
||||||
assert_eq!(s1, "[1,3]");
|
assert_eq!(s1, "[1,3]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- Object.getOwnPropertyDescriptors --
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_descriptors_empty() {
|
||||||
|
// 结果对象 proto 为 %Object.prototype%,空对象无自身键。
|
||||||
|
let (_vm, result) = eval(
|
||||||
|
"(function(){ var d = Object.getOwnPropertyDescriptors({}); \
|
||||||
|
return Object.getPrototypeOf(d) === Object.prototype && Object.keys(d).length === 0 })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(result.is_bool() && result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_descriptors_data_and_accessor() {
|
||||||
|
// 数据描述符四字段齐全;访问器描述符有 get 无 value。
|
||||||
|
let (_vm, result) = eval(
|
||||||
|
"var o = {a: 1}; Object.defineProperty(o, 'g', {get: function(){ return 2 }, configurable: true}); \
|
||||||
|
var d = Object.getOwnPropertyDescriptors(o); \
|
||||||
|
d.a.value === 1 && d.a.writable === true && d.a.enumerable === true && d.a.configurable === true \
|
||||||
|
&& d.g.get !== undefined && d.g.value === undefined",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(result.is_bool() && result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_descriptors_integer_keys_ascending() {
|
||||||
|
// 整数索引键数值升序在前(10 排在 2 后),非整数串键按插入序随后,Symbol 键末段。
|
||||||
|
let (_vm, result) = eval(
|
||||||
|
"var o = {10: 'a', 2: 'b', x: 'c'}; var s = Symbol(); o[s] = 1; \
|
||||||
|
var d = Object.getOwnPropertyDescriptors(o); \
|
||||||
|
Object.getOwnPropertyNames(d).join(',') + '|' + d[s].value",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let s = unsafe { &*result.as_string_ptr() }.to_owned_string();
|
||||||
|
assert_eq!(s, "2,10,x|1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_descriptors_primitive_receiver() {
|
||||||
|
// 原始值接收者经 ToObject 装箱,装箱 number 无自身属性。
|
||||||
|
let (_vm, result) =
|
||||||
|
eval("(function(){ var d = Object.getOwnPropertyDescriptors(42); return Object.keys(d).length === 0 })()")
|
||||||
|
.unwrap();
|
||||||
|
assert!(result.is_bool() && result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_get_own_property_descriptors_null_receiver_throws() {
|
||||||
|
let err = match eval("Object.getOwnPropertyDescriptors(null)") {
|
||||||
|
Ok(_) => panic!("null receiver should throw TypeError"),
|
||||||
|
Err(err) => err,
|
||||||
|
};
|
||||||
|
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user