feat: 支持 class 私有字段与品牌检查
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
||||
use oxide_kernel::string_forge::StringForge;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::private_key::is_private_name_key;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::coercion;
|
||||
@@ -19,7 +20,7 @@ pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u32)> {
|
||||
}
|
||||
if let Some(shape) = vm.kernel_core().shape_forge().get_shape(id) {
|
||||
cursor = shape.parent;
|
||||
if shape.property_name != u32::MAX {
|
||||
if shape.property_name != u32::MAX && !is_private_name_key(shape.property_name) {
|
||||
shape_ids.push(id);
|
||||
}
|
||||
} else {
|
||||
@@ -28,7 +29,7 @@ pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u32)> {
|
||||
}
|
||||
for id in shape_ids.iter().rev() {
|
||||
if let Some(shape) = vm.kernel_core().shape_forge().get_shape(*id) {
|
||||
if shape.property_name != 0 {
|
||||
if shape.property_name != 0 && !is_private_name_key(shape.property_name) {
|
||||
keys.push((shape.property_name, pos));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use oxide_compiler::opcode::OpCode;
|
||||
use oxide_kernel::prop_forge::PropTemplate;
|
||||
use oxide_kernel::{ic_debug, ic_trace};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::private_key::make_private_name_id;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::vm::Vm;
|
||||
@@ -38,10 +39,101 @@ impl Vm {
|
||||
OpCode::GET_PROP_DYNAMIC => self.dispatch_get_prop_dynamic(rd, a, b),
|
||||
OpCode::SET_PROP_DYNAMIC => self.dispatch_set_prop_dynamic(rd, a, b),
|
||||
OpCode::SET_ELEM => self.dispatch_set_elem(rd, a, b),
|
||||
OpCode::GET_PRIVATE => self.dispatch_get_private(rd, a, b),
|
||||
OpCode::SET_PRIVATE => self.dispatch_set_private(rd, a, b),
|
||||
OpCode::INIT_PRIVATE => self.dispatch_init_private(rd, a, b),
|
||||
OpCode::PRIVATE_BRAND_IN => self.dispatch_private_brand_in(rd, a, b),
|
||||
_ => unreachable!("non-property opcode passed to dispatch_property_op"),
|
||||
}
|
||||
}
|
||||
|
||||
fn private_key_from_reg(&self, reg: usize) -> u32 {
|
||||
let value = self.regs[reg];
|
||||
let local_id = if value.is_int() {
|
||||
value.as_int().max(0) as u32
|
||||
} else if value.is_double() {
|
||||
value.as_double().max(0.0) as u32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
make_private_name_id(local_id)
|
||||
}
|
||||
|
||||
fn private_slot(&self, obj: &JsObject, private_key: u32) -> Option<u32> {
|
||||
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));
|
||||
}
|
||||
let mut proto = obj.proto();
|
||||
while proto.is_object() {
|
||||
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
|
||||
}
|
||||
|
||||
fn dispatch_get_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
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 {
|
||||
return self.raise_type_error("private field brand check failed");
|
||||
};
|
||||
self.regs[rd] = value;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_set_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "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 {
|
||||
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_at(pos, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_init_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "private field initialization on non-object")?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let private_key = self.private_key_from_reg(b);
|
||||
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_at(pos, value);
|
||||
} else {
|
||||
self.set_or_create_prop_value(obj, private_key, value);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_private_brand_in(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let obj_val = self.regs[a];
|
||||
if !obj_val.is_object() {
|
||||
self.regs[rd] = JsValue::bool(false);
|
||||
return Ok(());
|
||||
}
|
||||
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());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_ic_get_prop(&mut self, a: usize, b: usize) -> Result<(), String> {
|
||||
let val = self.regs[a];
|
||||
let obj_ptr = val.as_object_ptr() as *mut JsObject;
|
||||
|
||||
@@ -913,7 +913,11 @@ impl Vm {
|
||||
| OpCode::SET_PROP
|
||||
| OpCode::GET_PROP_DYNAMIC
|
||||
| OpCode::SET_PROP_DYNAMIC
|
||||
| OpCode::SET_ELEM => {
|
||||
| OpCode::SET_ELEM
|
||||
| OpCode::GET_PRIVATE
|
||||
| OpCode::SET_PRIVATE
|
||||
| OpCode::INIT_PRIVATE
|
||||
| OpCode::PRIVATE_BRAND_IN => {
|
||||
self.dispatch_property_op(op, rd, a, b)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::native::{NativeFn, NativeResult};
|
||||
use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm};
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::private_key::is_private_name_key;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
impl Vm {
|
||||
@@ -273,7 +274,10 @@ impl Vm {
|
||||
break;
|
||||
}
|
||||
if let Some(shape) = self.kernel_core.shape_forge().get_shape(id) {
|
||||
if shape.property_name != u32::MAX && seen.insert(shape.property_name) {
|
||||
if shape.property_name != u32::MAX
|
||||
&& !is_private_name_key(shape.property_name)
|
||||
&& seen.insert(shape.property_name)
|
||||
{
|
||||
let enumerable = self
|
||||
.kernel_core
|
||||
.shape_forge()
|
||||
@@ -434,7 +438,7 @@ impl Vm {
|
||||
let Some(shape) = self.kernel_core.shape_forge().get_shape(shape_id) else {
|
||||
break;
|
||||
};
|
||||
if shape.property_name != u32::MAX {
|
||||
if shape.property_name != u32::MAX && !is_private_name_key(shape.property_name) {
|
||||
if let Some(name) = self.kernel_core.string_forge().lookup(shape.property_name) {
|
||||
if !excluded.contains(name.as_str()) {
|
||||
if let Some(pos) = self
|
||||
|
||||
@@ -243,3 +243,101 @@ fn computed_static_class_field_key_is_supported() {
|
||||
let result = eval(&mut vm, "var k = 'x'; class C { static [k] = 7; } C.x").unwrap();
|
||||
assert_eq!(result.as_int(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_class_field_read_returns_value() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "class C { #x = 1; get(){ return this.#x; } } new C().get()").unwrap();
|
||||
assert_eq!(result.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_class_field_write_round_trips() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class C { #x; 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 private_class_method_call_uses_receiver() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "class C { #m(){ return 4; } get(){ return this.#m(); } } new C().get()").unwrap();
|
||||
assert_eq!(result.as_int(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_class_brand_in_checks_presence() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class C { #x = 1; has(o){ return #x in o; } } var c = new C(); c.has(c) && !c.has({})",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.is_bool() && result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_class_missing_brand_throws_type_error() {
|
||||
let mut vm = Vm::new();
|
||||
let err = eval(&mut vm, "class C { #x = 1; get(o){ return o.#x; } } new C().get({})").unwrap_err();
|
||||
assert!(err.contains("TypeError"), "expected TypeError, got: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_static_field_and_method_work_on_constructor() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class C { static #x = 2; static #m(){ return this.#x + 3; } static get(){ return this.#m(); } } C.get()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_num(result, 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_private_field_initializes_after_super() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class B { constructor(){ this.b = 3; } } class D extends B { #x = this.b; get(){ return this.#x; } } new D().get()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result.as_int(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_class_fields_are_hidden_from_reflection_and_string_access() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class C { #x = 1; y = 2; keys(){ return Object.keys(this).length + Object.getOwnPropertyNames(this).length + (this['#x'] === undefined ? 10 : 0); } } new C().keys()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_num(result, 12.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_class_fields_are_hidden_from_for_in() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class C { #x = 1; } var o = new C(); var n = 0; for (var k in o) { n = n + 1; } n",
|
||||
)
|
||||
.unwrap();
|
||||
assert_num(result, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_class_same_name_different_classes_do_not_share_brand() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"class A { #x = 1; has(o){ return #x in o; } } class B { #x = 2; } var a = new A(); a.has(a) && !a.has(new B())",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.is_bool() && result.as_bool());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user