Files
OxideJS-New/crates/oxide_builtins/src/object.rs
T

1638 lines
69 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
use oxide_kernel::string_forge::PermInterner;
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::private_key::{
int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, make_well_known_symbol_key,
symbol_index_from_key, well_known_symbol_id_from_key,
};
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, VmHost};
use crate::builtins_debug;
/// 判断字符串是否为规范整数索引:非空、无前导零、全数字且值 < 2^32-1。
fn is_integer_index(key: &str) -> bool {
if key.is_empty() || key.len() > 1 && key.as_bytes()[0] == b'0' {
return false;
}
key.bytes().all(|b| b.is_ascii_digit()) && key.parse::<u64>().unwrap_or(u64::MAX) < (1u64 << 32) - 1
}
/// 收集对象全部自身属性(数组元素区 + shape 链),按规范顺序排列:整数索引在前
/// 升序,其余保持插入序。si 指 interned 字符串键编号(PermInterner 分配的 u32 id)。
/// 返回 `(属性键 si, 绝对存储索引)`:数组对象元素区索引即绝对下标,命名属性 =
/// `array_prop_count + shape 槽位`;普通对象即 shape 槽位。
pub fn walk_own_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
let mut keys: Vec<(u32, u32)> = Vec::new();
// 数组元素区:整数下标是可枚举自身属性(hole 视为不存在),排在命名属性之前。
if obj.is_array() {
for i in 0..obj.array_prop_count {
if obj.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
continue;
}
keys.push((make_int_key(i), i));
}
}
let shape_id = obj.shape_id();
let mut pos: u32 = 0;
let mut shape_ids = Vec::new();
let mut cursor = Some(shape_id);
while let Some(id) = cursor {
if id == EMPTY_SHAPE_ID {
break;
}
if let Some(shape) = vm.kernel_core().shape_forge().get_shape(id) {
cursor = shape.parent;
// Symbol 键/私有名键非字符串属性名,排除在字符串枚举之外(但仍占 shape
// 槽位,pos 计数须含它们才能与物理存储对齐)。
if shape.property_name != u32::MAX {
shape_ids.push(id);
}
} else {
break;
}
}
for id in shape_ids.iter().rev() {
if let Some(shape) = vm.kernel_core().shape_forge().get_shape(*id) {
if shape.property_name != 0
&& !is_symbol_key(shape.property_name)
&& !is_private_name_key(shape.property_name)
{
// 绝对存储索引:数组命名属性位于元素区之后。
let store = if obj.is_array() { obj.array_prop_count + pos } else { pos };
keys.push((shape.property_name, store));
}
}
pos += 1;
}
keys.sort_by(|(a_si, _), (b_si, _)| {
// 整数键(含 INT 区间键)以数值升序排在普通字符串键之前。
let a_idx = int_or_string_index(vm, *a_si);
let b_idx = int_or_string_index(vm, *b_si);
match (a_idx, b_idx) {
(Some(ai), Some(bi)) => ai.cmp(&bi),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
});
keys
}
/// 取键 si(interned 字符串键编号)对应的数组下标值:整数键直接反解,字符串键查
/// interner 后判是否规范数字串。
fn int_or_string_index<H: VmHost>(vm: &H, si: u32) -> Option<u32> {
if is_int_key(si) {
return Some(int_key_value(si));
}
let key = vm.kernel_core().perm_interner().lookup(si)?;
is_integer_index(key).then(|| key.parse::<u32>().unwrap())
}
/// 键 si 构造为 JS 可见字符串值(Object.keys / ownKeys / entries 族):
/// 整数键反解数字串,字符串键经 `decode_key` 还原单元序列(孤立 surrogate
/// 构造为 FlatU16;良形键的输出为良形 UTF-8 字符串,与直接 `to_string` 结果一致)。
pub fn key_si_to_js_value<H: VmHost>(vm: &mut H, si: u32) -> JsValue {
if is_int_key(si) {
return vm.new_string(&int_key_value(si).to_string());
}
let units = vm
.kernel_core()
.perm_interner()
.lookup(si)
.map(oxide_kernel::string_forge::decode_key)
.unwrap_or_default();
vm.new_string_units_owned(units)
}
/// 收集对象自身全部 Symbol 键(shape 链),按键序排列(根→叶,即插入序)。
/// 与 [`walk_own_keys`] 互补:只返回 Symbol 键。返回 `(属性键 si, 绝对存储索引)`:
/// 槽位计数须含全部非空节点(含字符串键)并对数组加元素区偏移,与物理存储及
/// [`walk_own_keys`] 的口径一致,消费方(delete 重建)才能按槽位取回正确值。
fn walk_own_symbol_keys<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> {
let mut keys: Vec<(u32, u32)> = Vec::new();
let shape_id = obj.shape_id();
let mut shape_ids = Vec::new();
let mut cursor = Some(shape_id);
while let Some(id) = cursor {
if id == EMPTY_SHAPE_ID {
break;
}
if let Some(shape) = vm.kernel_core().shape_forge().get_shape(id) {
cursor = shape.parent;
if shape.property_name != u32::MAX {
shape_ids.push(id);
}
} else {
break;
}
}
for (pos, id) in (0_u32..).zip(shape_ids.iter().rev()) {
if let Some(shape) = vm.kernel_core().shape_forge().get_shape(*id) {
// Symbol 键节点按绝对槽位回传(数组命名属性位于元素区之后)。
let store = if obj.is_array() { obj.array_prop_count + pos } else { pos };
if shape.property_name != 0 && is_symbol_key(shape.property_name) {
keys.push((shape.property_name, store));
}
}
}
keys
}
/// 把 Symbol 键反解为对应的 Symbol 值:well-known 键还原为内置 symbol 对象,
/// 用户 symbol 键还原为 `JsValue::symbol` 值。
fn decode_symbol_key<H: VmHost>(vm: &H, key: u32) -> JsValue {
if let Some(id) = well_known_symbol_id_from_key(key) {
let world = vm.session().builtin_world();
let ptr = match id {
0 => world.sym_iterator.as_ptr(),
1 => world.sym_match.as_ptr(),
2 => world.sym_replace.as_ptr(),
3 => world.sym_search.as_ptr(),
4 => world.sym_split.as_ptr(),
5 => world.sym_to_primitive.as_ptr(),
6 => world.sym_has_instance.as_ptr(),
7 => world.sym_match_all.as_ptr(),
8 => world.sym_async_iterator.as_ptr(),
10 => world.sym_species.as_ptr(),
11 => world.sym_async_dispose.as_ptr(),
12 => world.sym_dispose.as_ptr(),
_ => world.sym_to_string_tag.as_ptr(),
};
return JsValue::from_js_object(ptr as *mut JsObject);
}
JsValue::symbol(symbol_index_from_key(key))
}
/// 收集对象自身全部 Symbol 键并按插入序物化为 Symbol 值。
///
/// 供 `Object.getOwnPropertySymbols` 与 `Reflect.ownKeys` 的 Symbol 段共用;
/// 与 [`walk_own_keys`] 互补,后者只返回字符串键与整数键。
///
/// # 边界与前提
/// - 无自身 Symbol 键时返回空向量;数组元素区不含 Symbol 键。
///
/// # 注意事项
/// - 只读 shape 链,不分配对象;well-known symbol 值指向 session 内建 symbol 对象。
pub fn own_symbol_key_values<H: VmHost>(vm: &H, obj: &JsObject) -> Vec<JsValue> {
walk_own_symbol_keys(vm, obj)
.iter()
.map(|(key, _)| decode_symbol_key(vm, *key))
.collect()
}
/// 按规范 [[OwnPropertyKeys]] 重排命名空间导出:字符串键按 UTF-16 单元序升序,
/// Symbol 键(`@@toStringTag`)保持在其后。
///
/// 命名空间导出在模块 body 执行期按语句序写入,而规范要求导出键有序。重建 shape
/// 链使插入序即枚举序,字符串枚举消费端(getOwnPropertyNames / Reflect.ownKeys /
/// for-in / Object.keys / JSON)无需各自特判。
///
/// # 边界与前提
/// - 仅对模块命名空间对象调用;无自身字符串键时只保留 Symbol 键。
///
/// # 副作用
/// - 重写 obj 的 shape 链与属性表并 bump generation;对象 identity、属性值与
/// 描述符均不变。
pub fn sort_namespace_exports<H: VmHost>(vm: &mut H, obj: &mut JsObject) {
// 收集字符串导出与其值/描述符;Symbol 键不参与字符串排序,收集后原序追加。
let mut entries: Vec<(u32, JsValue, Option<PropMetaEntry>)> = walk_own_keys(vm, obj)
.into_iter()
.map(|(si, pos)| (si, obj.get_prop_at(pos), obj.prop_meta_at(pos)))
.collect();
let symbols: Vec<(u32, JsValue, Option<PropMetaEntry>)> = walk_own_symbol_keys(vm, obj)
.into_iter()
.map(|(si, pos)| (si, obj.get_prop_at(pos), obj.prop_meta_at(pos)))
.collect();
// 排序口径 = UTF-16 单元序(BMP 外字符的单元序与码点序不同)。
entries.sort_by_cached_key(|(si, _, _)| namespace_key_units(vm, *si));
// 重建 shape 链与属性表:插入序 = 枚举序。
obj.set_shape_id(EMPTY_SHAPE_ID);
obj.clear_props();
for (si, value, meta) in entries.into_iter().chain(symbols) {
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();
}
/// 命名空间导出键的排序单元序列:字符串键取 UTF-16 单元,整数键取十进制文本。
fn namespace_key_units<H: VmHost>(vm: &H, si: u32) -> Vec<u16> {
if is_int_key(si) {
return int_key_value(si).to_string().encode_utf16().collect();
}
vm.kernel_core()
.perm_interner()
.lookup(si)
.map(|key| key.encode_utf16().collect())
.unwrap_or_default()
}
/// `Object.getOwnPropertySymbols(obj)`:返回全部自身 Symbol 键数组。
pub fn object_get_own_property_symbols<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = match require_obj_arg(vm, args, "getOwnPropertySymbols") {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let symbols: Vec<JsValue> = {
let obj = unsafe { &*obj_ptr };
own_symbol_key_values(vm, obj)
};
let n = symbols.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, v) in symbols.iter().enumerate() {
unsafe {
(*arr).set_prop_at(i, *v);
}
}
unsafe {
(*arr).set_prop_count(n);
}
NativeResult::Ok(JsValue::from_js_object(arr))
}
/// 字符串键是否为数组下标("0"~"4294967294",无前导零)。
fn array_index_of<H: VmHost>(vm: &H, key_si: u32) -> Option<u32> {
if is_int_key(key_si) {
return Some(int_key_value(key_si));
}
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 运算符(不可配置在严格模式须抛 TypeError)与
/// `Reflect.deleteProperty`(恒投影为布尔、不抛错)两个消费者。
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DeleteOutcome {
/// 属性不在对象自身(含原型链属性):删除按规范成功。
Missing,
/// 属性存在且已删除。
Deleted,
/// 属性存在但不可配置:删除失败。
NonConfigurable,
}
/// 删除对象自身属性并返回三态结果(字节码 delete 与 Reflect.deleteProperty 共用)。
///
/// 数组下标键在元素区(shape 链外),标记为 hole(值 undefined + hole meta),
/// length 不变;数组 length 虚拟属性与命名属性经 shape 链处理。仅对象自身属性
/// 参与删除判定,原型链属性不影响结果。
///
/// # 步骤
/// 1. 数组 length 虚拟属性:不可配置,返回 `NonConfigurable`
/// 2. 数组下标元素:检查 configurable,`mark_hole_at` 标记为 hole
/// 3. 命名属性:walk_own_keys 定位槽位,不可配置返回 `NonConfigurable`
/// 4. 数组先保存元素区(值 + meta),重建命名属性后恢复元素区
///
/// # 边界与前提
/// - 键不在对象自身(含原型链属性)返回 `Missing`
/// - 非 configurable 属性返回 `NonConfigurable`
/// - `key_si` 须已 intern
///
/// # 副作用
/// - 修改 obj 的 shape_id、属性表与 generation;数组元素区内容不变
///
/// # 注意事项
/// - 数组元素存在性以 `prop_meta_at` 的 hole 标记判定,删除后重新写入元素
/// 会自动清除 hole 标记恢复存在
pub fn delete_own_property_outcome<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> DeleteOutcome {
// 数组下标元素在元素区,不参与 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 DeleteOutcome::Missing;
}
if meta.is_some_and(|m| !m.attributes.configurable()) {
return DeleteOutcome::NonConfigurable;
}
obj.mark_hole_at(index);
obj.bump_generation();
return DeleteOutcome::Deleted;
}
return DeleteOutcome::Missing;
}
// 非下标键:length 是虚拟属性(无 shape 槽、不在元素区),但描述符声明
// configurable:false,删除恒失败。
if key_si == vm.kernel_core().perm_interner().intern("length").0 {
return DeleteOutcome::NonConfigurable;
}
}
// walk_own_keys 只返回字符串键;Symbol 键(well-known/用户)需另行定位。
let keys = walk_own_keys(vm, obj);
let mut all_keys = keys;
if is_symbol_key(key_si) {
all_keys.extend(walk_own_symbol_keys(vm, obj));
}
let Some(delete_pos) = all_keys.iter().find(|(si, _)| *si == key_si).map(|(_, pos)| *pos) else {
// 属性缺失:delete 按规范返 true,镜像槽同步 undefined 保持
// "槽 = A 侧原始存储"不变式(与入口预载缺位语义幂等)。
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
return DeleteOutcome::Missing;
};
// walk_own_keys 已返回绝对存储索引(数组含元素区偏移),直接使用。
if obj
.prop_meta_at(delete_pos)
.map(|meta| !meta.attributes.configurable())
.unwrap_or(false)
{
return DeleteOutcome::NonConfigurable;
}
// 数组重建前保存元素区(值 + 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() {
Some((0..obj.array_prop_count).map(|i| obj.prop_meta_at(i)).collect())
} else {
None
};
let retained: Vec<(u32, JsValue, Option<PropMetaEntry>)> = all_keys
.into_iter()
// 数组元素区由下方独立保存/恢复,此处只重建命名属性(元素键绝对下标 < 元素数)。
.filter(|(_, pos)| *pos != delete_pos && !(obj.is_array() && *pos < obj.array_prop_count))
.map(|(si, pos)| (si, obj.get_prop_at(pos), obj.prop_meta_at(pos)))
.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_array_elements_meta();
for (i, entry) in saved_meta.into_iter().enumerate() {
if let Some(entry) = entry {
while meta.len() <= i {
meta.push(None);
}
meta[i] = Some(entry);
}
}
}
}
obj.bump_generation();
// 删除成功:镜像槽同步 undefined(成员形删除 / Reflect.deleteProperty /
// 0x9C 清槽同源收口,后两者对此幂等)。
vm.sync_global_builtin_mirror(obj, key_si, JsValue::undefined());
DeleteOutcome::Deleted
}
/// 删除对象自身属性并投影为布尔(`Reflect.deleteProperty` 语义):成功返回 true,
/// 属性不可配置返回 false,恒不抛错。
pub fn delete_own_property<H: VmHost>(vm: &mut H, obj: &mut JsObject, key_si: u32) -> bool {
delete_own_property_outcome(vm, obj, key_si) != DeleteOutcome::NonConfigurable
}
/// JS `Object()` 构造逻辑:创建空对象(prototype 为 null,由 VM 补装内置原型)。
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 object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
// 对象参数原样返回;原始值参数创建对应 boxed 对象(规范 ToObject)。
if val.is_object() {
return NativeResult::Ok(val);
}
if val.is_null() || val.is_undefined() {
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
return NativeResult::Ok(JsValue::from_js_object(obj));
}
if val.is_int() || val.is_double() {
let proto = vm.session().builtin_world().number_proto.as_ptr() as *mut JsObject;
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)));
let obj_ref = unsafe { &mut *obj };
obj_ref.type_tag = JsObject::OBJ_TYPE_NUMBER_OBJ;
obj_ref.set_prop_at(0, val);
return NativeResult::Ok(JsValue::from_js_object(obj));
}
if val.is_string() {
let proto = vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject;
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)));
let obj_ref = unsafe { &mut *obj };
obj_ref.type_tag = JsObject::OBJ_TYPE_STRING_OBJ;
obj_ref.set_prop_at(0, val);
return NativeResult::Ok(JsValue::from_js_object(obj));
}
if val.is_bool() {
let proto = vm.session().builtin_world().boolean_proto.as_ptr() as *mut JsObject;
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)));
let obj_ref = unsafe { &mut *obj };
obj_ref.type_tag = JsObject::OBJ_TYPE_BOOLEAN_OBJ;
obj_ref.set_prop_at(0, val);
return NativeResult::Ok(JsValue::from_js_object(obj));
}
if val.is_symbol() {
let proto = vm.session().builtin_world().symbol_proto.as_ptr() as *mut JsObject;
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)));
let obj_ref = unsafe { &mut *obj };
obj_ref.type_tag = JsObject::OBJ_TYPE_SYMBOL_OBJ;
obj_ref.set_prop_at(0, val);
return NativeResult::Ok(JsValue::from_js_object(obj));
}
if val.is_bigint() {
let proto = vm.session().builtin_world().bigint_proto.as_ptr() as *mut JsObject;
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)));
let obj_ref = unsafe { &mut *obj };
obj_ref.set_prop_at(0, val);
return NativeResult::Ok(JsValue::from_js_object(obj));
}
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
NativeResult::Ok(JsValue::from_js_object(obj))
}
/// `Object.keys(obj)`:返回可枚举自身属性的字符串名数组。
pub fn object_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = match require_obj_arg(vm, args, "keys") {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let obj = unsafe { &*obj_ptr };
let owned_keys: Vec<(u32, u32)> = walk_own_keys(vm, obj)
.into_iter()
.filter(|(_si, offset)| {
obj.prop_meta_at(*offset)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.collect();
let n = owned_keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, (si, _offset)) in owned_keys.iter().enumerate() {
let key_val = key_si_to_js_value(vm, *si);
unsafe {
(*arr).set_prop_at(i, key_val);
}
}
unsafe {
(*arr).set_prop_count(n);
}
NativeResult::Ok(JsValue::from_js_object(arr))
}
/// `Object.create(proto, properties)`:以指定 prototype 创建新对象,可选地按
/// 属性描述符集合定义自身属性。
pub fn object_create<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.create: at least 1 argument required"));
}
let proto_val = vm.reg(args[1]);
if proto_val.is_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));
}
if !proto_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.create: prototype must be an object or null",
));
}
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))
}
/// `Object.assign(target, ...sources)`:拷贝各源对象的可枚举自身属性到目标,返回目标。
pub fn object_assign<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.assign requires a target"));
}
let target_val = vm.reg(args[1]);
let target_val = match oxide_runtime_api::to_object(target_val, vm) {
Ok(val) => val,
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
};
let target_ptr = target_val.as_js_object_ptr();
if target_ptr.is_null() {
return NativeResult::Ok(target_val);
}
let mut all_assignments: Vec<(u32, JsValue)> = Vec::new();
for &arg_reg in args.iter().skip(2) {
let source_val = vm.reg(arg_reg);
if !source_val.is_object() {
continue;
}
let source_ptr = source_val.as_js_object_ptr();
if source_ptr.is_null() {
continue;
}
let source_keys: Vec<(u32, u32)> = {
let source = unsafe { &*source_ptr };
walk_own_keys(vm, source)
.into_iter()
// CopyDataProperties:只拷贝可枚举自身属性(无显式 meta 视为可枚举)。
.filter(|(_si, offset)| {
source
.prop_meta_at(*offset)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.collect()
};
for (si, _offset) in source_keys {
let source = unsafe { &*source_ptr };
// ordinary_get 取值(receiver = 源对象):数据属性等价直读,访问器属性
// 触发 getter;getter 抛错按规范中断整个 assign。
let val = match vm.ordinary_get(source, si, source_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)),
};
all_assignments.push((si, val));
}
}
let target = unsafe { &mut *target_ptr };
for (si, val) in all_assignments {
let promoted = vm.promote_if_needed_for_write_ptr(target_ptr, val);
// Set(to, key, value, true):receiver 为目标对象(目标同名 setter 的 this 指向 target)。
if let Err(err) = vm.ordinary_set(target, si, promoted, target_val, true) {
return NativeResult::Err(crate::error::create_type_error(vm, &err));
}
}
NativeResult::Ok(target_val)
}
/// `Object.is(a, b)`:按 SameValue 语义比较(NaN 相等、+0/-0 不等)。
pub fn object_is<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 3 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.is called with insufficient arguments"));
}
let lhs = vm.reg(args[1]);
let rhs = vm.reg(args[2]);
NativeResult::Ok(JsValue::bool(oxide_runtime_api::same_value(lhs, rhs)))
}
/// 按 ToPropertyDescriptor 语义把描述符定义/修改到对象的自身属性上。
///
/// 处理数据(value/writable)与访问器(get/set)两类描述符;描述符字段沿原型
/// 链解析(ordinary_get,触发 accessor getter);字段缺失时按已有属性回填,
/// 新属性缺省字段为 false。
///
/// # 步骤
/// 1. 解析 value/get/set/writable/enumerable/configurable 字段
/// 2. 校验 data 与 accessor 字段互斥、getter/setter 可调用
/// 3. 按访问器/数据/无字段三种形态调用对应 define 操作
///
/// # 边界与前提
/// - `desc_val` 必须是对象;`key_si` 须已 intern
/// - 修改已有属性时缺省字段回填现有值;新属性缺省为 false
///
/// # 副作用
/// - 修改 obj 的 shape 链、属性表与 generation
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() {
return Err("Property description must be an object".to_string());
}
let value_si = vm.kernel_core().perm_interner().intern("value").0;
let get_si = vm.kernel_core().perm_interner().intern("get").0;
let set_si = vm.kernel_core().perm_interner().intern("set").0;
let writable_si = vm.kernel_core().perm_interner().intern("writable").0;
let enumerable_si = vm.kernel_core().perm_interner().intern("enumerable").0;
let configurable_si = vm.kernel_core().perm_interner().intern("configurable").0;
let value_field = own_field(vm, desc_val, value_si);
let get_field = own_field(vm, desc_val, get_si);
let set_field = own_field(vm, desc_val, set_si);
let writable_field = own_field(vm, desc_val, writable_si);
let existing_pos = {
let obj = unsafe { &*obj_ptr };
vm.get_own_property_slot(obj, key_si)
};
let existing_meta = existing_pos.map(|pos| {
let obj = unsafe { &*obj_ptr };
// 已有属性无显式 meta(普通数据属性/数组元素)时按默认属性回填:
// 描述符缺省字段保持现有值(writable/enumerable/configurable 均 true),
// 而非按新属性处理为 false(Object.defineProperty 省略字段不改已有属性)。
obj.prop_meta_at(pos)
.unwrap_or_else(|| oxide_types::object::PropMetaEntry::data(PropAttributes::DEFAULT_DATA))
});
// 修改已有属性时缺省字段回填现有值,仅定义新属性时缺省才为 false。
let enumerable = own_field(vm, desc_val, enumerable_si)
.map(oxide_runtime_api::to_boolean)
.unwrap_or_else(|| existing_meta.map(|m| m.attributes.enumerable()).unwrap_or(false));
let configurable = own_field(vm, desc_val, configurable_si)
.map(oxide_runtime_api::to_boolean)
.unwrap_or_else(|| existing_meta.map(|m| m.attributes.configurable()).unwrap_or(false));
let has_data = value_field.is_some() || writable_field.is_some();
let has_accessor = get_field.is_some() || set_field.is_some();
if has_data && has_accessor {
return Err("Invalid property descriptor: cannot mix data and accessor fields".to_string());
}
let existing_value = existing_pos.map_or(JsValue::undefined(), |pos| {
let obj = unsafe { &*obj_ptr };
obj.get_prop_at(pos)
});
let obj = unsafe { &mut *obj_ptr };
if has_accessor {
let get = get_field.unwrap_or_else(|| {
existing_meta
.filter(|m| m.is_accessor)
.map(|m| m.get)
.unwrap_or(JsValue::undefined())
});
let set = set_field.unwrap_or_else(|| {
existing_meta
.filter(|m| m.is_accessor)
.map(|m| m.set)
.unwrap_or(JsValue::undefined())
});
if (!get.is_undefined() && !is_callable(get)) || (!set.is_undefined() && !is_callable(set)) {
return Err("accessor descriptor get/set must be callable or undefined".to_string());
}
vm.define_accessor_property(obj, key_si, get, set, PropAttributes::new(false, enumerable, configurable))?;
} else if has_data {
let value = if existing_pos.is_some() {
value_field.unwrap_or(existing_value)
} else {
value_field.unwrap_or(JsValue::undefined())
};
let writable = if existing_pos.is_some() {
writable_field
.map(oxide_runtime_api::to_boolean)
.unwrap_or_else(|| existing_meta.map(|m| m.attributes.writable()).unwrap_or(false))
} else {
writable_field.map(oxide_runtime_api::to_boolean).unwrap_or(false)
};
vm.define_data_property(obj, key_si, value, PropAttributes::new(writable, enumerable, configurable))?;
} else {
if existing_pos.is_none() {
vm.define_data_property(
obj,
key_si,
JsValue::undefined(),
PropAttributes::new(false, enumerable, configurable),
)?;
return Ok(());
}
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 attrs = PropAttributes::new(writable, enumerable, configurable);
if is_accessor {
let get = existing_meta.map(|m| m.get).unwrap_or(JsValue::undefined());
let set = existing_meta.map(|m| m.set).unwrap_or(JsValue::undefined());
vm.define_accessor_property(obj, key_si, get, set, attrs)?;
} else {
vm.define_data_property(obj, key_si, existing_value, attrs)?;
}
}
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 = match oxide_runtime_api::to_object(vm.reg(args[1]), vm) {
Ok(v) => v,
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
};
let obj_ptr = obj_val.as_js_object_ptr();
// well-known symbol 等特殊键统一走 property_key_si(映射到各自的 Symbol 键),
// 保证与计算属性访问、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)
}
/// `Object.getOwnPropertyDescriptor(obj, key)`:返回自身属性的描述符对象;
/// 不存在返回 undefined。
pub fn object_get_own_property_descriptor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 3 {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.getOwnPropertyDescriptor called on non-object",
));
}
let obj_val = match oxide_runtime_api::to_object(vm.reg(args[1]), vm) {
Ok(v) => v,
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
};
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}。
// 冻结数组(含模板对象)writable=false;普通数组 writable=true。
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_frozen()));
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 {
return NativeResult::Ok(JsValue::undefined());
};
let found_value = obj.get_prop_at(offset);
let found_meta = obj.prop_meta_at(offset);
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;
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 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()));
}
NativeResult::Ok(JsValue::from_js_object(desc))
}
/// 按 ToPropertyDescriptor 语义取描述符字段:沿原型链判存在性,存在时经
/// ordinary_get 取值(触发 accessor getter,receiver 为描述符对象本身)。
fn own_field<H: VmHost>(vm: &mut H, desc: JsValue, prop_si: u32) -> Option<JsValue> {
// ordinary_get 对缺失属性返回 undefined,无法区分"不存在"与"值为
// undefined",故先用 resolve_property 沿原型链判 HasProperty。
let obj = unsafe { &*desc.as_js_object_ptr() };
vm.resolve_property(obj, prop_si)?;
vm.ordinary_get(obj, prop_si, desc).ok()
}
fn is_callable(value: JsValue) -> bool {
value.is_object() && unsafe { &*value.as_js_object_ptr() }.is_function()
}
fn push_desc_prop(obj: &mut JsObject, shape_forge: &ShapeForge, prop_si: u32, val: JsValue) {
let shape_id = shape_forge.make_shape(obj.shape_id(), prop_si);
obj.set_shape_id(shape_id);
obj.push_prop(val);
}
/// 校验并取出 Object 类方法的目标对象:`args[0]` 为接收者,`args[1]` 为待转换值,
/// 缺参或值经 ToObject 失败(null/undefined)时抛 TypeError。
fn require_obj_arg<H: VmHost>(vm: &mut H, args: &[u8], fn_name: &str) -> Result<*mut JsObject, JsValue> {
if args.len() < 2 {
return Err(crate::error::create_type_error(vm, &format!("Object.{fn_name} called on non-object")));
}
let val = vm.reg(args[1]);
// ToObject:原始值装箱后返回其对象指针;null/undefined 抛 TypeError。
let obj_val = match oxide_runtime_api::to_object(val, vm) {
Ok(v) => v,
Err(msg) => return Err(crate::error::create_type_error(vm, &msg)),
};
Ok(obj_val.as_js_object_ptr())
}
macro_rules! native_try {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(err) => return NativeResult::Err(err),
}
};
}
/// 把指定存储下标的 own 属性 meta 改写为冻结形态:数据属性 writable=false +
/// configurable=false,访问器属性 configurable=false(writable 不适用),
/// enumerable 保留原值。
fn freeze_own_prop_meta(obj: &mut JsObject, store: u32) {
let meta = obj
.prop_meta_at(store)
.unwrap_or_else(|| PropMetaEntry::data(PropAttributes::DEFAULT_DATA));
let attrs = PropAttributes::new(false, meta.attributes.enumerable(), false);
if meta.is_accessor {
obj.set_accessor_meta(store, meta.get, meta.set, attrs);
} else {
obj.set_data_meta(store, attrs);
}
}
/// 把指定存储下标的 own 属性 meta 改写为密封形态:configurable=false,
/// writable/enumerable 与访问器形态保留原值。
fn seal_own_prop_meta(obj: &mut JsObject, store: u32) {
let meta = obj
.prop_meta_at(store)
.unwrap_or_else(|| PropMetaEntry::data(PropAttributes::DEFAULT_DATA));
let attrs = PropAttributes::new(meta.attributes.writable(), meta.attributes.enumerable(), false);
if meta.is_accessor {
obj.set_accessor_meta(store, meta.get, meta.set, attrs);
} else {
obj.set_data_meta(store, attrs);
}
}
/// `Object.freeze(obj)`:冻结对象(不可扩展 + 全部属性不可配置/不可写),返回原对象。
///
/// # 副作用
/// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续
/// 写/define 一律回落 ordinary_set / define 检查(writable/configurable 判定)
pub fn object_freeze<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.freeze called on non-object"));
}
let val = vm.reg(args[1]);
if !val.is_object() || val.as_js_object_ptr().is_null() {
return NativeResult::Ok(val);
}
let obj_ptr = val.as_js_object_ptr();
{
let obj = unsafe { &mut *obj_ptr };
// 命名属性:walk_own_keys 返回绝对存储索引(数组含元素区偏移)。
let keys = walk_own_keys(vm, obj);
for (_si, pos) in keys {
freeze_own_prop_meta(obj, pos);
}
// 数组元素区独立于 shape 链(hole 非 own 属性,跳过)。
if obj.is_array() {
for i in 0..obj.array_prop_count {
if obj.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
continue;
}
freeze_own_prop_meta(obj, i);
}
}
obj.set_frozen(true);
obj.set_extensible(false);
}
NativeResult::Ok(val)
}
/// `Object.seal(obj)`:密封对象(不可扩展 + 全部属性不可配置),返回原对象。
///
/// # 副作用
/// - 逐属性写 meta 使 `has_prop_meta()` 恒 true,IC 直写路径自动失效,后续
/// define 回落 non-configurable 检查(已有属性写仍可正常进行)
pub fn object_seal<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.seal called on non-object"));
}
let val = vm.reg(args[1]);
if !val.is_object() || val.as_js_object_ptr().is_null() {
return NativeResult::Ok(val);
}
let obj_ptr = val.as_js_object_ptr();
{
let obj = unsafe { &mut *obj_ptr };
let keys = walk_own_keys(vm, obj);
for (_si, pos) in keys {
seal_own_prop_meta(obj, pos);
}
if obj.is_array() {
for i in 0..obj.array_prop_count {
if obj.prop_meta_at(i).is_some_and(|m| m.is_hole()) {
continue;
}
seal_own_prop_meta(obj, i);
}
}
obj.set_sealed(true);
obj.set_extensible(false);
}
NativeResult::Ok(val)
}
/// `Object.preventExtensions(obj)`:禁止添加新属性,返回原对象。
pub fn object_prevent_extensions<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.preventExtensions called on non-object"));
}
let val = vm.reg(args[1]);
if !val.is_object() || val.as_js_object_ptr().is_null() {
return NativeResult::Ok(val);
}
let obj_ptr = val.as_js_object_ptr();
unsafe {
(*obj_ptr).set_extensible(false);
}
NativeResult::Ok(val)
}
/// `Object.isFrozen(obj)`:对象是否冻结(检查 extensible 及全部属性 writable/configurable)。
pub fn object_is_frozen<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.isFrozen called on non-object"));
}
let val = vm.reg(args[1]);
if !val.is_object() {
return NativeResult::Ok(JsValue::bool(true));
}
let obj_ptr = val.as_js_object_ptr();
if obj_ptr.is_null() {
return NativeResult::Ok(JsValue::bool(true));
}
let obj = unsafe { &*obj_ptr };
if obj.is_frozen() {
return NativeResult::Ok(JsValue::bool(true));
}
if obj.is_extensible() {
return NativeResult::Ok(JsValue::bool(false));
}
let keys = walk_own_keys(vm, obj);
for (_si, offset) in keys {
if let Some(meta) = obj.prop_meta_at(offset) {
if meta.attributes.configurable() {
return NativeResult::Ok(JsValue::bool(false));
}
if !meta.is_accessor && meta.attributes.writable() {
return NativeResult::Ok(JsValue::bool(false));
}
} else {
return NativeResult::Ok(JsValue::bool(false));
}
}
NativeResult::Ok(JsValue::bool(true))
}
/// `Object.isSealed(obj)`:对象是否密封(检查 extensible 及全部属性 configurable)。
pub fn object_is_sealed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.isSealed called on non-object"));
}
let val = vm.reg(args[1]);
if !val.is_object() {
return NativeResult::Ok(JsValue::bool(true));
}
let obj_ptr = val.as_js_object_ptr();
if obj_ptr.is_null() {
return NativeResult::Ok(JsValue::bool(true));
}
let obj = unsafe { &*obj_ptr };
if obj.is_sealed() {
return NativeResult::Ok(JsValue::bool(true));
}
if obj.is_extensible() {
return NativeResult::Ok(JsValue::bool(false));
}
let keys = walk_own_keys(vm, obj);
for (_si, offset) in keys {
if let Some(meta) = obj.prop_meta_at(offset) {
if meta.attributes.configurable() {
return NativeResult::Ok(JsValue::bool(false));
}
} else {
return NativeResult::Ok(JsValue::bool(false));
}
}
NativeResult::Ok(JsValue::bool(true))
}
/// `Object.isExtensible(obj)`:对象是否可扩展。
pub fn object_is_extensible<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.isExtensible called on non-object"));
}
let val = vm.reg(args[1]);
if !val.is_object() {
return NativeResult::Ok(JsValue::bool(false));
}
let obj_ptr = val.as_js_object_ptr();
if obj_ptr.is_null() {
return NativeResult::Ok(JsValue::bool(false));
}
let obj = unsafe { &*obj_ptr };
NativeResult::Ok(JsValue::bool(obj.is_extensible()))
}
/// `Object.getOwnPropertyNames(obj)`:返回全部自身属性名(含不可枚举)。
pub fn object_get_own_property_names<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "getOwnPropertyNames"));
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
let n = keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, (si, _)) in keys.iter().enumerate() {
let key_val = key_si_to_js_value(vm, *si);
unsafe {
(*arr).set_prop_at(i, key_val);
}
}
unsafe {
(*arr).set_prop_count(n);
}
NativeResult::Ok(JsValue::from_js_object(arr))
}
/// `Object.defineProperties(obj, descriptors)`:批量按描述符定义属性,返回目标对象。
pub fn object_define_properties<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 3 {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.defineProperties: expected at least 2 arguments",
));
}
let target_val = vm.reg(args[1]);
let target_ptr = match require_obj_arg(vm, args, "defineProperties") {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
if let Err(msg) = define_all_from_properties(vm, target_ptr, vm.reg(args[2])) {
return NativeResult::Err(crate::error::create_type_error(vm, &msg));
}
NativeResult::Ok(target_val)
}
/// `Object.fromEntries(entries)`:由 `[key, value]` 对数组构建对象。
pub fn object_from_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.fromEntries: expected 1 argument"));
}
let entries = vm.reg(args[1]);
if !entries.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.fromEntries: argument must be iterable"));
}
let entries_ptr = entries.as_js_object_ptr();
if entries_ptr.is_null() {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.fromEntries: argument must be iterable"));
}
let obj = vm.alloc_object(JsObject::new_empty(
EMPTY_SHAPE_ID,
JsValue::from_js_object(vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject),
));
let target_val = JsValue::from_js_object(obj);
// entry 数组的元素存元素区(元素区之外才是命名属性区),按元素区长度迭代。
let n: usize = unsafe { (*entries_ptr).array_prop_count } as usize;
for i in 0..n {
let pair_val = unsafe { (*entries_ptr).get_prop_at(i) };
if !pair_val.is_object() {
continue;
}
let pair_ptr = pair_val.as_js_object_ptr();
if pair_ptr.is_null() {
continue;
}
let pair = unsafe { &*pair_ptr };
let key_val = pair.get_prop_at(0);
let value_val = pair.get_prop_at(1);
// ToPropertyKey 语义建键:int/规范数字串/symbol 统一映射,避免数字键分裂。
let si = vm.property_key_si(key_val);
let promoted = vm.promote_if_needed_for_write_ptr(obj, value_val);
let _ = vm.ordinary_set(unsafe { &mut *obj }, si, promoted, target_val, true);
}
NativeResult::Ok(target_val)
}
/// `Object.getPrototypeOf(obj)`:返回对象的 prototype。
pub fn object_get_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "getPrototypeOf"));
NativeResult::Ok(unsafe { (*obj_ptr).proto() })
}
/// `Object.hasOwn(obj, key)`:对象是否有指定自身属性。
pub fn object_has_own<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 3 {
return NativeResult::Ok(JsValue::bool(false));
}
let obj_ptr = native_try!(require_obj_arg(vm, args, "hasOwn"));
let key_si = vm.property_key_si(vm.reg(args[2]));
let obj = unsafe { &*obj_ptr };
NativeResult::Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some()))
}
/// `Object.prototype.valueOf`:返回 this 本身(配合 OrdinaryToPrimitive 的兜底)。
pub fn object_proto_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// Object.prototype.valueOf 原样返回 this 对象;OrdinaryToPrimitive
// 因结果非原始值而继续走 toString。
NativeResult::Ok(vm.reg(args[0]))
}
/// 沿原型链判断是否为 Error 家族对象:对象自身带 `OBJ_TYPE_ERROR` 标签,或原型链上
/// 含 Error.prototype(等价 [[ErrorData]] 内部槽语义,覆盖 Error 用户子类)。
fn is_error_family<H: VmHost>(vm: &H, ptr: *mut JsObject) -> bool {
let error_proto_ptr = vm.session().builtin_world().error_proto.as_ptr() as *mut JsObject;
let mut cur = ptr;
while !cur.is_null() {
if std::ptr::eq(cur, error_proto_ptr) {
return true;
}
// SAFETY: cur 沿原型链遍历,链上每个节点都是合法 JsObject(proto 非对象时为空指针终止)。
let o = unsafe { &*cur };
if o.is_error_obj() {
return true;
}
cur = o.proto().as_js_object_ptr();
}
false
}
/// `Object.prototype.toString`:返回 `[object Tag]`。对象路径先按内置类型判定标签,
/// 再读 `@@toStringTag`——为字符串时覆盖内置标签(TypedArray 依赖它区分具体类型)。
pub fn object_proto_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let tag = if !this_val.is_object() {
if this_val.is_string() {
"String"
} else if this_val.is_int() || this_val.is_double() {
"Number"
} else if this_val.is_bool() {
"Boolean"
} else if this_val.is_symbol() {
"Symbol"
} else {
"Object"
}
} else {
let ptr = this_val.as_js_object_ptr();
if ptr.is_null() {
"Object"
} else {
let obj = unsafe { &*ptr };
let builtin = if obj.is_array() {
"Array"
} else if obj.is_function() {
"Function"
} else if obj.is_string_obj() {
"String"
} else if obj.is_number_obj() {
"Number"
} else if obj.is_boolean_obj() {
"Boolean"
} else if obj.is_regexp_obj() {
"RegExp"
} else if obj.is_date_obj() {
"Date"
} else if obj.is_typed_array_obj() {
"TypedArray"
} else if obj.is_array_buffer_obj() {
"ArrayBuffer"
} else if obj.is_arguments_obj() {
"Arguments"
} else if is_error_family(vm, ptr) {
"Error"
} else if std::ptr::eq(ptr, vm.session().builtin_world().math_object.as_ptr()) {
"Math"
} else if std::ptr::eq(ptr, vm.session().builtin_world().json_object.as_ptr()) {
"JSON"
} else {
"Object"
};
// @@toStringTag 为字符串时覆盖内置标签;getter 抛错透传原异常。
let tag_key = make_well_known_symbol_key(9);
let tag_value = match vm.ordinary_get(obj, tag_key, this_val) {
Ok(value) if !value.is_undefined() => Ok(value),
Ok(_) => {
let legacy_key = vm.kernel_core().perm_interner().intern("@@toStringTag").0;
vm.ordinary_get(obj, legacy_key, this_val)
}
Err(err) => Err(err),
};
match tag_value {
Ok(v) => match vm.lookup_str(v) {
Some(s) => return NativeResult::Ok(vm.new_string(&format!("[object {s}]"))),
None => builtin,
},
Err(err) => return NativeResult::Err(crate::iterator::engine_error(vm, &err)),
}
}
};
NativeResult::Ok(vm.new_string(&format!("[object {tag}]")))
}
/// `Object.prototype.hasOwnProperty(key)`:this 是否有指定自身属性。
///
/// # 步骤
/// 1. 先 ToPropertyKey 求键(spec 顺序:键先于 ToObject)。
/// 2. ToObject 装箱 this(null/undefined 抛 TypeError,原始值装箱后查 own 槽)。
pub fn object_proto_has_own_property<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let key_si = vm.property_key_si(vm.reg(args[1]));
let this_val = vm.reg(args[0]);
let obj_val = match oxide_runtime_api::to_object(this_val, vm) {
Ok(v) => v,
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
};
let obj = unsafe { &*obj_val.as_js_object_ptr() };
NativeResult::Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some()))
}
/// `Object.prototype.propertyIsEnumerable(key)`:指定自身属性是否可枚举。
///
/// # 步骤
/// 1. 先 ToPropertyKey 求键。
/// 2. ToObject 装箱 this(null/undefined 抛 TypeError)。
pub fn object_proto_property_is_enumerable<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let key_si = vm.property_key_si(vm.reg(args[1]));
let this_val = vm.reg(args[0]);
let obj_val = match oxide_runtime_api::to_object(this_val, vm) {
Ok(v) => v,
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
};
let obj = unsafe { &*obj_val.as_js_object_ptr() };
let Some(pos) = vm.get_own_property_slot(obj, key_si) else {
return NativeResult::Ok(JsValue::bool(false));
};
let enumerable = obj
.prop_meta_at(pos)
.map(|meta| meta.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable());
NativeResult::Ok(JsValue::bool(enumerable))
}
/// `Object.prototype.__defineGetter__(key, getter)`:把 key 定义为访问器属性,
/// getter 作 `[[Get]]`,描述符 `{ enumerable: true, configurable: true }`。
///
/// # 步骤
/// 1. ToObject 装箱 this(null/undefined 抛 TypeError)
/// 2. getter 非 callable → TypeError
/// 3. ToPropertyKey 求键
/// 4. DefinePropertyOrThrow(复用 `define_accessor_property` 的拒绝语义:
/// 不可扩展对象 / 不可配置属性覆盖均抛 TypeError)
///
/// # 副作用
/// - 修改 this 的 shape 链与属性表;返回 undefined
pub fn object_proto_define_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 3 {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.prototype.__defineGetter__ requires 2 arguments",
));
}
let this_val = vm.reg(args[0]);
let obj_val = match oxide_runtime_api::to_object(this_val, vm) {
Ok(v) => v,
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
};
let getter = vm.reg(args[2]);
if !is_callable(getter) {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.prototype.__defineGetter__: getter must be callable",
));
}
let key_si = match vm.to_property_key_si(vm.reg(args[1])) {
Ok(si) => si,
Err(e) => {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &e));
return NativeResult::Err(exc);
}
};
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
let attrs = PropAttributes::new(false, true, true);
// DefinePropertyOrThrow 的 desc 无 [[Set]] 键:覆盖既有访问器时保留其 setter
// (define_accessor_property 是全量替换,set 缺省须显式传现有值)。
let existing_set = vm
.get_own_property_slot(obj, key_si)
.and_then(|pos| obj.prop_meta_at(pos))
.filter(|m| m.is_accessor)
.map(|m| m.set)
.unwrap_or(JsValue::undefined());
if let Err(e) = vm.define_accessor_property(obj, key_si, getter, existing_set, attrs) {
return NativeResult::Err(crate::error::create_type_error(vm, &e));
}
NativeResult::Ok(JsValue::undefined())
}
/// `Object.entries(obj)`:返回可枚举自身属性的 `[key, value]` 对数组。
pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "entries"));
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
let owned_keys: Vec<(u32, u32)> = keys
.into_iter()
.filter(|(_si, offset)| {
obj.prop_meta_at(*offset)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.collect();
let n = owned_keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, (si, offset)) in owned_keys.iter().enumerate() {
let key_val = key_si_to_js_value(vm, *si);
let val = obj.get_prop_at(*offset);
let pair = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
2,
vm.epoch().bump(),
));
unsafe {
(*pair).set_prop_at(0, key_val);
(*pair).set_prop_at(1, val);
(*pair).set_prop_count(2);
(*arr).set_prop_at(i, JsValue::from_js_object(pair));
}
}
unsafe {
(*arr).set_prop_count(n);
}
NativeResult::Ok(JsValue::from_js_object(arr))
}
/// `Object.groupBy(items, callbackFn)`:按回调返回的键分组迭代元素到新对象。
/// 回调以 `(element, key)` 调用(对数组 items,key 为下标),返回值经
/// ToPropertyKey 转换后作为分组键;结果对象原型为 null。
pub fn object_group_by<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("Object.groupBy called with {} args", args.len());
if args.len() < 3 {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy requires 2 arguments"));
}
let callback_val = vm.reg(args[2]);
if !is_callable(callback_val) {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy: callbackFn is not callable"));
}
let items_val = vm.reg(args[1]);
// 取 @@iterator 方法(同步)。
let items_obj = match oxide_runtime_api::to_object(items_val, vm) {
Ok(v) => v,
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
};
let items_ptr = items_obj.as_js_object_ptr();
let sym_iter_si = make_well_known_symbol_key(0);
let iter_method = match unsafe { vm.ordinary_get(&*items_ptr, sym_iter_si, items_val) } {
Ok(m) => m,
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
};
if !is_callable(iter_method) {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.groupBy: items[Symbol.iterator] is not callable",
));
}
let iter_val = match vm.call_function_sync(iter_method, items_val, &[]) {
Ok(v) => v,
Err(e) => {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &e));
return NativeResult::Err(exc);
}
};
if !iter_val.is_object() || iter_val.as_js_object_ptr().is_null() {
return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy: iterator is not an object"));
}
let iter_obj = unsafe { &*iter_val.as_js_object_ptr() };
let next_si = vm.kernel_core().perm_interner().intern("next").0;
let next_fn = match vm.ordinary_get(iter_obj, next_si, iter_val) {
Ok(f) => f,
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
};
// 创建结果对象(OrdinaryObjectCreate(null),与 Object.create(null) 同款)。
let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let result_val = JsValue::from_js_object(result);
let mut counter: i32 = 0;
loop {
let next_result = match vm.call_function_sync(next_fn, iter_val, &[]) {
Ok(v) => v,
Err(e) => {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &e));
return NativeResult::Err(exc);
}
};
if !next_result.is_object() || next_result.as_js_object_ptr().is_null() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Object.groupBy: iterator next() returned non-object",
));
}
let nr = unsafe { &*next_result.as_js_object_ptr() };
let done_si = vm.kernel_core().perm_interner().intern("done").0;
let done = match vm.ordinary_get(nr, done_si, next_result) {
Ok(v) => oxide_runtime_api::to_boolean(v),
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
};
if done {
break;
}
let value_si = vm.kernel_core().perm_interner().intern("value").0;
let element = match vm.ordinary_get(nr, value_si, next_result) {
Ok(v) => v,
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
};
// 调用 callbackFn(element, counter):counter 对数组 items 即元素下标 k。
let key = match vm.call_function_sync(callback_val, JsValue::undefined(), &[element, JsValue::int(counter)]) {
Ok(v) => v,
Err(e) => {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &e));
return NativeResult::Err(exc);
}
};
// ToPropertyKey:undefined 归 "undefined" 分组,不跳过;对象转换异常原样传播。
let key_si = match vm.to_property_key_si(key) {
Ok(si) => si,
Err(e) => {
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &e));
return NativeResult::Err(exc);
}
};
// 检查 result 上是否已有该键的数组分组。
let result_ref = unsafe { &*result };
let existing = match vm.ordinary_get(result_ref, key_si, result_val) {
Ok(v) if v.is_object() && !v.as_js_object_ptr().is_null() => {
let arr = unsafe { &*v.as_js_object_ptr() };
if arr.is_array() {
Some(v)
} else {
None
}
}
_ => None,
};
let arr_val = if let Some(existing) = existing {
existing
} else {
let arr_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(arr_proto),
0,
vm.epoch().bump(),
));
let new_arr_val = JsValue::from_js_object(arr);
let result_ref_mut = unsafe { &mut *result };
let promoted = vm.promote_if_needed_for_write_ptr(result, new_arr_val);
let _ = vm.ordinary_set(result_ref_mut, key_si, promoted, result_val, true);
new_arr_val
};
// push element 到分组数组(写入前 promote,与 Array.prototype.push 同款)。
let arr_obj = unsafe { &mut *arr_val.as_js_object_ptr() };
let idx = arr_obj.prop_count();
let promoted_elem = vm.promote_if_needed_for_write_ptr(arr_val.as_js_object_ptr(), element);
arr_obj.set_prop_at(idx, promoted_elem);
counter += 1;
}
NativeResult::Ok(result_val)
}
/// `Object.values(obj)`:返回可枚举自身属性的值数组。
pub fn object_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj_ptr = native_try!(require_obj_arg(vm, args, "values"));
let obj = unsafe { &*obj_ptr };
let keys = walk_own_keys(vm, obj);
let owned_keys: Vec<(u32, u32)> = keys
.into_iter()
.filter(|(_si, offset)| {
obj.prop_meta_at(*offset)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable())
})
.collect();
let n = owned_keys.len();
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, (_si, offset)) in owned_keys.iter().enumerate() {
let val = obj.get_prop_at(*offset);
unsafe {
(*arr).set_prop_at(i, val);
}
}
unsafe {
(*arr).set_prop_count(n);
}
NativeResult::Ok(JsValue::from_js_object(arr))
}