This repository has been archived on 2026-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OxideJS/crates/oxide_vm/src/dispatch/property.rs
T

575 lines
27 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 crate::{ic_debug, ic_trace, vm_trace};
use oxide_bytecode::opcode::OpCode;
use oxide_kernel::prop_forge::PropTemplate;
use oxide_runtime_api as coercion;
use oxide_types::object::JsObject;
use oxide_types::private_key::make_private_name_id;
use oxide_types::value::JsValue;
use crate::ic_helper::{self, ic_get_hit, ic_set_hit};
use crate::vm::{Vm, MAX_PROTO_CHAIN_DEPTH};
#[cold]
fn prop_cache_miss() {}
impl Vm {
pub(crate) fn dispatch_property_op(&mut self, op: OpCode, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("PROPERTY_OP {:?} rd={} a={} b={}", op, rd, a, b);
match op {
OpCode::IC_GET_PROP => self.dispatch_ic_get_prop(a, b),
OpCode::IC_SET_PROP => self.dispatch_ic_set_prop(rd, a, b),
OpCode::GET_PROP => self.dispatch_get_prop(rd, a, b),
OpCode::SET_PROP => self.dispatch_set_prop(rd, a, b),
OpCode::GET_PROP_DYNAMIC => self.dispatch_get_prop_dynamic(rd, a, b),
OpCode::SET_PROP_DYNAMIC => self.dispatch_set_prop_dynamic(rd, a, b),
OpCode::SET_ELEM => self.dispatch_set_elem(rd, a, b),
OpCode::GET_PRIVATE => self.dispatch_get_private(rd, a, b),
OpCode::SET_PRIVATE => self.dispatch_set_private(rd, a, b),
OpCode::INIT_PRIVATE => self.dispatch_init_private(rd, a, b),
OpCode::PRIVATE_BRAND_IN => self.dispatch_private_brand_in(rd, a, b),
_ => unreachable!("non-property opcode passed to dispatch_property_op"),
}
}
fn private_key_from_reg(&self, reg: usize) -> u32 {
let value = self.regs[reg];
let local_id = if value.is_int() {
value.as_int().max(0) as u32
} else if value.is_double() {
value.as_double().max(0.0) as u32
} else {
0
};
make_private_name_id(local_id)
}
fn primitive_property_get(&mut self, val: JsValue, prop_name_si: u32) -> Result<Option<JsValue>, String> {
if val.is_string() {
let length_si = self.kernel_core.perm_interner().intern("length").0;
if prop_name_si == length_si {
// SAFETY: val 是字符串值。
let len = unsafe { (*val.as_string_ptr()).data.encode_utf16().count() };
return Ok(Some(JsValue::int(len as i32)));
}
let proto_ptr = self.session.builtin_world().string_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
if val.is_int() || val.is_double() {
let proto_ptr = self.session.builtin_world().number_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
if val.is_bool() {
let proto_ptr = self.session.builtin_world().boolean_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
Ok(None)
}
fn private_slot(&self, obj: &JsObject, private_key: u32) -> Option<u32> {
self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key)
}
/// 沿实例→proto 链查找私有方法/访问器槽(存于类的 home=proto)。字段槽不跨原型
/// (PrivateFieldFind 只查 own)——链上遇到字段槽(无 hole meta)即拒绝,防
/// `Object.create(instance)` 穿透读取实例字段。
fn find_private_method_slot(&self, obj: &JsObject, private_key: u32) -> Option<*mut JsObject> {
let mut proto = obj.proto();
let mut depth = 0usize;
while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH {
depth += 1;
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
if let Some(pos) = self.private_slot(proto_obj, private_key) {
// 方法槽有 hole 标记(set_private_method_meta);字段槽(存实例)沿链出现
// 说明接收者并非本类实例 → 拒绝穿透。
if proto_obj.prop_meta_at(pos).is_some_and(|m| m.is_hole()) {
return Some(proto_obj as *const JsObject as *mut JsObject);
}
return None;
}
proto = proto_obj.proto();
}
None
}
/// 私有方法/访问器/静态字段访问的 brand 检查:接收者 own 的 brand 槽(构造时写入
/// 的类 brand 对象)必须与当前类 brand 对象同一。类每次求值创建新 brand,同字节码
/// 多次实例化(eval/factory)可借此区分。
fn private_brand_check(&mut self, obj: &JsObject, brand_reg: usize, brand_key: u32) -> Result<(), String> {
let Some(pos) = self.private_slot(obj, brand_key) else {
return self.raise_type_error("private field brand check failed");
};
let inst_brand = obj.get_prop_at(pos);
if !coercion::strict_equality(inst_brand, self.regs[brand_reg]) {
return self.raise_type_error("private field brand check failed");
}
Ok(())
}
fn dispatch_get_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("GET_PRIVATE rd={} a={} b={}", rd, a, b);
let obj_val = self.regs[a];
let Some(obj_ptr) = self.checked_object_ptr(obj_val, "private field access on non-object")? else {
return Ok(());
};
let private_key = self.private_key_from_reg(b);
let brand_reg = self.bytecode[self.pc] as usize;
let brand_key = make_private_name_id(self.bytecode[self.pc + 1]);
self.pc += 2;
let obj = unsafe { &*obj_ptr };
// 字段路径:own 有该私有槽(字段存实例 own)→ 直接读,不跨原型、不做 brand 值比较
// (PrivateFieldFind 只查 own;接收者带槽即构造器产物)。
if let Some(pos) = self.private_slot(obj, private_key) {
if let Some(meta) = obj.prop_meta_at(pos) {
if meta.is_accessor {
if meta.get.is_undefined() {
return self.raise_type_error("private field has no getter");
}
let getter = meta.get;
self.push_bytecode_getter_frame(getter, obj_val, rd as u8)?;
self.accessor_frame_target_reg.take();
return Ok(());
}
}
self.regs[rd] = obj.get_prop_at(pos);
return Ok(());
}
// 方法路径:own 无槽(私有方法/访问器存于 home=proto)→ 沿链找方法槽 + brand 检查。
let Some(home_ptr) = self.find_private_method_slot(obj, private_key) else {
return self.raise_type_error("private field brand check failed");
};
if brand_reg != 0 {
self.private_brand_check(obj, brand_reg, brand_key)?;
}
let home = unsafe { &*home_ptr };
let pos = self.private_slot(home, private_key).expect("slot just found");
if let Some(meta) = home.prop_meta_at(pos) {
if meta.is_accessor {
if meta.get.is_undefined() {
return self.raise_type_error("private field has no getter");
}
let getter = meta.get;
self.push_bytecode_getter_frame(getter, obj_val, rd as u8)?;
// 与 dispatch_get_prop 一致:getter 帧入栈后消费 accessor_frame_target_reg,
// 避免残留状态污染后续属性访问(getter 返回经 continuation 写 rd)。
self.accessor_frame_target_reg.take();
return Ok(());
}
}
self.regs[rd] = home.get_prop_at(pos);
Ok(())
}
fn dispatch_set_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_PRIVATE rd={} a={} b={}", rd, a, b);
let obj_val = self.regs[rd];
let Some(obj_ptr) = self.checked_object_ptr(obj_val, "private field assignment on non-object")? else {
return Ok(());
};
let private_key = self.private_key_from_reg(b);
let brand_reg = self.bytecode[self.pc] as usize;
let brand_key = make_private_name_id(self.bytecode[self.pc + 1]);
self.pc += 2;
let obj = unsafe { &*obj_ptr };
// 字段路径:own 有槽(字段存实例 own)→ 直接写,不跨原型、不做 brand 值比较。
if let Some(pos) = self.private_slot(obj, private_key) {
if let Some(meta) = obj.prop_meta_at(pos) {
if meta.is_accessor {
if meta.set.is_undefined() {
return self.raise_type_error("private field has no setter");
}
let setter = meta.set;
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
return self.call_or_push_setter(setter, obj_val, value, true);
}
// 私有方法槽不可写(init 时打标记)。
if meta.is_hole() {
return self
.raise_type_error("Cannot write private member to an object whose class did not declare it");
}
}
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
let obj = unsafe { &mut *obj_ptr };
obj.set_prop_shape(pos, value);
return Ok(());
}
// 方法路径:own 无槽(私有方法/访问器存于 home=proto)→ 沿链找方法槽 + brand 检查。
let Some(home_ptr) = self.find_private_method_slot(obj, private_key) else {
return self.raise_type_error("private field brand check failed");
};
if brand_reg != 0 {
self.private_brand_check(obj, brand_reg, brand_key)?;
}
let home = unsafe { &*home_ptr };
let pos = self.private_slot(home, private_key).expect("slot just found");
if let Some(meta) = home.prop_meta_at(pos) {
if meta.is_accessor {
if meta.set.is_undefined() {
return self.raise_type_error("private field has no setter");
}
let setter = meta.set;
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
return self.call_or_push_setter(setter, obj_val, value, true);
}
// 私有方法槽不可写(init 时打标记)。
if meta.is_hole() {
return self
.raise_type_error("Cannot write private member to an object whose class did not declare it");
}
}
let value = self.promote_if_needed_for_write_ptr(home_ptr, self.regs[a]);
let home = unsafe { &mut *home_ptr };
home.set_prop_shape(pos, value);
Ok(())
}
fn dispatch_init_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("INIT_PRIVATE rd={} a={} b={}", rd, a, b);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "private field initialization on non-object")?
else {
return Ok(());
};
let private_key = self.private_key_from_reg(b);
let is_method = self.bytecode[self.pc] != 0;
self.pc += 1;
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
let obj = unsafe { &mut *obj_ptr };
if self
.kernel_core
.shape_forge()
.lookup_position(obj.shape_id(), private_key)
.is_some()
{
// 私有元素重复初始化(构造器返回外部对象、同一对象再次初始化):
// 规范 PrivateFieldAdd 对已存在的私有名抛 TypeError。
return self.raise_type_error("Cannot initialize private element twice");
}
self.set_or_create_prop_value(obj, private_key, value);
if is_method {
let pos = self
.kernel_core
.shape_forge()
.lookup_position(obj.shape_id(), private_key)
.expect("private slot just created");
obj.set_private_method_meta(pos);
}
Ok(())
}
fn dispatch_private_brand_in(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("PRIVATE_BRAND_IN rd={} a={} b={}", rd, a, b);
let obj_val = self.regs[a];
if !obj_val.is_object() {
// `#x in rval`:rval 非对象抛 TypeError(规范 PrivateFieldIn 步骤 4)。
return self.raise_type_error("private field `in` requires an object on the right-hand side");
}
let obj = unsafe { &*obj_val.as_js_object_ptr() };
let private_key = self.private_key_from_reg(b);
let brand_reg = self.bytecode[self.pc] as usize;
let brand_key = make_private_name_id(self.bytecode[self.pc + 1]);
self.pc += 2;
// 只查 own 私有元素(PrivateFieldIn 不跨原型链):own 有该私有槽(字段)→ true;
// 否则接收者 own 有当前类 brand 槽(方法/访问器,槽在 home)→ true;否则 false。
// brand_key 是类级私有名 id(每类独立),own 存在该槽即本类实例——存在性即可
// 判定,不做 cell 值比较(规避 upvalue cell 生命周期缺陷)。
if self.private_slot(obj, private_key).is_some() {
self.regs[rd] = JsValue::bool(true);
return Ok(());
}
let has_brand = brand_reg != 0 && self.private_slot(obj, brand_key).is_some();
self.regs[rd] = JsValue::bool(has_brand);
Ok(())
}
fn dispatch_ic_get_prop(&mut self, a: usize, b: usize) -> Result<(), String> {
let val = self.regs[a];
let obj_ptr = val.as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() {
let prop_name_si = self.property_key_si(self.regs[b]);
if let Some(resolved) = self.primitive_property_get(val, prop_name_si)? {
self.regs[a] = resolved;
self.pc += 3;
return Ok(());
}
self.raise_type_error("IC_GET_PROP on non-object")?;
return Ok(());
}
let addr = obj_ptr as usize;
if addr < 0x10000 || addr % std::mem::align_of::<JsObject>() != 0 {
self.raise_type_error("IC_GET_PROP on non-object")?;
return Ok(());
}
let obj = unsafe { &*obj_ptr };
let prop_name_si = self.property_key_si(self.regs[b]);
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc);
if obj.has_prop_meta() {
let val = self.ordinary_get_with_target(obj, prop_name_si, val, a as u8)?;
if self.accessor_frame_target_reg.take().is_none() {
self.regs[a] = val;
}
return Ok(());
}
if let Some(value) = ic_get_hit(obj, cached_shape_id, cached_slot, cached_depth) {
self.regs[a] = value;
self.profiling.record_ic_hit();
ic_trace!("IC_GET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth);
} else if let Some(template) = self.kernel_core.prop_forge().get_template(obj.shape_id()) {
self.profiling.record_ic_miss();
prop_cache_miss();
if template.prop_name == prop_name_si {
if template.position < obj.prop_vec_len() as u32 {
ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), template.position, 0);
ic_debug!(
"IC_GET propforge hit shape={} prop={} slot={}",
obj.shape_id(),
prop_name_si,
template.position
);
self.regs[a] = obj.get_prop_shape(template.position);
} else {
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
}
} else {
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
}
} else {
prop_cache_miss();
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
// 先试自身属性(快路径,depth=0)。
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
if !obj.is_accessor_meta(pos) {
ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0);
ic_debug!("IC_GET write-back own shape={} slot={}", obj.shape_id(), pos);
}
} else {
// 沿原型链查找真正拥有该属性的对象。
let mut cursor = obj.proto().as_js_object_ptr();
let mut depth = 1u8;
while !cursor.is_null() {
let co_ref = unsafe { &*cursor };
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(co_ref.shape_id(), prop_name_si) {
if !co_ref.is_accessor_meta(pos) {
ic_helper::write_ic_back(&mut self.bytecode, self.pc, co_ref.shape_id(), pos, depth);
ic_debug!(
"IC_GET write-back proto shape={} slot={} depth={}",
co_ref.shape_id(),
pos,
depth
);
}
break;
}
if !co_ref.proto().is_object() {
break;
}
cursor = co_ref.proto().as_js_object_ptr();
depth += 1;
}
}
self.regs[a] = resolved;
}
Ok(())
}
fn dispatch_ic_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
return Ok(());
};
let prop_name_si = self.property_key_si(self.regs[b]);
if self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") {
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
self.pc += 3;
return Ok(());
}
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
self.pc += 3;
return Ok(());
}
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_entry(&self.bytecode, &mut self.pc);
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
if obj.has_prop_meta() {
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
return Ok(());
}
if ic_set_hit(obj, cached_shape_id, cached_slot, cached_depth, value) {
self.profiling.record_ic_hit();
ic_trace!("IC_SET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth);
} else if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
self.profiling.record_ic_miss();
prop_cache_miss();
obj.set_prop_shape(pos, value);
ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0);
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
} else {
self.profiling.record_ic_miss();
prop_cache_miss();
let old_shape = obj.shape_id();
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
if old_shape != obj.shape_id() {
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos, 0);
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
self.kernel_core.prop_forge().upsert(
obj.shape_id(),
PropTemplate {
shape_id: obj.shape_id(),
prop_name: prop_name_si,
position: pos,
generation: obj.generation(),
},
);
}
}
}
Ok(())
}
fn dispatch_get_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("GET_PROP rd={} a={} b={}", rd, a, b);
let prop_name_si = self.property_key_si(self.regs[b]);
if let Some(value) = self.primitive_property_get(self.regs[rd], prop_name_si)? {
self.regs[a] = value;
return Ok(());
}
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "GET_PROP on non-object")? else {
return Ok(());
};
let obj = unsafe { &*obj_ptr };
let val = self.ordinary_get_with_target(obj, prop_name_si, self.regs[rd], a as u8)?;
if self.accessor_frame_target_reg.take().is_none() {
self.regs[a] = val;
}
Ok(())
}
fn dispatch_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_PROP rd={} a={} b={}", rd, a, b);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
return Ok(());
};
let prop_name_si = self.property_key_si(self.regs[b]);
if self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") {
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
return Ok(());
}
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
return Ok(());
}
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
let obj = unsafe { &mut *obj_ptr };
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd])?;
Ok(())
}
fn dispatch_get_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("GET_PROP_DYNAMIC rd={} a={} b={}", rd, a, b);
let prop_name_si = self.property_key_si(self.regs[a]);
if let Some(value) = self.primitive_property_get(self.regs[rd], prop_name_si)? {
self.regs[b] = value;
return Ok(());
}
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "GET_PROP_DYNAMIC on non-object")? else {
return Ok(());
};
let obj = unsafe { &*obj_ptr };
let val = self.ordinary_get_with_target(obj, prop_name_si, self.regs[rd], b as u8)?;
if self.accessor_frame_target_reg.take().is_none() {
self.regs[b] = val;
}
Ok(())
}
fn dispatch_set_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_PROP_DYNAMIC rd={} a={} b={}", rd, a, b);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
return Ok(());
};
let prop_name_si = self.property_key_si(self.regs[a]);
if self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") {
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
return Ok(());
}
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
return Ok(());
}
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
let obj = unsafe { &mut *obj_ptr };
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd])?;
Ok(())
}
fn dispatch_set_elem(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_ELEM rd={} a={} b={}", rd, a, b);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
return Ok(());
};
let idx = if self.regs[a].is_int() {
self.regs[a].as_int().max(0) as u32
} else if self.regs[a].is_double() {
self.regs[a].as_double().max(0.0) as u32
} else {
0
};
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
let obj = unsafe { &mut *obj_ptr };
obj.set_prop_at(idx, value);
Ok(())
}
}
impl Vm {
pub(crate) fn dispatch_delete_prop_static(&mut self, rd: usize) -> Result<bool, String> {
vm_trace!("DELETE_PROP_STATIC rd={}", rd);
let prop_idx = self.bytecode[self.pc] as usize;
self.pc += 1;
let key_val = self.immutables().get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
let prop_name_si = self.property_key_si(key_val);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
self.regs[rd] = JsValue::bool(true);
return Ok(false);
};
let obj = unsafe { &mut *obj_ptr };
// 统一走共享删除逻辑(与 Reflect.deleteProperty 一致);不可配置返回 false。
let deleted = oxide_builtins::object::delete_own_property(self, obj, prop_name_si);
self.regs[rd] = JsValue::bool(deleted);
Ok(false)
}
pub(crate) fn dispatch_delete_prop_dynamic(&mut self, rd: usize, b: usize) -> Result<bool, String> {
vm_trace!("DELETE_PROP_DYNAMIC rd={}", rd);
let prop_name_si = self.property_key_si(self.regs[b]);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
self.regs[rd] = JsValue::bool(true);
return Ok(false);
};
let obj = unsafe { &mut *obj_ptr };
// 统一走共享删除逻辑(与 Reflect.deleteProperty 一致);不可配置返回 false。
let deleted = oxide_builtins::object::delete_own_property(self, obj, prop_name_si);
self.regs[rd] = JsValue::bool(deleted);
Ok(false)
}
}