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

This commit is contained in:
爱特每羊队
2026-08-09 11:08:36 +08:00
parent e1f0d578fa
commit 915dedb151
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);
@@ -138,11 +138,7 @@ fn typed_array_to_string_is_identifiable() {
#[test]
fn typed_array_element_write_roundtrips() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var ta = new Int32Array(2); ta[0] = 42; ta[1] = ta[0] * 2; ta[1] === 84",
)
.unwrap();
let result = eval(&mut vm, "var ta = new Int32Array(2); ta[0] = 42; ta[1] = ta[0] * 2; ta[1] === 84").unwrap();
assert!(result.as_bool());
}
@@ -156,11 +152,7 @@ fn typed_array_element_write_clamps_int8() {
#[test]
fn typed_array_out_of_bounds_write_is_ignored() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var ta = new Int8Array(1); ta[5] = 1; ta.length === 1 && ta[5] === undefined",
)
.unwrap();
let result = eval(&mut vm, "var ta = new Int8Array(1); ta[5] = 1; ta.length === 1 && ta[5] === undefined").unwrap();
assert!(result.as_bool());
}
+5 -3
View File
@@ -363,9 +363,11 @@ fn computed_field_key_evaluated_once_at_class_definition() {
#[test]
fn computed_field_key_once_but_value_per_instance() {
let mut vm = Vm::new();
let result =
eval(&mut vm, "var n=0; class C { [++n] = ++n; } var a=new C(); var b=new C(); n*100 + a[1]*10 + b[1]")
.unwrap();
let result = eval(
&mut vm,
"var n=0; class C { [++n] = ++n; } var a=new C(); var b=new C(); n*100 + a[1]*10 + b[1]",
)
.unwrap();
assert_num(result, 323.0);
}
@@ -36,7 +36,11 @@ fn delete_non_configurable_returns_false_in_sloppy_mode() {
"delete obj.x",
])
.unwrap();
assert!(r.is_bool() && !r.as_bool(), "delete non-configurable in sloppy should return false, got {:?}", r);
assert!(
r.is_bool() && !r.as_bool(),
"delete non-configurable in sloppy should return false, got {:?}",
r
);
}
// ── 删除不存在的属性返回 true ──
@@ -27,17 +27,26 @@ fn eval(source: &str) -> String {
#[test]
fn break_runs_finally_and_exits_loop() {
assert_eq!(eval("var fin = 0; var i = 0; for (; i < 5; ) { try { break; } finally { fin = 1; } i = i + 1; } fin * 10 + i"), "10");
assert_eq!(
eval("var fin = 0; var i = 0; for (; i < 5; ) { try { break; } finally { fin = 1; } i = i + 1; } fin * 10 + i"),
"10"
);
}
#[test]
fn continue_runs_finally_every_iteration() {
assert_eq!(eval("var fin = 0; for (var i = 0; i < 5; i++) { try { continue; } finally { fin = fin + 1; } } fin"), "5");
assert_eq!(
eval("var fin = 0; for (var i = 0; i < 5; i++) { try { continue; } finally { fin = fin + 1; } } fin"),
"5"
);
}
#[test]
fn return_runs_finally() {
assert_eq!(eval("var fin = 0; function f() { try { return 1; } finally { fin = 1; } } var r = f(); r * 10 + fin"), "11");
assert_eq!(
eval("var fin = 0; function f() { try { return 1; } finally { fin = 1; } } var r = f(); r * 10 + fin"),
"11"
);
}
#[test]
@@ -47,7 +56,10 @@ fn break_inside_finally_breaks_loop() {
#[test]
fn throw_still_runs_finally() {
assert_eq!(eval("var fin = 0; function g() { try { throw 1; } finally { fin = 1; } } try { g(); } catch (e) {} fin"), "1");
assert_eq!(
eval("var fin = 0; function g() { try { throw 1; } finally { fin = 1; } } try { g(); } catch (e) {} fin"),
"1"
);
}
#[test]
@@ -101,12 +113,20 @@ fn break_in_finally_overrides_exception() {
#[test]
fn continue_in_finally_overrides_break() {
assert_eq!(eval("var log = 0; for (var i = 0; i < 3; i++) { try { break; } finally { log = log * 10 + i; continue; } } log"), "12");
assert_eq!(
eval(
"var log = 0; for (var i = 0; i < 3; i++) { try { break; } finally { log = log * 10 + i; continue; } } log"
),
"12"
);
}
#[test]
fn throw_in_finally_overrides_previous_exception() {
assert_eq!(eval("var fin = 0; try { try { throw 1; } finally { throw 2; } } catch (e) { fin = e; } fin"), "2");
assert_eq!(
eval("var fin = 0; try { try { throw 1; } finally { throw 2; } } catch (e) { fin = e; } fin"),
"2"
);
}
#[test]
@@ -143,7 +163,10 @@ fn continue_with_finally_conditionally() {
#[test]
fn normal_finally_path_still_works() {
assert_eq!(eval("var s = 0; for (var i = 0; i < 3; i++) { try { s = s + i; } finally { s = s + 10; } } s"), "33");
assert_eq!(
eval("var s = 0; for (var i = 0; i < 3; i++) { try { s = s + i; } finally { s = s + 10; } } s"),
"33"
);
}
#[test]
@@ -202,7 +225,10 @@ fn return_in_try_catch_value_path() {
#[test]
fn return_in_try_finally_runs_finally() {
assert_eq!(eval("var fin = 0; function f(){ try{ return 1 } finally{ fin = 1 } } f() * 10 + fin"), "11");
assert_eq!(
eval("var fin = 0; function f(){ try{ return 1 } finally{ fin = 1 } } f() * 10 + fin"),
"11"
);
}
#[test]