feat: 支持 class 私有字段与品牌检查
This commit is contained in:
@@ -144,6 +144,8 @@ pub(crate) struct CompileCtx {
|
||||
pub(crate) in_instance_method: bool,
|
||||
pub(crate) in_static_method: bool,
|
||||
pub(crate) static_block_this_reg: Option<u8>,
|
||||
pub(crate) private_name_map: Vec<(String, u32)>,
|
||||
pub(crate) next_private_name_id: u32,
|
||||
pub(crate) after_super_insert: Option<Vec<opcode::Instr>>,
|
||||
pub(crate) after_super_inserted: bool,
|
||||
/// Set when alloc_reg() overflows into the reserved this/new.target range (≥254).
|
||||
@@ -208,6 +210,8 @@ impl CompileCtx {
|
||||
in_instance_method: false,
|
||||
in_static_method: false,
|
||||
static_block_this_reg: None,
|
||||
private_name_map: Vec::new(),
|
||||
next_private_name_id: 1,
|
||||
after_super_insert: None,
|
||||
after_super_inserted: false,
|
||||
reg_overflow: false,
|
||||
@@ -497,6 +501,8 @@ impl Compiler {
|
||||
// Inherit parent's builtin_reg_map so builtin identifiers (Math, Object, etc.)
|
||||
// resolve to the correct pre-allocated registers in the sub-module's register file.
|
||||
ctx.builtin_reg_map = parent_ctx.builtin_reg_map.clone();
|
||||
ctx.private_name_map = parent_ctx.private_name_map.clone();
|
||||
ctx.next_private_name_id = parent_ctx.next_private_name_id;
|
||||
|
||||
// Propagate enclosing_this_reg so nested arrow functions capture the correct `this`.
|
||||
ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg;
|
||||
|
||||
@@ -132,6 +132,14 @@ impl Compiler {
|
||||
if matches!(method.kind, MethodDefinitionKind::Constructor) {
|
||||
continue;
|
||||
}
|
||||
if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) {
|
||||
if method.r#static {
|
||||
ctx.alloc_reg();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 3;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ctx.alloc_reg(); // key_reg
|
||||
ctx.projected_pc += 1;
|
||||
ctx.alloc_reg(); // method_reg
|
||||
@@ -479,6 +487,12 @@ impl Compiler {
|
||||
ctx.restore_reg_checkpoint(checkpoint.saturating_add(1));
|
||||
}
|
||||
}
|
||||
Expression::PrivateInExpression(pin) => {
|
||||
self.count_expression(&pin.right, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2;
|
||||
}
|
||||
Expression::UnaryExpression(un) => {
|
||||
match un.operator {
|
||||
UnaryOperator::Delete => {
|
||||
@@ -516,6 +530,12 @@ impl Compiler {
|
||||
return;
|
||||
}
|
||||
match &call.callee {
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2;
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
if matches!(&member.object, Expression::Super(_)) {
|
||||
ctx.alloc_reg(); // this register
|
||||
@@ -566,6 +586,11 @@ impl Compiler {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // SET_PROP_DYNAMIC
|
||||
} else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left {
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2; // LOAD_CONST private id + SET_PRIVATE
|
||||
} else if let oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) = &assign.left {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
self.count_array_assignment(ap, ctx);
|
||||
@@ -773,6 +798,12 @@ impl Compiler {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // GET_PROP_DYNAMIC
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2;
|
||||
}
|
||||
Expression::ParenthesizedExpression(p) => {
|
||||
self.count_expression(&p.expression, ctx);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,21 @@ use crate::compiler::{
|
||||
use crate::module::Constant;
|
||||
|
||||
impl Compiler {
|
||||
fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result<u32, String> {
|
||||
ctx.private_name_map
|
||||
.iter()
|
||||
.find_map(|(n, id)| (n == name).then_some(*id))
|
||||
.ok_or_else(|| format!("private name #{name} is not defined"))
|
||||
}
|
||||
|
||||
fn emit_private_id_reg(&self, name: &str, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
let id = self.private_name_id(name, ctx)?;
|
||||
let idx = ctx.add_constant(Constant::Int(id as i32));
|
||||
let reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(reg, idx);
|
||||
Ok(reg)
|
||||
}
|
||||
|
||||
fn emit_class_key_reg(&self, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
if !computed {
|
||||
let name = self.class_property_name(key)?;
|
||||
@@ -54,6 +69,32 @@ impl Compiler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_private_field_init(
|
||||
&self, target_reg: u8, name: &str, value: Option<&Expression>, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let value_reg = if let Some(expr) = value {
|
||||
self.emit_expression(expr, ctx)?
|
||||
} else {
|
||||
self.emit_undefined(ctx)
|
||||
};
|
||||
ctx.emit(opcode::encode(OpCode::INIT_PRIVATE, target_reg, value_reg, key_reg));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_private_method_init(
|
||||
&self, target_reg: u8, method: &oxide_parser::MethodDefinition, home_reg: u8, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
let PropertyKey::PrivateIdentifier(private) = &method.key else {
|
||||
return Err("expected private method key".into());
|
||||
};
|
||||
let name = private.name.as_str();
|
||||
let key_reg = self.emit_private_id_reg(name, ctx)?;
|
||||
let method_reg = self.emit_class_method_function(method, name, home_reg, ctx, &[])?;
|
||||
ctx.emit(opcode::encode(OpCode::INIT_PRIVATE, target_reg, method_reg, key_reg));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn count_public_field_init(
|
||||
&self, key: &PropertyKey, computed: bool, value: Option<&Expression>, ctx: &mut CompileCtx,
|
||||
) {
|
||||
@@ -67,6 +108,18 @@ impl Compiler {
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
|
||||
fn count_private_field_init(&self, value: Option<&Expression>, ctx: &mut CompileCtx) {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
if let Some(expr) = value {
|
||||
self.count_expression(expr, ctx);
|
||||
} else {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
|
||||
fn static_property_name(&self, key: &PropertyKey) -> Result<String, String> {
|
||||
match key {
|
||||
PropertyKey::StaticIdentifier(ident) => Ok(ident.name.as_str().to_string()),
|
||||
@@ -368,14 +421,21 @@ impl Compiler {
|
||||
fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
let mut constructor_method = None;
|
||||
let mut instance_fields = Vec::new();
|
||||
let mut private_names = Vec::<(String, u32)>::new();
|
||||
let is_derived = class.super_class.is_some();
|
||||
|
||||
for element in &class.body.body {
|
||||
match element {
|
||||
ClassElement::MethodDefinition(method) => {
|
||||
let method = method.as_ref();
|
||||
if matches!(method.key, PropertyKey::PrivateIdentifier(_)) {
|
||||
return Err("private class elements not yet supported".into());
|
||||
if let PropertyKey::PrivateIdentifier(private) = &method.key {
|
||||
let name = private.name.as_str().to_string();
|
||||
if private_names.iter().any(|(existing, _)| existing == &name) {
|
||||
return Err(format!("duplicate private name #{name}"));
|
||||
}
|
||||
let id = ctx.next_private_name_id;
|
||||
ctx.next_private_name_id = ctx.next_private_name_id.saturating_add(1);
|
||||
private_names.push((name, id));
|
||||
}
|
||||
if method.kind == MethodDefinitionKind::Constructor {
|
||||
if constructor_method.is_some() {
|
||||
@@ -386,8 +446,14 @@ impl Compiler {
|
||||
}
|
||||
ClassElement::PropertyDefinition(prop) => {
|
||||
let prop = prop.as_ref();
|
||||
if matches!(prop.key, PropertyKey::PrivateIdentifier(_)) {
|
||||
return Err("private class elements not yet supported".into());
|
||||
if let PropertyKey::PrivateIdentifier(private) = &prop.key {
|
||||
let name = private.name.as_str().to_string();
|
||||
if private_names.iter().any(|(existing, _)| existing == &name) {
|
||||
return Err(format!("duplicate private name #{name}"));
|
||||
}
|
||||
let id = ctx.next_private_name_id;
|
||||
ctx.next_private_name_id = ctx.next_private_name_id.saturating_add(1);
|
||||
private_names.push((name, id));
|
||||
}
|
||||
if !prop.r#static {
|
||||
instance_fields.push(prop);
|
||||
@@ -409,7 +475,9 @@ impl Compiler {
|
||||
};
|
||||
|
||||
let saved_derived = ctx.in_derived_constructor;
|
||||
let saved_private_names = ctx.private_name_map.clone();
|
||||
ctx.in_derived_constructor = is_derived;
|
||||
ctx.private_name_map = private_names.clone();
|
||||
|
||||
let mut ctor_module = if let Some(method) = constructor_method {
|
||||
let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?;
|
||||
@@ -422,18 +490,36 @@ impl Compiler {
|
||||
FunctionBodyContext::ClassElement,
|
||||
Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| {
|
||||
for field in &instance_fields {
|
||||
compiler.count_public_field_init(&field.key, field.computed, field.value.as_ref(), field_ctx);
|
||||
if matches!(field.key, PropertyKey::PrivateIdentifier(_)) {
|
||||
compiler.count_private_field_init(field.value.as_ref(), field_ctx);
|
||||
} else {
|
||||
compiler.count_public_field_init(
|
||||
&field.key,
|
||||
field.computed,
|
||||
field.value.as_ref(),
|
||||
field_ctx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| -> Result<(), String> {
|
||||
for field in &instance_fields {
|
||||
compiler.emit_public_field_init(
|
||||
254,
|
||||
&field.key,
|
||||
field.computed,
|
||||
field.value.as_ref(),
|
||||
field_ctx,
|
||||
)?;
|
||||
if let PropertyKey::PrivateIdentifier(private) = &field.key {
|
||||
compiler.emit_private_field_init(
|
||||
254,
|
||||
private.name.as_str(),
|
||||
field.value.as_ref(),
|
||||
field_ctx,
|
||||
)?;
|
||||
} else {
|
||||
compiler.emit_public_field_init(
|
||||
254,
|
||||
&field.key,
|
||||
field.computed,
|
||||
field.value.as_ref(),
|
||||
field_ctx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
@@ -449,18 +535,36 @@ impl Compiler {
|
||||
FunctionBodyContext::ClassElement,
|
||||
Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| {
|
||||
for field in &instance_fields {
|
||||
compiler.count_public_field_init(&field.key, field.computed, field.value.as_ref(), field_ctx);
|
||||
if matches!(field.key, PropertyKey::PrivateIdentifier(_)) {
|
||||
compiler.count_private_field_init(field.value.as_ref(), field_ctx);
|
||||
} else {
|
||||
compiler.count_public_field_init(
|
||||
&field.key,
|
||||
field.computed,
|
||||
field.value.as_ref(),
|
||||
field_ctx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| -> Result<(), String> {
|
||||
for field in &instance_fields {
|
||||
compiler.emit_public_field_init(
|
||||
254,
|
||||
&field.key,
|
||||
field.computed,
|
||||
field.value.as_ref(),
|
||||
field_ctx,
|
||||
)?;
|
||||
if let PropertyKey::PrivateIdentifier(private) = &field.key {
|
||||
compiler.emit_private_field_init(
|
||||
254,
|
||||
private.name.as_str(),
|
||||
field.value.as_ref(),
|
||||
field_ctx,
|
||||
)?;
|
||||
} else {
|
||||
compiler.emit_public_field_init(
|
||||
254,
|
||||
&field.key,
|
||||
field.computed,
|
||||
field.value.as_ref(),
|
||||
field_ctx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
@@ -473,8 +577,19 @@ impl Compiler {
|
||||
module.bytecode.push(opcode::encode(OpCode::SUPER_CALL, 0, 0, 0));
|
||||
module.bytecode.push(0);
|
||||
let mut field_ctx = CompileCtx::new();
|
||||
field_ctx.private_name_map = private_names.clone();
|
||||
for field in &instance_fields {
|
||||
self.emit_public_field_init(254, &field.key, field.computed, field.value.as_ref(), &mut field_ctx)?;
|
||||
if let PropertyKey::PrivateIdentifier(private) = &field.key {
|
||||
self.emit_private_field_init(254, private.name.as_str(), field.value.as_ref(), &mut field_ctx)?;
|
||||
} else {
|
||||
self.emit_public_field_init(
|
||||
254,
|
||||
&field.key,
|
||||
field.computed,
|
||||
field.value.as_ref(),
|
||||
&mut field_ctx,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
module.bytecode.extend(field_ctx.bytecode);
|
||||
module.constants = field_ctx.constants;
|
||||
@@ -524,6 +639,11 @@ impl Compiler {
|
||||
if method.kind == MethodDefinitionKind::Constructor {
|
||||
continue;
|
||||
}
|
||||
if matches!(method.key, PropertyKey::PrivateIdentifier(_)) {
|
||||
let home_reg = if method.r#static { ctor_reg } else { proto_reg };
|
||||
self.emit_private_method_init(home_reg, method, home_reg, ctx)?;
|
||||
continue;
|
||||
}
|
||||
let home_reg = if method.r#static { ctor_reg } else { proto_reg };
|
||||
let key_reg = self.emit_class_key_reg(&method.key, method.computed, ctx)?;
|
||||
let method_name = if method.computed {
|
||||
@@ -564,7 +684,11 @@ impl Compiler {
|
||||
if prop.r#static {
|
||||
let saved_static_this = ctx.static_block_this_reg;
|
||||
ctx.static_block_this_reg = Some(ctor_reg);
|
||||
self.emit_public_field_init(ctor_reg, &prop.key, prop.computed, prop.value.as_ref(), ctx)?;
|
||||
if let PropertyKey::PrivateIdentifier(private) = &prop.key {
|
||||
self.emit_private_field_init(ctor_reg, private.name.as_str(), prop.value.as_ref(), ctx)?;
|
||||
} else {
|
||||
self.emit_public_field_init(ctor_reg, &prop.key, prop.computed, prop.value.as_ref(), ctx)?;
|
||||
}
|
||||
ctx.static_block_this_reg = saved_static_this;
|
||||
}
|
||||
}
|
||||
@@ -582,6 +706,7 @@ impl Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.private_name_map = saved_private_names;
|
||||
Ok(ctor_reg)
|
||||
}
|
||||
|
||||
@@ -1168,6 +1293,13 @@ impl Compiler {
|
||||
ctx.emit_load_const(r, idx);
|
||||
Ok(r)
|
||||
}
|
||||
Expression::PrivateInExpression(pin) => {
|
||||
let obj_reg = self.emit_expression(&pin.right, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(pin.left.name.as_str(), ctx)?;
|
||||
let result_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::PRIVATE_BRAND_IN, result_reg, obj_reg, key_reg));
|
||||
Ok(result_reg)
|
||||
}
|
||||
Expression::BinaryExpression(bin) => {
|
||||
let checkpoint = ctx.reg_checkpoint();
|
||||
let left = self.emit_expression(&bin.left, ctx)?;
|
||||
@@ -1362,6 +1494,13 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::GET_PROP_DYNAMIC, obj_reg, key_reg, r));
|
||||
Ok(r)
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::GET_PRIVATE, r, obj_reg, key_reg));
|
||||
Ok(r)
|
||||
}
|
||||
Expression::ObjectExpression(obj) => {
|
||||
let obj_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::NEW_OBJECT, obj_reg, 0, 0));
|
||||
@@ -1464,6 +1603,15 @@ impl Compiler {
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, obj_reg, key_reg, val_reg));
|
||||
Ok(val_reg)
|
||||
} else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left {
|
||||
if assign.operator != AssignmentOperator::Assign {
|
||||
return Err("compound assignment to private fields not supported".into());
|
||||
}
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::SET_PRIVATE, obj_reg, val_reg, key_reg));
|
||||
Ok(val_reg)
|
||||
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(id_ref) = &assign.left {
|
||||
if assign.operator != AssignmentOperator::Assign {
|
||||
if assign.operator == AssignmentOperator::Addition
|
||||
@@ -1820,6 +1968,13 @@ impl Compiler {
|
||||
return Ok(result_reg);
|
||||
}
|
||||
let (callee_reg, this_reg) = match &call.callee {
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let callee_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::GET_PRIVATE, callee_reg, obj_reg, key_reg));
|
||||
(callee_reg, obj_reg)
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
let is_super_member = matches!(&member.object, Expression::Super(_));
|
||||
let obj_reg = if is_super_member {
|
||||
|
||||
@@ -250,6 +250,10 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
|
||||
hash_expression(&cond.consequent, h);
|
||||
hash_expression(&cond.alternate, h);
|
||||
}
|
||||
Expression::PrivateInExpression(pin) => {
|
||||
pin.left.name.as_str().hash(h);
|
||||
hash_expression(&pin.right, h);
|
||||
}
|
||||
Expression::Identifier(_) => {}
|
||||
Expression::NumericLiteral(num) => {
|
||||
num.value.to_bits().hash(h);
|
||||
@@ -262,6 +266,9 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
|
||||
}
|
||||
Expression::AssignmentExpression(assign) => {
|
||||
std::mem::discriminant(&assign.operator).hash(h);
|
||||
if let Some(target) = assign.left.as_simple_assignment_target() {
|
||||
hash_simple_assignment_target(target, h);
|
||||
}
|
||||
hash_expression(&assign.right, h);
|
||||
}
|
||||
Expression::UpdateExpression(update) => {
|
||||
@@ -323,6 +330,10 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
hash_expression(&member.object, h);
|
||||
member.field.name.as_str().hash(h);
|
||||
}
|
||||
Expression::Super(_) => {}
|
||||
Expression::RegExpLiteral(lit) => {
|
||||
if let Some(raw) = &lit.raw {
|
||||
@@ -356,6 +367,10 @@ fn hash_simple_assignment_target(target: &SimpleAssignmentTarget, h: &mut rustc_
|
||||
hash_expression(&member.object, h);
|
||||
hash_expression(&member.expression, h);
|
||||
}
|
||||
SimpleAssignmentTarget::PrivateFieldExpression(member) => {
|
||||
hash_expression(&member.object, h);
|
||||
member.field.name.as_str().hash(h);
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,6 +123,10 @@ pub enum OpCode {
|
||||
// ── Parallel — placeholders (0x70-0x7F) ──
|
||||
FORK = 0x70,
|
||||
JOIN = 0x71,
|
||||
GET_PRIVATE = 0x72,
|
||||
SET_PRIVATE = 0x73,
|
||||
INIT_PRIVATE = 0x74,
|
||||
PRIVATE_BRAND_IN = 0x75,
|
||||
|
||||
// -- Bitwise (0x80-0x8F) --
|
||||
BIT_AND = 0x80,
|
||||
@@ -269,6 +273,10 @@ impl TryFrom<u8> for OpCode {
|
||||
0x66 => Ok(OpCode::PROFILE_CALL),
|
||||
0x70 => Ok(OpCode::FORK),
|
||||
0x71 => Ok(OpCode::JOIN),
|
||||
0x72 => Ok(OpCode::GET_PRIVATE),
|
||||
0x73 => Ok(OpCode::SET_PRIVATE),
|
||||
0x74 => Ok(OpCode::INIT_PRIVATE),
|
||||
0x75 => Ok(OpCode::PRIVATE_BRAND_IN),
|
||||
0x80 => Ok(OpCode::BIT_AND),
|
||||
0x81 => Ok(OpCode::BIT_OR),
|
||||
0x82 => Ok(OpCode::BIT_XOR),
|
||||
@@ -382,6 +390,10 @@ impl fmt::Display for OpCode {
|
||||
OpCode::PROFILE_CALL => "PROFILE_CALL",
|
||||
OpCode::FORK => "FORK",
|
||||
OpCode::JOIN => "JOIN",
|
||||
OpCode::GET_PRIVATE => "GET_PRIVATE",
|
||||
OpCode::SET_PRIVATE => "SET_PRIVATE",
|
||||
OpCode::INIT_PRIVATE => "INIT_PRIVATE",
|
||||
OpCode::PRIVATE_BRAND_IN => "PRIVATE_BRAND_IN",
|
||||
OpCode::BIT_AND => "BIT_AND",
|
||||
OpCode::BIT_OR => "BIT_OR",
|
||||
OpCode::BIT_XOR => "BIT_XOR",
|
||||
|
||||
@@ -297,4 +297,14 @@ mod tests {
|
||||
assert!(forge.has_property(s2, 1_000_310));
|
||||
assert!(!forge.has_property(s2, 99));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_band_keys_are_shape_properties() {
|
||||
let forge = ShapeForge::new();
|
||||
let private_key = oxide_types::private_key::make_private_name_id(12);
|
||||
let shape = forge.make_shape(EMPTY_SHAPE_ID, private_key);
|
||||
assert!(oxide_types::private_key::is_private_name_key(private_key));
|
||||
assert!(forge.has_property(shape, private_key));
|
||||
assert_eq!(forge.lookup_position(shape, private_key), Some(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,13 +367,7 @@ fn is_skipped(meta: &TestMeta) -> Option<String> {
|
||||
"well-formed-json-stringify",
|
||||
"optional-chaining",
|
||||
"symbols-as-weakmap-keys",
|
||||
"class-fields-public",
|
||||
"class-fields-private",
|
||||
"class-static-block",
|
||||
"class-methods-private",
|
||||
"class-accessors-private",
|
||||
"class-fields-public-static",
|
||||
"class-fields-private-static",
|
||||
];
|
||||
|
||||
for feat in &meta.features {
|
||||
@@ -560,10 +554,7 @@ fn process_path(
|
||||
if path_str.contains("built-ins/Promise/") {
|
||||
return TestResult::skip(path.to_path_buf(), "Promise tests excluded".into());
|
||||
}
|
||||
if path_str.contains("/private-")
|
||||
|| path_str.contains("/class-fields/")
|
||||
|| path_str.contains("/class-static-block/")
|
||||
|| path_str.contains("/dstr/")
|
||||
if path_str.contains("/dstr/")
|
||||
|| path_str.contains("/eval/")
|
||||
|| path_str.contains("/function-ctor/")
|
||||
|| path_str.contains("/realm/")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
pub mod error;
|
||||
pub mod mem;
|
||||
pub mod object;
|
||||
pub mod private_key;
|
||||
pub mod shape;
|
||||
pub mod value;
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
pub const PRIVATE_NAME_BASE: u32 = 0x8000_0000;
|
||||
|
||||
#[inline]
|
||||
pub const fn is_private_name_key(key: u32) -> bool {
|
||||
key >= PRIVATE_NAME_BASE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn make_private_name_id(local_id: u32) -> u32 {
|
||||
PRIVATE_NAME_BASE | (local_id & !PRIVATE_NAME_BASE)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn private_name_ids_use_high_band() {
|
||||
let id = make_private_name_id(7);
|
||||
assert!(is_private_name_key(id));
|
||||
assert_ne!(id, u32::MAX);
|
||||
assert!(!is_private_name_key(7));
|
||||
}
|
||||
}
|
||||
@@ -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