feat: class 私有成员语义(字段/方法/访问器 + brand 检查 + 契约修复)
This commit is contained in:
@@ -31,7 +31,11 @@ pub fn bind_regexp(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
),
|
||||
("@@search", oxide_builtins::regexp::regexp_symbol_search::<crate::vm::Vm> as *const (), 1),
|
||||
("@@split", oxide_builtins::regexp::regexp_symbol_split::<crate::vm::Vm> as *const (), 2),
|
||||
("@@matchAll", oxide_builtins::regexp::regexp_symbol_match_all::<crate::vm::Vm> as *const (), 1),
|
||||
(
|
||||
"@@matchAll",
|
||||
oxide_builtins::regexp::regexp_symbol_match_all::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -171,9 +171,12 @@ impl Vm {
|
||||
|
||||
// length:实参个数,可写、不可枚举、可配置。
|
||||
let length_si = self.kernel_core.perm_interner().intern("length").0;
|
||||
if let Err(msg) =
|
||||
self.define_data_property(obj, length_si, JsValue::int(count as i32), PropAttributes::new(true, false, true))
|
||||
{
|
||||
if let Err(msg) = self.define_data_property(
|
||||
obj,
|
||||
length_si,
|
||||
JsValue::int(count as i32),
|
||||
PropAttributes::new(true, false, true),
|
||||
) {
|
||||
return self.raise_error_kind("TypeError", &msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -88,33 +89,115 @@ impl Vm {
|
||||
None
|
||||
}
|
||||
|
||||
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 Some(obj_ptr) = self.checked_object_ptr(self.regs[a], "private field access on non-object")? else {
|
||||
return Ok(());
|
||||
};
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let private_key = self.private_key_from_reg(b);
|
||||
let Some(value) = self.resolve_private_value(obj, private_key) else {
|
||||
/// 沿实例→proto 链查找私有槽:私有方法/访问器存于类的 home(proto),
|
||||
/// 私有字段存于实例自身,两者都需要原型链遍历。
|
||||
fn find_private_slot(&self, obj: &JsObject, private_key: u32) -> Option<*mut JsObject> {
|
||||
if self.private_slot(obj, private_key).is_some() {
|
||||
return Some(obj as *const JsObject as *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 self.private_slot(proto_obj, private_key).is_some() {
|
||||
return Some(proto_obj as *const JsObject as *mut JsObject);
|
||||
}
|
||||
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");
|
||||
};
|
||||
self.regs[rd] = value;
|
||||
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 };
|
||||
let Some(home_ptr) = self.find_private_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;
|
||||
let pushed = 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();
|
||||
if pushed {
|
||||
return Ok(());
|
||||
}
|
||||
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 Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "private field assignment on non-object")? else {
|
||||
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 obj = unsafe { &mut *obj_ptr };
|
||||
let Some(pos) = self.private_slot(obj, private_key) else {
|
||||
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 };
|
||||
let Some(home_ptr) = self.find_private_slot(obj, private_key) else {
|
||||
return self.raise_type_error("private field brand check failed");
|
||||
};
|
||||
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);
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -125,12 +208,28 @@ impl Vm {
|
||||
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 let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key) {
|
||||
obj.set_prop_shape(pos, value);
|
||||
} else {
|
||||
self.set_or_create_prop_value(obj, private_key, value);
|
||||
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(())
|
||||
}
|
||||
@@ -139,8 +238,8 @@ impl Vm {
|
||||
vm_trace!("PRIVATE_BRAND_IN rd={} a={} b={}", rd, a, b);
|
||||
let obj_val = self.regs[a];
|
||||
if !obj_val.is_object() {
|
||||
self.regs[rd] = JsValue::bool(false);
|
||||
return Ok(());
|
||||
// `#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);
|
||||
|
||||
@@ -86,7 +86,10 @@ pub(crate) fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32, proto_d
|
||||
if proto_depth == 0 {
|
||||
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
|
||||
let v = obj.get_prop_shape(slot_index);
|
||||
eprintln!("[DBG] ic_get_hit shape={} slot={} apc={} v={:?}", shape_id, slot_index, obj.array_prop_count, v);
|
||||
eprintln!(
|
||||
"[DBG] ic_get_hit shape={} slot={} apc={} v={:?}",
|
||||
shape_id, slot_index, obj.array_prop_count, v
|
||||
);
|
||||
return Some(v);
|
||||
}
|
||||
return None;
|
||||
@@ -127,4 +130,3 @@ pub(crate) fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, pro
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -154,18 +154,9 @@ pub struct TryHandler {
|
||||
/// 逐个恢复(`remaining_finally` 为仍需穿越的 finally 体数,进入一个递减一个)。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Completion {
|
||||
Break {
|
||||
target_pc: usize,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
Continue {
|
||||
target_pc: usize,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
Return {
|
||||
value: JsValue,
|
||||
remaining_finally: usize,
|
||||
},
|
||||
Break { target_pc: usize, remaining_finally: usize },
|
||||
Continue { target_pc: usize, remaining_finally: usize },
|
||||
Return { value: JsValue, remaining_finally: usize },
|
||||
}
|
||||
|
||||
impl Completion {
|
||||
@@ -181,9 +172,18 @@ impl Completion {
|
||||
/// 复制并改写剩余 finally 计数(进入一个 finally 后递减)。
|
||||
pub fn with_remaining(&self, remaining: usize) -> Completion {
|
||||
match *self {
|
||||
Completion::Break { target_pc, .. } => Completion::Break { target_pc, remaining_finally: remaining },
|
||||
Completion::Continue { target_pc, .. } => Completion::Continue { target_pc, remaining_finally: remaining },
|
||||
Completion::Return { value, .. } => Completion::Return { value, remaining_finally: remaining },
|
||||
Completion::Break { target_pc, .. } => Completion::Break {
|
||||
target_pc,
|
||||
remaining_finally: remaining,
|
||||
},
|
||||
Completion::Continue { target_pc, .. } => Completion::Continue {
|
||||
target_pc,
|
||||
remaining_finally: remaining,
|
||||
},
|
||||
Completion::Return { value, .. } => Completion::Return {
|
||||
value,
|
||||
remaining_finally: remaining,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::vm::{Completion, FrameContinuation, TryHandler, Vm};
|
||||
use crate::vm_trace;
|
||||
use oxide_bytecode::opcode;
|
||||
use oxide_types::object::PropAttributes;
|
||||
use oxide_types::private_key::make_private_name_id;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
impl Vm {
|
||||
@@ -15,7 +16,8 @@ impl Vm {
|
||||
let obj_ptr = callee.as_js_object_ptr();
|
||||
if !obj_ptr.is_null() {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
if obj.is_function() { if obj.is_class_constructor() {
|
||||
if obj.is_function() {
|
||||
if obj.is_class_constructor() {
|
||||
return self
|
||||
.raise_type_error("class constructor cannot be invoked without 'new'")
|
||||
.map(|_| true);
|
||||
@@ -106,7 +108,13 @@ impl Vm {
|
||||
return self.raise_type_error("DEFINE_ACCESSOR target is not object");
|
||||
}
|
||||
let key_val = self.immutables()[prop_idx];
|
||||
let prop_name_si = self.property_key_si(key_val);
|
||||
// 私有访问器:key 常量编码为 Int(私有名局部 id),映射到私有键高半区;
|
||||
// 普通访问器 key 是字符串常量,走 interner 键。
|
||||
let prop_name_si = if key_val.is_int() {
|
||||
make_private_name_id(key_val.as_int().max(0) as u32)
|
||||
} else {
|
||||
self.property_key_si(key_val)
|
||||
};
|
||||
let getter = self.regs[a];
|
||||
let setter = self.regs[b];
|
||||
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
||||
@@ -153,7 +161,10 @@ impl Vm {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
let crossed = opcode::rd(instr) as usize;
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Break { target_pc, remaining_finally: crossed }) {
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Break {
|
||||
target_pc,
|
||||
remaining_finally: crossed,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
} else {
|
||||
self.pc = target_pc;
|
||||
@@ -165,7 +176,10 @@ impl Vm {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
let crossed = opcode::rd(instr) as usize;
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Continue { target_pc, remaining_finally: crossed }) {
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Continue {
|
||||
target_pc,
|
||||
remaining_finally: crossed,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
} else {
|
||||
self.pc = target_pc;
|
||||
@@ -242,9 +256,10 @@ impl Vm {
|
||||
.iter()
|
||||
.filter(|h| h.frame_depth == self.frames.len() && h.finally_pc.is_some())
|
||||
.count();
|
||||
if let Some(finally_pc) =
|
||||
self.record_completion(Completion::Return { value: result, remaining_finally: crossed })
|
||||
{
|
||||
if let Some(finally_pc) = self.record_completion(Completion::Return {
|
||||
value: result,
|
||||
remaining_finally: crossed,
|
||||
}) {
|
||||
self.pc = finally_pc;
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -283,11 +298,7 @@ impl Vm {
|
||||
let is_derived_constructor = frame.is_derived_constructor;
|
||||
let continuation = frame.continuation;
|
||||
let callee_this = self.regs[254];
|
||||
vm_trace!(
|
||||
"RETURN frame: continuation={:?}, derived={}",
|
||||
continuation,
|
||||
is_derived_constructor
|
||||
);
|
||||
vm_trace!("RETURN frame: continuation={:?}, derived={}", continuation, is_derived_constructor);
|
||||
self.restore_frame(frame);
|
||||
if let (Some(target_reg), Some(constructed_this)) = (construct_result_reg, constructed_this) {
|
||||
if is_derived_constructor && result.is_undefined() && callee_this.is_undefined() {
|
||||
|
||||
@@ -91,7 +91,7 @@ impl Vm {
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
fn push_bytecode_getter_frame(
|
||||
pub(crate) fn push_bytecode_getter_frame(
|
||||
&mut self, getter: JsValue, receiver: JsValue, target_reg: u8,
|
||||
) -> Result<bool, String> {
|
||||
vm_trace!(
|
||||
@@ -230,7 +230,7 @@ impl Vm {
|
||||
self.ordinary_set_inner(receiver_obj, prop_name_si, promoted, receiver, use_frame_push)
|
||||
}
|
||||
|
||||
fn call_or_push_setter(
|
||||
pub(crate) fn call_or_push_setter(
|
||||
&mut self, setter: JsValue, receiver: JsValue, val: JsValue, use_frame_push: bool,
|
||||
) -> Result<(), String> {
|
||||
vm_trace!("call_or_push_setter: frame_push={}", use_frame_push);
|
||||
|
||||
Reference in New Issue
Block a user