feat: 实现 Object.getOwnPropertyDescriptors 内建

This commit is contained in:
2026-09-20 20:45:15 +00:00
parent d750d4a2b5
commit 4a8894a560
5 changed files with 172 additions and 46 deletions
+111 -46
View File
@@ -883,6 +883,72 @@ pub fn object_define_property<H: VmHost>(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<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)`:返回自身属性的描述符对象;
/// 不存在返回 undefined。
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 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<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 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 语义取描述符字段:沿原型链判存在性,存在时经
+1
View File
@@ -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),
@@ -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 (),
@@ -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 (),
get_own_property_descriptor: oxide_builtins::object::object_get_own_property_descriptor::<crate::vm::Vm>
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 (),
seal: oxide_builtins::object::object_seal::<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!(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}");
}