diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index f76fdbd4..a50e8a41 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -15,12 +15,14 @@ impl Emitter { ) -> Result { 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) } diff --git a/crates/oxide_emit/src/shared/class_key.rs b/crates/oxide_emit/src/shared/class_key.rs index c842f2e2..3645eb12 100644 --- a/crates/oxide_emit/src/shared/class_key.rs +++ b/crates/oxide_emit/src/shared/class_key.rs @@ -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> { diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index a775df75..c0e84b58 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -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]) diff --git a/crates/oxide_regalloc/src/rewrite.rs b/crates/oxide_regalloc/src/rewrite.rs index 7b325bc4..302ecf20 100644 --- a/crates/oxide_regalloc/src/rewrite.rs +++ b/crates/oxide_regalloc/src/rewrite.rs @@ -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 { diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 36e5fb9b..fdf67079 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -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 { - 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(()) } diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index 30c73cab..b465d923 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -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();