From 4a8894a560990a1a6a39759ebe5d73dc9744dfea Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 20 Sep 2026 20:45:15 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Object.getOwnPrope?= =?UTF-8?q?rtyDescriptors=20=E5=86=85=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/object.rs | 157 +++++++++++++----- crates/oxide_kernel/src/builtin/bind.rs | 1 + crates/oxide_kernel/src/builtin/methods.rs | 1 + crates/oxide_vm/src/bindings/bind_object.rs | 2 + .../oxide_vm/tests/builtins_object_tests.rs | 57 +++++++ 5 files changed, 172 insertions(+), 46 deletions(-) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index ebe14d43..e9cedd03 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -883,6 +883,72 @@ pub fn object_define_property(vm: &mut H, args: &[u8]) -> NativeResul NativeResult::Ok(obj_val) } +/// 取单个自身属性的描述符对象(gOPD / gOPDs 共享核):按 ToPropertyDescriptor +/// 形态构造描述符对象,proto = %Object.prototype%。 +/// +/// # 边界与前提 +/// - 键不在对象自身返回 `Ok(None)` +/// - 模块命名空间未初始化导出返回 `Err`(ReferenceError,原值传播) +/// +/// # 副作用 +/// - 在 epoch 分配一个描述符对象 +fn own_descriptor_of(vm: &mut H, obj: &JsObject, key_si: u32) -> Result, 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(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)`:返回自身属性的描述符对象; /// 不存在返回 undefined。 pub fn object_get_own_property_descriptor(vm: &mut H, args: &[u8]) -> NativeResult { @@ -899,59 +965,58 @@ pub fn object_get_own_property_descriptor(vm: &mut H, args: &[u8]) -> let obj_ptr = obj_val.as_js_object_ptr(); let key = vm.property_key_si(vm.reg(args[2])); let obj = unsafe { &*obj_ptr }; - - // 数组 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 == 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)); + match own_descriptor_of(vm, obj, key) { + Ok(Some(desc)) => NativeResult::Ok(desc), + Ok(None) => NativeResult::Ok(JsValue::undefined()), + Err(exc) => NativeResult::Err(exc), } +} - let Some(offset) = vm.get_own_property_slot(obj, key) else { - return NativeResult::Ok(JsValue::undefined()); +/// `Object.getOwnPropertyDescriptors(obj)`:返回全部自身属性描述符集合对象 +/// (键 = 自身属性名,值 = 对应描述符对象)。 +/// +/// # 步骤 +/// 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(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 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 obj = unsafe { &*obj_ptr }; - let sf_ptr = vm.kernel_core().perm_interner().as_ref() as *const PermInterner; - let sh_ptr = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge; + // 三段自身键序:字符串/整数键(整键升序在前)后接 Symbol 键插入序。 + 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 = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto))); - let sf = unsafe { &*sf_ptr }; - let sh = unsafe { &*sh_ptr }; + let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(desc_proto))); + let result_val = JsValue::from_js_object(result); - let d: &mut JsObject = unsafe { &mut *desc }; - 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())); + for (si, _pos) in keys { + // 描述符核:缺失键跳过;未初始化导出原值传播。 + let desc = match own_descriptor_of(vm, obj, si) { + Ok(Some(desc)) => desc, + Ok(None) => continue, + Err(exc) => return NativeResult::Err(exc), + }; + // CreateDataPropertyOrThrow:结果对象为 fresh 普通对象,属性恒全真。 + if let Err(err) = vm.define_data_property(unsafe { &mut *result }, si, desc, PropAttributes::DEFAULT_DATA) { + return NativeResult::Err(crate::error::create_type_error(vm, &err)); + } } - - NativeResult::Ok(JsValue::from_js_object(desc)) + NativeResult::Ok(result_val) } /// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经 diff --git a/crates/oxide_kernel/src/builtin/bind.rs b/crates/oxide_kernel/src/builtin/bind.rs index b8154485..e6c85583 100644 --- a/crates/oxide_kernel/src/builtin/bind.rs +++ b/crates/oxide_kernel/src/builtin/bind.rs @@ -28,6 +28,7 @@ impl BuiltinWorld { ("is", methods.is, 2), ("defineProperty", methods.define_property, 3), ("getOwnPropertyDescriptor", methods.get_own_property_descriptor, 2), + ("getOwnPropertyDescriptors", methods.get_own_property_descriptors, 1), ("freeze", methods.freeze, 1), ("seal", methods.seal, 1), ("preventExtensions", methods.prevent_extensions, 1), diff --git a/crates/oxide_kernel/src/builtin/methods.rs b/crates/oxide_kernel/src/builtin/methods.rs index b059737d..b71443c6 100644 --- a/crates/oxide_kernel/src/builtin/methods.rs +++ b/crates/oxide_kernel/src/builtin/methods.rs @@ -8,6 +8,7 @@ pub struct ObjectMethods { pub is: *const (), pub define_property: *const (), pub get_own_property_descriptor: *const (), + pub get_own_property_descriptors: *const (), pub freeze: *const (), pub seal: *const (), pub prevent_extensions: *const (), diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index 0891a5ba..c2cf51b0 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -17,6 +17,8 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut define_property: oxide_builtins::object::object_define_property:: as *const (), get_own_property_descriptor: oxide_builtins::object::object_get_own_property_descriptor:: as *const (), + get_own_property_descriptors: oxide_builtins::object::object_get_own_property_descriptors:: + as *const (), freeze: oxide_builtins::object::object_freeze:: as *const (), seal: oxide_builtins::object::object_seal:: as *const (), prevent_extensions: oxide_builtins::object::object_prevent_extensions:: as *const (), diff --git a/crates/oxide_vm/tests/builtins_object_tests.rs b/crates/oxide_vm/tests/builtins_object_tests.rs index 1b5cdcc8..2c1f751f 100644 --- a/crates/oxide_vm/tests/builtins_object_tests.rs +++ b/crates/oxide_vm/tests/builtins_object_tests.rs @@ -375,3 +375,60 @@ fn entries_values_with_interleaved_symbol_key() { assert_eq!(s0, "[[\"a\",1],[\"b\",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}"); +}