fix: 私有字段 brand 检查 own 槽(字段不跨原型,规避 cell 值缺陷)
This commit is contained in:
@@ -15,12 +15,14 @@ impl Emitter {
|
||||
) -> Result<u32, String> {
|
||||
let obj_reg = self.emit_expression(&pin.right, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(pin.left.name.as_str(), ctx)?;
|
||||
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, pin.left.name.as_str(), ctx)?;
|
||||
let result_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(
|
||||
OpCode::PRIVATE_BRAND_IN,
|
||||
ctx.inst(Inst::private_brand_in(
|
||||
Operand::Reg(result_reg),
|
||||
Operand::Reg(obj_reg),
|
||||
Operand::Reg(key_reg),
|
||||
brand_reg,
|
||||
brand_id,
|
||||
));
|
||||
Ok(result_reg)
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ impl Emitter {
|
||||
/// 私有成员访问的 brand 编码:返回 `(brand_reg, brand_id)`,供 GET_PRIVATE/SET_PRIVATE
|
||||
/// dispatch 做 brand 检查。
|
||||
///
|
||||
/// instance 字段走 PrivateFieldFind 原型链查找(允许 `Object.create` 链穿透),
|
||||
/// 不检查 brand(返回 `(0,0)`);方法/访问器/静态字段须验证接收者属于当前类
|
||||
/// (brand 对象同一性)。brand 对象由方法函数捕获的 `@@class_brand` upvalue 提供;
|
||||
/// 嵌套函数未捕获该 upvalue 时跳过检查(保持语法合法,brand 语义受限)。
|
||||
/// instance 字段:dispatch 靠 own 槽存在性 + 不跨原型判定(PrivateFieldFind 只查 own),
|
||||
/// 不需要 brand 值比较(返回 `(0,0)`);方法/访问器/静态字段须验证接收者属于当前类
|
||||
/// (brand 对象同一性),返回 brand 编码。brand 对象由方法函数捕获的 `@@class_brand`
|
||||
/// upvalue 提供;嵌套函数未捕获该 upvalue 时跳过检查(保持语法合法,brand 语义受限)。
|
||||
pub(crate) fn private_access_brand(
|
||||
&self, _obj_reg: u32, name: &str, ctx: &mut CompileCtx,
|
||||
) -> Result<(u32, u32), String> {
|
||||
|
||||
@@ -175,6 +175,14 @@ impl Inst {
|
||||
Self::with_ext(OpCode::GET_PRIVATE, result, obj, key, &[brand_reg, brand_id])
|
||||
}
|
||||
|
||||
/// 私有名 `in` 判定:result=结果,obj=接收者,key=私有名 id。
|
||||
/// ext = [brand_reg, brand_id]:brand_reg 为当前类 brand 对象寄存器(0 表示跳过
|
||||
/// brand 检查),brand_id 为实例 brand 槽的私有名 id。判定 own 私有槽(字段)或
|
||||
/// own brand(方法/访问器,槽在 home)——不跨原型链(PrivateFieldIn)。
|
||||
pub fn private_brand_in(result: Operand, obj: Operand, key: Operand, brand_reg: u32, brand_id: u32) -> Self {
|
||||
Self::with_ext(OpCode::PRIVATE_BRAND_IN, result, obj, key, &[brand_reg, brand_id])
|
||||
}
|
||||
|
||||
/// 私有成员写入:obj=接收者,value=新值,key=私有名 id。ext 语义同 `get_private`。
|
||||
pub fn set_private(obj: Operand, value: Operand, key: Operand, brand_reg: u32, brand_id: u32) -> Self {
|
||||
Self::with_ext(OpCode::SET_PRIVATE, obj, value, key, &[brand_reg, brand_id])
|
||||
|
||||
@@ -225,8 +225,9 @@ fn rewrite_inst(
|
||||
}
|
||||
ext
|
||||
}
|
||||
// GET_PRIVATE/SET_PRIVATE:ext[0] 是 brand 对象寄存器(0 表示跳过检查),须重映射。
|
||||
OpCode::GET_PRIVATE | OpCode::SET_PRIVATE => {
|
||||
// GET_PRIVATE/SET_PRIVATE/PRIVATE_BRAND_IN:ext[0] 是 brand 对象寄存器
|
||||
// (0 表示跳过检查),须重映射。
|
||||
OpCode::GET_PRIVATE | OpCode::SET_PRIVATE | OpCode::PRIVATE_BRAND_IN => {
|
||||
let mut ext = inst.ext.clone();
|
||||
if let Some(brand_reg) = ext.first_mut() {
|
||||
if *brand_reg != 0 {
|
||||
|
||||
@@ -72,36 +72,22 @@ impl Vm {
|
||||
self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key)
|
||||
}
|
||||
|
||||
fn resolve_private_value(&self, obj: &JsObject, private_key: u32) -> Option<JsValue> {
|
||||
if let Some(pos) = self.private_slot(obj, private_key) {
|
||||
return Some(obj.get_prop_at(pos));
|
||||
}
|
||||
/// 沿实例→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) {
|
||||
return Some(proto_obj.get_prop_at(pos));
|
||||
}
|
||||
proto = proto_obj.proto();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 沿实例→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);
|
||||
// 方法槽有 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();
|
||||
}
|
||||
@@ -133,7 +119,25 @@ impl Vm {
|
||||
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 {
|
||||
// 字段路径: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 {
|
||||
@@ -147,13 +151,10 @@ impl Vm {
|
||||
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)?;
|
||||
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(());
|
||||
}
|
||||
}
|
||||
@@ -172,7 +173,30 @@ impl Vm {
|
||||
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 {
|
||||
// 字段路径: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 {
|
||||
@@ -243,7 +267,19 @@ impl Vm {
|
||||
}
|
||||
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
||||
let private_key = self.private_key_from_reg(b);
|
||||
self.regs[rd] = JsValue::bool(self.resolve_private_value(obj, private_key).is_some());
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -353,6 +353,50 @@ fn this_survives_native_call_inside_method() {
|
||||
assert_eq!(result.as_int(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_field_access_does_not_pierce_prototype() {
|
||||
// P4:Object.create(instance) 的对象访问 #x 抛 TypeError(字段不跨原型链)。
|
||||
let mut vm = Vm::new();
|
||||
let err = eval(&mut vm, "class C { #x = 1; m() { return this.#x; } } Object.create(new C()).m()").unwrap_err();
|
||||
assert!(err.contains("TypeError"), "expected TypeError, got: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_brand_in_does_not_pierce_prototype() {
|
||||
// P5:#x in Object.create(instance) 为 false(PrivateFieldIn 只查 own)。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class C { #x = 1; check(o) { return #x in o; } } var inst = new C(); var o = Object.create(inst); new C().check(o) === false && new C().check(inst) === true",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_method_in_requires_own_brand() {
|
||||
// 方法场景:#m in Object.create(instance) 为 false(无 own brand)。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class C { #m(){ return 1; } check(o) { return #m in o; } } var inst = new C(); new C().check(inst) === true && new C().check(Object.create(inst)) === false",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_field_read_write_after_two_statements() {
|
||||
// 回归:字段 set/get 跨语句边界正常(不依赖 brand cell 值比较)。
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class C { #x = 0; set(v){ this.#x = v; } get(){ return this.#x; } } var c = new C(); c.set(4); c.get()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result.as_int(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn computed_field_key_evaluated_once_at_class_definition() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
Reference in New Issue
Block a user