feat: Object.create/defineProperties 完整描述符管线(复用 ToPropertyDescriptor)

This commit is contained in:
爱特每羊队
2026-08-08 20:59:47 +08:00
parent d5ce35f4ba
commit afffcff46e
+119 -95
View File
@@ -259,7 +259,8 @@ pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::from_js_object(arr)) NativeResult::Ok(JsValue::from_js_object(arr))
} }
/// `Object.create(proto, properties)`:以指定 prototype 创建新对象。 /// `Object.create(proto, properties)`:以指定 prototype 创建新对象,可选地按
/// 属性描述符集合定义自身属性。
pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 { if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.create: at least 1 argument required")); return NativeResult::Err(crate::error::create_type_error(vm, "Object.create: at least 1 argument required"));
@@ -267,6 +268,11 @@ pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let proto_val = vm.reg(args[1]); let proto_val = vm.reg(args[1]);
if proto_val.is_null() { if proto_val.is_null() {
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
if args.len() >= 3 && !vm.reg(args[2]).is_undefined() {
if let Err(msg) = define_all_from_properties(vm, obj, vm.reg(args[2])) {
return NativeResult::Err(crate::error::create_type_error(vm, &msg));
}
}
return NativeResult::Ok(JsValue::from_js_object(obj)); return NativeResult::Ok(JsValue::from_js_object(obj));
} }
if !proto_val.is_object() { if !proto_val.is_object() {
@@ -276,6 +282,11 @@ pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
)); ));
} }
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
if args.len() >= 3 && !vm.reg(args[2]).is_undefined() {
if let Err(msg) = define_all_from_properties(vm, obj, vm.reg(args[2])) {
return NativeResult::Err(crate::error::create_type_error(vm, &msg));
}
}
NativeResult::Ok(JsValue::from_js_object(obj)) NativeResult::Ok(JsValue::from_js_object(obj))
} }
@@ -333,34 +344,32 @@ pub fn object_is<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::bool(oxide_runtime_api::same_value(lhs, rhs))) NativeResult::Ok(JsValue::bool(oxide_runtime_api::same_value(lhs, rhs)))
} }
/// `Object.defineProperty(obj, key, descriptor)`:按 descriptor 定义/修改属性, /// 按 ToPropertyDescriptor 语义把描述符定义/修改到对象的自身属性上。
/// 支持数据与访问器描述符,兼容已有属性的默认回填。 ///
pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { /// 处理数据(value/writable)与访问器(get/set)两类描述符;描述符字段沿原型
if args.len() < 4 { /// 链解析(ordinary_get,触发 accessor getter);字段缺失时按已有属性回填,
return NativeResult::Err(crate::error::create_type_error( /// 新属性缺省字段为 false。
vm, ///
"Object.defineProperty: expected at least 3 arguments", /// # 步骤
)); /// 1. 解析 value/get/set/writable/enumerable/configurable 字段
} /// 2. 校验 data 与 accessor 字段互斥、getter/setter 可调用
let obj_val = vm.reg(args[1]); /// 3. 按访问器/数据/无字段三种形态调用对应 define 操作
if !obj_val.is_object() { ///
return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object")); /// # 边界与前提
} /// - `desc_val` 必须是对象;`key_si` 须已 intern
let obj_ptr = obj_val.as_js_object_ptr(); /// - 修改已有属性时缺省字段回填现有值;新属性缺省为 false
if obj_ptr.is_null() { ///
return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object")); /// # 副作用
} /// - 修改 obj 的 shape 链、属性表与 generation
let desc_val = vm.reg(args[3]); fn define_from_descriptor<H: VmHost>(
vm: &mut H,
obj_ptr: *mut JsObject,
key_si: u32,
desc_val: JsValue,
) -> Result<(), String> {
if !desc_val.is_object() { if !desc_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "Property description must be an object")); return Err("Property description must be an object".to_string());
} }
let desc_ptr = desc_val.as_js_object_ptr();
if desc_ptr.is_null() {
return NativeResult::Err(crate::error::create_type_error(vm, "Property description must be an object"));
}
// well-known symbol 等特殊键统一走 property_key_si(映射到 @@iterator 等别名),
// 保证与计算属性访问、Reflect.defineProperty 等读键路径一致。
let si = vm.property_key_si(vm.reg(args[2]));
let value_si = vm.kernel_core().perm_interner().intern("value").0; let value_si = vm.kernel_core().perm_interner().intern("value").0;
let get_si = vm.kernel_core().perm_interner().intern("get").0; let get_si = vm.kernel_core().perm_interner().intern("get").0;
@@ -376,7 +385,7 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
let existing_pos = { let existing_pos = {
let obj = unsafe { &*obj_ptr }; let obj = unsafe { &*obj_ptr };
vm.get_own_property_slot(obj, si) vm.get_own_property_slot(obj, key_si)
}; };
let existing_meta = existing_pos.and_then(|pos| { let existing_meta = existing_pos.and_then(|pos| {
let obj = unsafe { &*obj_ptr }; let obj = unsafe { &*obj_ptr };
@@ -394,10 +403,7 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
let has_data = value_field.is_some() || writable_field.is_some(); let has_data = value_field.is_some() || writable_field.is_some();
let has_accessor = get_field.is_some() || set_field.is_some(); let has_accessor = get_field.is_some() || set_field.is_some();
if has_data && has_accessor { if has_data && has_accessor {
return NativeResult::Err(crate::error::create_type_error( return Err("Invalid property descriptor: cannot mix data and accessor fields".to_string());
vm,
"Invalid property descriptor: cannot mix data and accessor fields",
));
} }
let existing_value = existing_pos.map_or(JsValue::undefined(), |pos| { let existing_value = existing_pos.map_or(JsValue::undefined(), |pos| {
@@ -420,16 +426,9 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
.unwrap_or(JsValue::undefined()) .unwrap_or(JsValue::undefined())
}); });
if (!get.is_undefined() && !is_callable(get)) || (!set.is_undefined() && !is_callable(set)) { if (!get.is_undefined() && !is_callable(get)) || (!set.is_undefined() && !is_callable(set)) {
return NativeResult::Err(crate::error::create_type_error( return Err("accessor descriptor get/set must be callable or undefined".to_string());
vm,
"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))
{
return NativeResult::Err(crate::error::create_type_error(vm, &e));
} }
vm.define_accessor_property(obj, key_si, get, set, PropAttributes::new(false, enumerable, configurable))?;
} else if has_data { } else if has_data {
let value = if existing_pos.is_some() { let value = if existing_pos.is_some() {
value_field.unwrap_or(existing_value) value_field.unwrap_or(existing_value)
@@ -443,21 +442,16 @@ 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)) vm.define_data_property(obj, key_si, value, PropAttributes::new(writable, enumerable, configurable))?;
{
return NativeResult::Err(crate::error::create_type_error(vm, &e));
}
} else { } else {
if existing_pos.is_none() { if existing_pos.is_none() {
if let Err(e) = vm.define_data_property( vm.define_data_property(
obj, obj,
si, key_si,
JsValue::undefined(), JsValue::undefined(),
PropAttributes::new(false, enumerable, configurable), PropAttributes::new(false, enumerable, configurable),
) { )?;
return NativeResult::Err(crate::error::create_type_error(vm, &e)); return Ok(());
}
return NativeResult::Ok(obj_val);
} }
let is_accessor = existing_meta.map(|m| m.is_accessor).unwrap_or(false); let is_accessor = existing_meta.map(|m| m.is_accessor).unwrap_or(false);
let writable = existing_meta.map(|m| m.attributes.writable()).unwrap_or(true); let writable = existing_meta.map(|m| m.attributes.writable()).unwrap_or(true);
@@ -465,13 +459,79 @@ pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
if is_accessor { if is_accessor {
let get = existing_meta.map(|m| m.get).unwrap_or(JsValue::undefined()); let get = existing_meta.map(|m| m.get).unwrap_or(JsValue::undefined());
let set = existing_meta.map(|m| m.set).unwrap_or(JsValue::undefined()); let set = existing_meta.map(|m| m.set).unwrap_or(JsValue::undefined());
if let Err(e) = vm.define_accessor_property(obj, si, get, set, attrs) { vm.define_accessor_property(obj, key_si, get, set, attrs)?;
return NativeResult::Err(crate::error::create_type_error(vm, &e)); } else {
} vm.define_data_property(obj, key_si, existing_value, attrs)?;
} else if let Err(e) = vm.define_data_property(obj, si, existing_value, attrs) {
return NativeResult::Err(crate::error::create_type_error(vm, &e));
} }
} }
Ok(())
}
/// 遍历对象自身的可枚举属性,把每个值当作描述符依次定义到目标对象上。
/// `Object.defineProperties` 与 `Object.create(proto, properties)` 共用。
///
/// # 边界与前提
/// - `props_val` 必须是对象,否则返回错误(null 触 ToObject 抛 TypeError)
/// - 仅处理可枚举自身属性;描述符值经 ordinary_get 读取(触发访问器 getter)
///
/// # 副作用
/// - 修改 target 的 shape 链、属性表与 generation
fn define_all_from_properties<H: VmHost>(
vm: &mut H,
target_ptr: *mut JsObject,
props_val: JsValue,
) -> Result<(), String> {
if !props_val.is_object() {
return Err("Property description must be an object".to_string());
}
let props_ptr = props_val.as_js_object_ptr();
if props_ptr.is_null() {
return Err("Property description must be an object".to_string());
}
let prop_keys: Vec<(u32, u32)> = {
let props = unsafe { &*props_ptr };
walk_own_keys(vm, props)
};
for (key_si, offset) in prop_keys {
let props = unsafe { &*props_ptr };
// 非可枚举自身属性跳过;无显式 meta 视为可枚举(普通字面量默认)。
if props
.prop_meta_at(offset)
.map(|m| !m.attributes.enumerable())
.unwrap_or(false)
{
continue;
}
let desc_val = vm.ordinary_get(props, key_si, props_val)?;
define_from_descriptor(vm, target_ptr, key_si, desc_val)?;
}
Ok(())
}
/// `Object.defineProperty(obj, key, descriptor)`:按 descriptor 定义/修改属性,
/// 支持数据与访问器描述符,兼容已有属性的默认回填。
pub fn object_define_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 4 {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.defineProperty: expected at least 3 arguments",
));
}
let obj_val = vm.reg(args[1]);
if !obj_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object"));
}
let obj_ptr = obj_val.as_js_object_ptr();
if obj_ptr.is_null() {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.defineProperty called on non-object"));
}
// well-known symbol 等特殊键统一走 property_key_si(映射到 @@iterator 等别名),
// 保证与计算属性访问、Reflect.defineProperty 等读键路径一致。
let si = vm.property_key_si(vm.reg(args[2]));
if let Err(msg) = define_from_descriptor(vm, obj_ptr, si, vm.reg(args[3])) {
return NativeResult::Err(crate::error::create_type_error(vm, &msg));
}
NativeResult::Ok(obj_val) NativeResult::Ok(obj_val)
} }
@@ -754,7 +814,7 @@ pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> Nati
NativeResult::Ok(JsValue::from_js_object(arr)) NativeResult::Ok(JsValue::from_js_object(arr))
} }
/// `Object.defineProperties(obj, descriptors)`:批量定义数据属性,返回目标对象。 /// `Object.defineProperties(obj, descriptors)`:批量按描述符定义属性,返回目标对象。
pub fn object_define_properties<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn object_define_properties<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 3 { if args.len() < 3 {
return NativeResult::Err(crate::error::create_type_error( return NativeResult::Err(crate::error::create_type_error(
@@ -767,44 +827,8 @@ pub fn object_define_properties<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
Ok(ptr) => ptr, Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err), Err(err) => return NativeResult::Err(err),
}; };
let desc_val = vm.reg(args[2]); if let Err(msg) = define_all_from_properties(vm, target_ptr, vm.reg(args[2])) {
if !desc_val.is_object() { return NativeResult::Err(crate::error::create_type_error(vm, &msg));
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.defineProperties: descriptor must be an object",
));
}
let desc_ptr = desc_val.as_js_object_ptr();
if desc_ptr.is_null() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.defineProperties: descriptor must be an object",
));
}
let prop_keys: Vec<(u32, u32)> = {
let desc = unsafe { &*desc_ptr };
walk_own_keys(vm, desc)
};
for (key_si, offset) in prop_keys {
let desc = unsafe { &*desc_ptr };
let prop_desc_val = desc.get_prop_at(offset);
if !prop_desc_val.is_object() {
continue;
}
let value_si = vm.kernel_core().perm_interner().intern("value").0;
let desc_obj_ptr = prop_desc_val.as_js_object_ptr();
if desc_obj_ptr.is_null() {
continue;
}
let desc_obj = unsafe { &*desc_obj_ptr };
let prop_val = if let Some(pos) = vm.kernel_core().shape_forge().lookup_position(desc_obj.shape_id(), value_si)
{
desc_obj.get_prop_at(pos)
} else {
JsValue::undefined()
};
let target = unsafe { &mut *target_ptr };
let _ = vm.define_data_property(target, key_si, prop_val, PropAttributes::DEFAULT_DATA);
} }
NativeResult::Ok(target_val) NativeResult::Ok(target_val)
} }