feat: class 私有成员语义(字段/方法/访问器 + brand 检查 + 契约修复)

This commit is contained in:
2026-08-09 11:08:36 +08:00
parent 088b3bc976
commit 6c79dbb21c
41 changed files with 573 additions and 216 deletions
+5 -1
View File
@@ -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,
),
],
);
+6 -3
View File
@@ -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);
}
+120 -21
View File
@@ -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);
+4 -2
View File
@@ -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
View File
@@ -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,
},
}
}
}
+23 -12
View File
@@ -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() {
+2 -2
View File
@@ -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);