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
+3 -12
View File
@@ -362,10 +362,7 @@ pub fn object_is<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// # 副作用
/// - 修改 obj 的 shape 链、属性表与 generation
fn define_from_descriptor<H: VmHost>(
vm: &mut H,
obj_ptr: *mut JsObject,
key_si: u32,
desc_val: JsValue,
vm: &mut H, obj_ptr: *mut JsObject, key_si: u32, desc_val: JsValue,
) -> Result<(), String> {
if !desc_val.is_object() {
return Err("Property description must be an object".to_string());
@@ -477,9 +474,7 @@ fn define_from_descriptor<H: VmHost>(
/// # 副作用
/// - 修改 target 的 shape 链、属性表与 generation
fn define_all_from_properties<H: VmHost>(
vm: &mut H,
target_ptr: *mut JsObject,
props_val: JsValue,
vm: &mut H, target_ptr: *mut JsObject, props_val: JsValue,
) -> Result<(), String> {
if !props_val.is_object() {
return Err("Property description must be an object".to_string());
@@ -495,11 +490,7 @@ fn define_all_from_properties<H: VmHost>(
for (key_si, offset) in prop_keys {
let props = unsafe { &*props_ptr };
// 非可枚举自身属性跳过;无显式 meta 视为可枚举(普通字面量默认)。
if props
.prop_meta_at(offset)
.map(|m| !m.attributes.enumerable())
.unwrap_or(false)
{
if props.prop_meta_at(offset).map(|m| !m.attributes.enumerable()).unwrap_or(false) {
continue;
}
let desc_val = vm.ordinary_get(props, key_si, props_val)?;
+3 -7
View File
@@ -224,12 +224,9 @@ pub fn regexp_exec<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let group_count = m.captures.len();
let n = 1 + group_count;
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch().alloc(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(proto),
n,
vm.epoch().bump(),
));
let arr =
vm.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump()));
unsafe {
(*arr).set_prop_at(0, vm.new_string(&haystack[range.start..range.end]));
// 捕获组:未参与匹配的组为 undefined。
@@ -545,4 +542,3 @@ pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
vm.set_or_create_prop_value(wrapper_obj, next_si, next_fn);
NativeResult::Ok(JsValue::from_js_object(wrapper))
}
+2 -11
View File
@@ -973,10 +973,7 @@ pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
let pos = if args.len() > 1 {
let pos_val = vm.reg(args[1]);
if pos_val.is_symbol() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Cannot convert a Symbol value to a number",
));
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a number"));
}
match vm.coerce_number_bounded(pos_val) {
Ok(n) => n,
@@ -985,10 +982,7 @@ pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(
vm,
"Cannot convert argument to a number",
));
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert argument to a number"));
}
}
} else {
@@ -1264,6 +1258,3 @@ pub fn string_replace_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let result = s.replace(&pattern, &replacement);
NativeResult::Ok(vm.new_string(&result))
}
+9 -1
View File
@@ -25,7 +25,15 @@ pub(super) fn partition_blocks(f: &IRFunction) -> Vec<bool> {
// 跳转的 fallthrough 后继是块头:条件跳转否则 cond+then 融块、条件边丢失;
// 无条件 JMP 同样必须切块头——JMP 不 fallthrough,但其后继位置需成块边界,
// 否则 JMP 与后续指令融块、块尾判定错、Jump 边丢失(DCE 可达性误删目标块)。
if matches!(inst.op, OpCode::JMP | OpCode::BREAK | OpCode::CONTINUE | OpCode::JMP_IF_TRUE | OpCode::JMP_IF_FALSE | OpCode::JMP_IF_NULLISH) {
if matches!(
inst.op,
OpCode::JMP
| OpCode::BREAK
| OpCode::CONTINUE
| OpCode::JMP_IF_TRUE
| OpCode::JMP_IF_FALSE
| OpCode::JMP_IF_NULLISH
) {
heads[i + 1] = true;
}
}
+6 -7
View File
@@ -24,14 +24,13 @@ fn assign_ids(module: &mut CompiledModule, next_id: &mut u32) {
if opcode::opcode(*instr) == OpCode::CREATE_CLOSURE {
let rel = opcode::imm16(*instr) as usize;
// rel 应为 1-based 子模块下标;0 表示无引用(异常输入),按未解析处理。
let flat = if rel > 0 { module.sub_modules.get(rel - 1).map(|s| s.flat_id).unwrap_or(0) } else { 0 };
let flat = if rel > 0 {
module.sub_modules.get(rel - 1).map(|s| s.flat_id).unwrap_or(0)
} else {
0
};
let rd = opcode::rd(*instr);
*instr = opcode::encode(
OpCode::CREATE_CLOSURE,
rd,
(flat & 0xFF) as u8,
((flat >> 8) & 0xFF) as u8,
);
*instr = opcode::encode(OpCode::CREATE_CLOSURE, rd, (flat & 0xFF) as u8, ((flat >> 8) & 0xFF) as u8);
}
}
for sub in &mut module.sub_modules {
@@ -537,7 +537,10 @@ fn compile_call_spread_emits_spread_opcodes() {
"f(...args) should emit CALL_SPREAD"
);
assert!(
module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION_SPREAD),
module
.bytecode
.iter()
.any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION_SPREAD),
"new f(...args) should emit NEW_EXPRESSION_SPREAD"
);
// 无 spread 的调用不发 spread 变体
+97 -12
View File
@@ -14,7 +14,7 @@ impl Emitter {
let elements = &class.body.body;
let mut constructor_method = None;
let mut instance_field_indices = Vec::new();
let mut private_names = Vec::<(String, u32)>::new();
let mut private_names = Vec::<(String, u32, Option<MethodDefinitionKind>, bool)>::new();
let is_derived = class.super_class.is_some();
for (idx, element) in elements.iter().enumerate() {
@@ -23,12 +23,18 @@ impl Emitter {
let method = method.as_ref();
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 kind = method.kind;
match private_names.iter().find(|(n, _, _, _)| n == &name) {
// 同名 getter/setter 构成访问器对,复用同一私有名。
Some((_, _, Some(MethodDefinitionKind::Get), _)) if kind == MethodDefinitionKind::Set => {}
Some((_, _, Some(MethodDefinitionKind::Set), _)) if kind == MethodDefinitionKind::Get => {}
Some(_) => return Err(format!("duplicate private name #{name}")),
None => {
let id = ctx.scopes.next_private_name_id;
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
private_names.push((name, id, Some(kind), method.r#static));
}
}
let id = ctx.scopes.next_private_name_id;
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
private_names.push((name, id));
}
if method.kind == MethodDefinitionKind::Constructor {
if constructor_method.is_some() {
@@ -41,12 +47,12 @@ impl Emitter {
let prop = prop.as_ref();
if let PropertyKey::PrivateIdentifier(private) = &prop.key {
let name = private.name.as_str().to_string();
if private_names.iter().any(|(existing, _)| existing == &name) {
if private_names.iter().any(|(existing, _, _, _)| existing == &name) {
return Err(format!("duplicate private name #{name}"));
}
let id = ctx.scopes.next_private_name_id;
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
private_names.push((name, id));
private_names.push((name, id, None, prop.r#static));
}
if !prop.r#static {
instance_field_indices.push(idx);
@@ -85,8 +91,26 @@ impl Emitter {
let self_binding = ctor_name.as_deref().map(|name| vec![(name, ctor_reg)]).unwrap_or_default();
let saved_derived = ctx.in_derived_constructor;
let saved_private_names = ctx.scopes.private_name_map.clone();
let saved_private_kinds = ctx.scopes.private_element_kinds.clone();
let saved_brand_id = ctx.scopes.private_brand_id;
ctx.in_derived_constructor = is_derived;
ctx.scopes.private_name_map = private_names.clone();
ctx.scopes.private_name_map = private_names.iter().map(|(n, id, _, _)| (n.clone(), *id)).collect();
ctx.scopes.private_element_kinds = private_names
.iter()
.map(|(n, _, kind, is_static)| (n.clone(), *kind, *is_static))
.collect();
// 类 brand:有私有元素时分配 brand 私有名 id,并创建 brand 对象(= 类原型)。
// 构造器把 brand 槽写入实例 own;私有方法/访问器/静态字段访问(GET/SET)据
// brand 对象同一性做检查;instance 字段走 PrivateFieldFind 原型链查找,不检查。
let private_brand_id = if !private_names.is_empty() {
let id = ctx.scopes.next_private_name_id;
ctx.scopes.next_private_name_id = ctx.scopes.next_private_name_id.saturating_add(1);
Some(id)
} else {
None
};
ctx.scopes.private_brand_id = private_brand_id;
// 实例公有字段 computed key 求值于构造器帧外,须类定义期存入数组。构造器以
// upvalue 捕获该数组:父作用域登记 `@@field_keys`(cell_idx 取现有最大 +1)。
@@ -101,6 +125,12 @@ impl Emitter {
} else {
None
};
// 类 brand 对象(= proto)由类定义期 MAKE_CELL 写入,构造器经 upvalue 捕获。
let brand_cell: Option<u8> = private_brand_id.map(|_| {
let cell_idx = ctx.captured_bindings.values().copied().max().map_or(0, |m| m.saturating_add(1));
ctx.captured_bindings.insert("@@class_brand".to_string(), cell_idx);
cell_idx
});
// 字段值表达式运行于构造器帧:其自由变量须纳入构造器 upvalue 捕获。
let field_value_exprs: Vec<&Expression> = instance_field_indices
.iter()
@@ -109,11 +139,44 @@ impl Emitter {
_ => None,
})
.collect();
let extra_upvalue_names: Vec<(&str, u8)> = field_key_cell.map(|c| ("@@field_keys", c)).into_iter().collect();
let mut extra_upvalue_names: Vec<(&str, u8)> =
field_key_cell.map(|c| ("@@field_keys", c)).into_iter().collect();
if let Some(c) = brand_cell {
extra_upvalue_names.push(("@@class_brand", c));
}
let emit_instance_fields = |compiler: &Emitter, field_ctx: &mut CompileCtx| -> Result<(), String> {
// 先写 brand 槽(私有方法/访问器的 brand 检查依据),再初始化字段。
if let Some(bid) = private_brand_id {
if brand_cell.is_none() {
return Err("class brand cell missing".into());
}
let brand_reg = field_ctx.alloc_reg();
let uv_idx = field_ctx
.current_upvalue_captures
.iter()
.position(|u| u.name == "@@class_brand")
.ok_or("class brand upvalue missing")? as u16;
field_ctx.inst(Inst::new(
OpCode::LOAD_UPVALUE,
Operand::Reg(brand_reg),
Operand::Imm(uv_idx),
Operand::None,
));
let key_idx = field_ctx.add_constant(Constant::Int(bid as i32));
let brand_key_reg = field_ctx.alloc_reg();
field_ctx.inst(Inst::load_const(Operand::Reg(brand_key_reg), key_idx));
field_ctx.inst(Inst::init_private(
Operand::This,
Operand::Reg(brand_reg),
Operand::Reg(brand_key_reg),
false,
));
}
for &idx in &instance_field_indices {
let ClassElement::PropertyDefinition(field) = &elements[idx] else { unreachable!() };
let ClassElement::PropertyDefinition(field) = &elements[idx] else {
unreachable!()
};
let field = field.as_ref();
if let PropertyKey::PrivateIdentifier(private) = &field.key {
compiler.emit_private_field_init(
@@ -170,7 +233,8 @@ impl Emitter {
module.insts.push(Inst::super_call(Operand::None, Operand::None, 0));
// 字段初始化直接重发:构造器 upvalue/内置槽引用须与首轮编译产物对齐。
let mut field_ctx = CompileCtx::new();
field_ctx.scopes.private_name_map = private_names.clone();
field_ctx.scopes.private_name_map =
private_names.iter().map(|(n, id, _, _)| (n.clone(), *id)).collect();
field_ctx.scopes.builtin_reg_map = module.builtin_reg_map.clone();
field_ctx.current_upvalue_captures = module.upvalue_captures.clone();
field_ctx.field_keys_uv = module
@@ -238,10 +302,31 @@ impl Emitter {
}
self.emit_class_prototype(ctor_reg, proto_reg, super_reg, ctor_sub_idx, ctx)?;
// brand 对象即类原型:构造器对象(静态私有访问)与实例(实例私有访问)各写
// brand 槽;构造器经 @@class_brand upvalue 捕获原型对象。
if let (Some(bid), Some(cell_idx)) = (private_brand_id, brand_cell) {
let key_idx = ctx.add_constant(Constant::Int(bid as i32));
let brand_key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(brand_key_reg), key_idx));
ctx.inst(Inst::init_private(
Operand::Reg(ctor_reg),
Operand::Reg(proto_reg),
Operand::Reg(brand_key_reg),
false,
));
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(proto_reg),
Operand::Imm(cell_idx as u16),
Operand::None,
));
}
self.emit_class_methods(&class.body.body, ctor_reg, proto_reg, &self_binding, &key_slots, ctx)?;
self.emit_class_static_elements(&class.body.body, ctor_reg, &key_slots, ctx)?;
ctx.scopes.private_name_map = saved_private_names;
ctx.scopes.private_element_kinds = saved_private_kinds;
ctx.scopes.private_brand_id = saved_brand_id;
Ok(ctor_reg)
}
}
+13 -1
View File
@@ -90,13 +90,25 @@ impl Emitter {
let saved_static = ctx.in_static_method;
ctx.in_instance_method = !method.r#static;
ctx.in_static_method = method.r#static;
let mut method_module = self.compile_function_body_with_bindings(
// 私有方法/访问器访问需对接收者做 brand 检查:方法函数捕获类 brand 对象
// (@@class_brand upvalue,值 = 类原型)。
let extra_uv: Vec<(&str, u8)> = ctx
.captured_bindings
.get("@@class_brand")
.map(|c| ("@@class_brand", *c))
.into_iter()
.collect();
let mut method_module = self.compile_function_body_with_field_hooks(
&param_names,
body_stmts,
ctx,
false,
self_binding,
FunctionBodyContext::ClassElement,
None::<fn(&Emitter, &mut CompileCtx) -> Result<(), String>>,
false,
&[],
&extra_uv,
)?;
ctx.in_instance_method = saved_instance;
ctx.in_static_method = saved_static;
+9 -2
View File
@@ -190,7 +190,13 @@ impl Emitter {
// 参数声明名遮蔽 catch 作用域。
let mut catch_shadow = shadow.clone();
if let Some(param) = &h.param {
self.collect_capture_names_binding_pattern(&param.pattern, ref_set, shadow, out, &mut catch_shadow);
self.collect_capture_names_binding_pattern(
&param.pattern,
ref_set,
shadow,
out,
&mut catch_shadow,
);
}
for s in &h.body.body {
self.collect_capture_names_stmt(s, ref_set, &catch_shadow, out);
@@ -490,7 +496,8 @@ impl Emitter {
}
/// 只从嵌套函数节点进入扫描(本函数直接引用不算捕获)。
fn collect_captured_stmt(&self, stmt: &Statement, own: &HashSet<String>, out: &mut HashSet<String>) { match stmt {
fn collect_captured_stmt(&self, stmt: &Statement, own: &HashSet<String>, out: &mut HashSet<String>) {
match stmt {
Statement::FunctionDeclaration(fd) => {
let body: &[Statement] = fd.body.as_ref().map(|b| &b.statements[..]).unwrap_or(&[]);
self.collect_fn_default_captured(&fd.params, own, out);
+11 -6
View File
@@ -24,7 +24,6 @@ pub fn is_anonymous_function_definition(expr: &oxide_parser::Expression) -> bool
}
}
use crate::emit_ctx::{LabelCtx, ScopeCtx};
use crate::symbol_table::{Binding, ScopeKind, SymbolTable};
@@ -254,6 +253,8 @@ impl CompileCtx {
symbols: SymbolTable::new(),
builtin_reg_map: Vec::new(),
private_name_map: Vec::new(),
private_element_kinds: Vec::new(),
private_brand_id: None,
next_private_name_id: 1,
},
nested: Vec::new(),
@@ -659,7 +660,6 @@ impl Emitter {
}
}
// ── 闭包捕获分析(AST 级,时序无关)──
/// 收集函数参数的绑定名(BindingIdentifier 形态)。
@@ -741,8 +741,8 @@ impl Emitter {
pub(crate) fn compile_function_body_with_field_hooks<'a, E>(
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
is_expression_body: bool, extra_bindings: &[(&str, u32)], body_context: FunctionBodyContext,
mut emit_fields: Option<E>, fields_after_super: bool,
extra_capture_exprs: &[&'a Expression<'a>], extra_upvalue_names: &[(&str, u8)],
mut emit_fields: Option<E>, fields_after_super: bool, extra_capture_exprs: &[&'a Expression<'a>],
extra_upvalue_names: &[(&str, u8)],
) -> Result<IRFunction, String>
where
E: FnMut(&Emitter, &mut CompileCtx) -> Result<(), String>,
@@ -753,6 +753,8 @@ impl Emitter {
// 解析到父预先分配的槽位。
ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone();
ctx.scopes.private_name_map = parent_ctx.scopes.private_name_map.clone();
ctx.scopes.private_element_kinds = parent_ctx.scopes.private_element_kinds.clone();
ctx.scopes.private_brand_id = parent_ctx.scopes.private_brand_id;
ctx.scopes.next_private_name_id = parent_ctx.scopes.next_private_name_id;
// 传递 enclosing_this_reg:嵌套箭头函数捕获正确的 `this`。
@@ -961,7 +963,11 @@ impl Emitter {
for spec in param_specs {
match spec {
ParamSpec::Pattern { synthetic_name, pattern, initializer } => {
ParamSpec::Pattern {
synthetic_name,
pattern,
initializer,
} => {
let src_reg = ctx.lookup(synthetic_name)?;
let src_reg = if let Some(init) = initializer {
self.emit_default_if_undefined(src_reg, init, Some(synthetic_name), ctx)?
@@ -1154,4 +1160,3 @@ impl Default for Emitter {
Self::new()
}
}
+6
View File
@@ -8,6 +8,7 @@
use crate::symbol_table::SymbolTable;
use crate::LabelScope;
use oxide_ir::operand::LabelId;
use oxide_parser::MethodDefinitionKind;
/// 跳转目标 / 标签语句解析状态。
pub(crate) struct LabelCtx {
@@ -41,6 +42,11 @@ pub(crate) struct ScopeCtx {
pub(crate) symbols: SymbolTable,
pub(crate) builtin_reg_map: Vec<(String, u32)>,
pub(crate) private_name_map: Vec<(String, u32)>,
/// 私有元素类型(name, kind,static 标志)。kind=None 表示字段;
/// instance 字段的私有访问走 PrivateFieldFind 原型链查找,不加 brand 检查。
pub(crate) private_element_kinds: Vec<(String, Option<MethodDefinitionKind>, bool)>,
/// 当前类的私有 brand 私有名 id:私有方法/访问器访问时对实例做 brand 检查。
pub(crate) private_brand_id: Option<u32>,
pub(crate) next_private_name_id: u32,
}
+6 -3
View File
@@ -207,12 +207,15 @@ impl Emitter {
}
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.inst(Inst::new(
OpCode::SET_PRIVATE,
let name = member.field.name.as_str();
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
let key_reg = self.emit_private_id_reg(name, ctx)?;
ctx.inst(Inst::set_private(
Operand::Reg(obj_reg),
Operand::Reg(val_reg),
Operand::Reg(key_reg),
brand_reg,
brand_id,
));
Ok(val_reg)
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(id_ref) = &assign.left {
+6 -3
View File
@@ -39,13 +39,16 @@ impl Emitter {
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 name = member.field.name.as_str();
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
let key_reg = self.emit_private_id_reg(name, ctx)?;
let callee_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PRIVATE,
ctx.inst(Inst::get_private(
Operand::Reg(callee_reg),
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
brand_reg,
brand_id,
));
(callee_reg, obj_reg)
}
+3 -18
View File
@@ -54,12 +54,7 @@ impl Emitter {
ctx.inst(Inst::load_const(Operand::Reg(key_reg), key_idx));
let has_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::IN,
Operand::Reg(has_reg),
Operand::Reg(key_reg),
Operand::Reg(obj_reg),
));
ctx.inst(Inst::new(OpCode::IN, Operand::Reg(has_reg), Operand::Reg(key_reg), Operand::Reg(obj_reg)));
let fallback_label = ctx.next_label_id();
let end_label = ctx.next_label_id();
@@ -101,12 +96,7 @@ impl Emitter {
));
}
} else if let Some(reg) = ctx.scopes.symbols.lookup_any(name) {
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(result_reg),
Operand::Reg(reg),
Operand::None,
));
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::Reg(reg), Operand::None));
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
ctx.inst(Inst::load_const(Operand::Reg(result_reg), undef_idx));
@@ -164,12 +154,7 @@ impl Emitter {
ctx.inst(Inst::load_const(Operand::Reg(key_reg), key_idx));
let has_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::IN,
Operand::Reg(has_reg),
Operand::Reg(key_reg),
Operand::Reg(obj_reg),
));
ctx.inst(Inst::new(OpCode::IN, Operand::Reg(has_reg), Operand::Reg(key_reg), Operand::Reg(obj_reg)));
let fallback_label = ctx.next_label_id();
let end_label = ctx.next_label_id();
+1 -5
View File
@@ -70,9 +70,7 @@ impl Emitter {
// 非法正则字面量须在编译期报 SyntaxError(负面测试期望编译失败)。
// 用 regress(ECMAScript 语法)校验,避免误报 backreference/lookaround 等合法模式。
if regress::Regex::with_flags(&pattern, flags.as_str()).is_err() {
return Err(format!(
"SyntaxError: Invalid regular expression: /{pattern}/{flags}"
));
return Err(format!("SyntaxError: Invalid regular expression: /{pattern}/{flags}"));
}
let pat_ci = ctx.add_constant(Constant::String(pattern));
let pat_reg = ctx.alloc_reg();
@@ -96,5 +94,3 @@ impl Emitter {
}
}
}
+6 -3
View File
@@ -60,13 +60,16 @@ impl Emitter {
&self, member: &oxide_parser::PrivateFieldExpression, ctx: &mut CompileCtx,
) -> Result<u32, String> {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
let name = member.field.name.as_str();
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
let key_reg = self.emit_private_id_reg(name, ctx)?;
let r = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PRIVATE,
ctx.inst(Inst::get_private(
Operand::Reg(r),
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
brand_reg,
brand_id,
));
Ok(r)
}
+1 -6
View File
@@ -343,12 +343,7 @@ impl Emitter {
} else {
OpCode::SUB
};
ctx.inst(Inst::new(
op,
Operand::Reg(new_reg),
Operand::Reg(old_reg),
Operand::Reg(one_reg),
));
ctx.inst(Inst::new(op, Operand::Reg(new_reg), Operand::Reg(old_reg), Operand::Reg(one_reg)));
ctx.inst(Inst::new(
OpCode::SET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
+18 -9
View File
@@ -72,13 +72,16 @@ impl Emitter {
self.emit_optional_guard(obj_reg, label, ctx)?;
}
}
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
let name = member.field.name.as_str();
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
let key_reg = self.emit_private_id_reg(name, ctx)?;
let callee_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PRIVATE,
ctx.inst(Inst::get_private(
Operand::Reg(callee_reg),
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
brand_reg,
brand_id,
));
(callee_reg, obj_reg)
}
@@ -165,13 +168,16 @@ impl Emitter {
self.emit_optional_guard(obj_reg, label, ctx)?;
}
}
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
let name = member.field.name.as_str();
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
let key_reg = self.emit_private_id_reg(name, ctx)?;
let value_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PRIVATE,
ctx.inst(Inst::get_private(
Operand::Reg(value_reg),
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
brand_reg,
brand_id,
));
Ok(value_reg)
}
@@ -200,13 +206,16 @@ impl Emitter {
self.emit_optional_guard(obj_reg, label, ctx)?;
}
}
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
let name = member.field.name.as_str();
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
let key_reg = self.emit_private_id_reg(name, ctx)?;
let value_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PRIVATE,
ctx.inst(Inst::get_private(
Operand::Reg(value_reg),
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
brand_reg,
brand_id,
));
Ok(value_reg)
}
+33 -7
View File
@@ -5,14 +5,14 @@ use oxide_bytecode::module::Constant;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
use oxide_parser::{Expression, PropertyKey};
use oxide_parser::{Expression, MethodDefinitionKind, PropertyKey};
impl Emitter {
/// 实例字段初始化:define 语义(不触发原型 setter)。
/// 非计算键用字符串常量;计算键从 `@@field_keys` upvalue 数组按 `key_slot` 取(类定义期求值一次)。
pub(crate) fn emit_public_field_init(
&self, target: Operand, key: &PropertyKey, computed: bool, value: Option<&Expression>,
key_slot: Option<u8>, ctx: &mut CompileCtx,
&self, target: Operand, key: &PropertyKey, computed: bool, value: Option<&Expression>, key_slot: Option<u8>,
ctx: &mut CompileCtx,
) -> Result<(), String> {
let key_reg = if computed {
self.emit_instance_field_key(key_slot, ctx)?
@@ -28,7 +28,12 @@ impl Emitter {
fn emit_instance_field_key(&self, key_slot: Option<u8>, ctx: &mut CompileCtx) -> Result<u32, String> {
let uv = ctx.field_keys_uv.expect("computed instance field without field keys upvalue");
let arr_reg = ctx.alloc_reg();
ctx.inst(Inst::new(OpCode::LOAD_UPVALUE, Operand::Reg(arr_reg), Operand::Imm(uv as u16), Operand::None));
ctx.inst(Inst::new(
OpCode::LOAD_UPVALUE,
Operand::Reg(arr_reg),
Operand::Imm(uv as u16),
Operand::None,
));
let idx_reg = ctx.alloc_reg();
let idx = ctx.add_constant(Constant::Int(key_slot.unwrap_or(0) as i32));
ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx));
@@ -56,7 +61,7 @@ impl Emitter {
) -> Result<(), String> {
let key_reg = self.emit_private_id_reg(name, ctx)?;
let value_reg = self.emit_field_value(value, ctx)?;
ctx.inst(Inst::new(OpCode::INIT_PRIVATE, target, Operand::Reg(value_reg), Operand::Reg(key_reg)));
ctx.inst(Inst::init_private(target, Operand::Reg(value_reg), Operand::Reg(key_reg), false));
Ok(())
}
@@ -67,9 +72,30 @@ impl Emitter {
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.inst(Inst::new(OpCode::INIT_PRIVATE, target, Operand::Reg(method_reg), Operand::Reg(key_reg)));
match method.kind {
MethodDefinitionKind::Method => {
let key_reg = self.emit_private_id_reg(name, ctx)?;
ctx.inst(Inst::init_private(target, Operand::Reg(method_reg), Operand::Reg(key_reg), true));
}
MethodDefinitionKind::Get | MethodDefinitionKind::Set => {
let id = self.private_name_id(name, ctx)?;
let key_idx = ctx.add_constant(Constant::Int(id as i32));
let undef_reg = self.emit_undefined(ctx);
let (get_reg, set_reg) = if method.kind == MethodDefinitionKind::Get {
(method_reg, undef_reg)
} else {
(undef_reg, method_reg)
};
ctx.inst(Inst::define_accessor(
target,
Operand::Reg(get_reg),
Operand::Reg(set_reg),
key_idx as u32,
));
}
MethodDefinitionKind::Constructor => return Err("constructor cannot be private".into()),
}
Ok(())
}
}
+31 -1
View File
@@ -9,7 +9,7 @@ use oxide_ir::operand::Operand;
use oxide_parser::PropertyKey;
impl Emitter {
fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result<u32, String> {
pub(crate) fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result<u32, String> {
ctx.scopes
.private_name_map
.iter()
@@ -25,6 +25,36 @@ impl Emitter {
Ok(reg)
}
/// 私有成员访问的 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 语义受限)。
pub(crate) fn private_access_brand(
&self, _obj_reg: u32, name: &str, ctx: &mut CompileCtx,
) -> Result<(u32, u32), String> {
let Some(brand_id) = ctx.scopes.private_brand_id else { return Ok((0, 0)) };
let Some((_, kind, is_static)) = ctx.scopes.private_element_kinds.iter().find(|(n, _, _)| n == name) else {
return Ok((0, 0));
};
if kind.is_none() && !is_static {
return Ok((0, 0));
}
let Some(uv_idx) = ctx.current_upvalue_captures.iter().position(|u| u.name == "@@class_brand") else {
return Ok((0, 0));
};
let brand_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::LOAD_UPVALUE,
Operand::Reg(brand_reg),
Operand::Imm(uv_idx as u16),
Operand::None,
));
Ok((brand_reg, brand_id))
}
pub(crate) fn emit_class_key_reg(
&self, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx,
) -> Result<u32, String> {
@@ -21,17 +21,15 @@ impl Emitter {
let mut param_names = Vec::new();
for (idx, param) in fd.params.items.iter().enumerate() {
match &param.pattern {
oxide_parser::BindingPattern::BindingIdentifier(bi) => {
param_names.push(ParamSpec::Identifier {
name: bi.name.to_string(),
initializer: param.initializer.as_deref(),
})
}
oxide_parser::BindingPattern::BindingIdentifier(bi) => param_names.push(ParamSpec::Identifier {
name: bi.name.to_string(),
initializer: param.initializer.as_deref(),
}),
pattern => param_names.push(ParamSpec::Pattern {
synthetic_name: format!("@@param_{idx}"),
pattern,
initializer: param.initializer.as_deref(),
}),
synthetic_name: format!("@@param_{idx}"),
pattern,
initializer: param.initializer.as_deref(),
}),
}
}
let body_stmts: &[Statement] = if let Some(body) = &fd.body { &body.statements } else { &[] };
@@ -54,4 +52,3 @@ impl Emitter {
Ok(None)
}
}
@@ -67,4 +67,3 @@ impl Emitter {
Ok(r)
}
}
+24 -5
View File
@@ -58,10 +58,8 @@ impl Inst {
| OpCode::SET_PROP_DYNAMIC
| OpCode::IC_SET_PROP
| OpCode::SET_ELEM
| OpCode::GET_PRIVATE
| OpCode::SET_PRIVATE
| OpCode::INIT_PRIVATE
| OpCode::PRIVATE_BRAND_IN
| OpCode::DELETE_PROP_STATIC
| OpCode::DELETE_PROP_DYNAMIC
| OpCode::DEFINE_ACCESSOR
@@ -253,16 +251,37 @@ impl Inst {
| OpCode::SET_PROP_DYNAMIC
| OpCode::IC_SET_PROP
| OpCode::SET_ELEM
| OpCode::GET_PRIVATE
| OpCode::SET_PRIVATE
| OpCode::INIT_PRIVATE
| OpCode::PRIVATE_BRAND_IN
| OpCode::DEFINE_ACCESSOR
| OpCode::DEFINE_PROP => {
push_operand(&mut uses, &self.rd);
push_operand(&mut uses, &self.a);
push_operand(&mut uses, &self.b);
}
// 私有写:rd=对象、a=值、b=键是 use,额外读 ext[0](brand 对象寄存器)
OpCode::SET_PRIVATE => {
push_operand(&mut uses, &self.rd);
push_operand(&mut uses, &self.a);
push_operand(&mut uses, &self.b);
let brand_reg = self.ext.first().copied().unwrap_or(0);
if brand_reg != 0 {
uses.push(brand_reg);
}
}
// 私有读/品牌检查:a=对象、b=私有键是 use,rd 是结果 def
// GET_PRIVATE 额外读 ext[0](brand 对象寄存器,0 表示跳过检查)
OpCode::GET_PRIVATE => {
push_operand(&mut uses, &self.a);
push_operand(&mut uses, &self.b);
let brand_reg = self.ext.first().copied().unwrap_or(0);
if brand_reg != 0 {
uses.push(brand_reg);
}
}
OpCode::PRIVATE_BRAND_IN => {
push_operand(&mut uses, &self.a);
push_operand(&mut uses, &self.b);
}
// delete:读 obj(+dynamic 读 key)
OpCode::DELETE_PROP_STATIC => push_operand(&mut uses, &self.rd),
OpCode::DELETE_PROP_DYNAMIC => {
+18
View File
@@ -168,6 +168,24 @@ impl Inst {
// ── 其他带 ext 字 ──
/// 私有成员读取:result=结果,obj=接收者,key=私有名 id。
/// ext = [brand_reg, brand_id]:brand_reg 为当前类 brand 对象寄存器(0 表示跳过
/// brand 检查,instance 字段路径),brand_id 为实例 brand 槽的私有名 id。
pub fn get_private(result: Operand, obj: Operand, key: Operand, brand_reg: u32, brand_id: u32) -> Self {
Self::with_ext(OpCode::GET_PRIVATE, 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])
}
/// 私有成员初始化:target=接收者,value=初值,key=私有名 id。
/// ext = [is_method]:1 表示私有方法槽(不可写),0 表示字段/brand 槽。
pub fn init_private(target: Operand, value: Operand, key: Operand, is_method: bool) -> Self {
Self::with_ext(OpCode::INIT_PRIVATE, target, value, key, &[is_method as u32])
}
/// 定义访问器属性:home 为宿主对象,get/set 为访问器函数寄存器,key_idx 为属性名常量下标。
pub fn define_accessor(home: Operand, get: Operand, set: Operand, key_idx: u32) -> Self {
Self::with_ext(OpCode::DEFINE_ACCESSOR, home, get, set, &[key_idx])
-1
View File
@@ -307,4 +307,3 @@ fn operand_this_is_semantic_not_physical_index() {
assert_eq!(Operand::This, Operand::This);
assert_eq!(Operand::NewTarget, Operand::NewTarget);
}
+8 -1
View File
@@ -1104,7 +1104,14 @@ impl BuiltinWorld {
pub fn bind_string_methods(&self, methods: &StringMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) {
let ctor_ptr = P::as_ptr(&self.string_constructor) as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
bind_methods!(self, ctor, string_forge, shape_forge, ("fromCharCode", methods.from_char_code, 1), ("fromCodePoint", methods.from_code_point, 1),);
bind_methods!(
self,
ctor,
string_forge,
shape_forge,
("fromCharCode", methods.from_char_code, 1),
("fromCodePoint", methods.from_code_point, 1),
);
let proto_ptr = P::as_ptr(&self.string_proto) as *mut JsObject;
let proto = unsafe { &mut *proto_ptr };
+1 -2
View File
@@ -85,8 +85,7 @@ pub fn init(config: &LogConfig) {
}
SUBSYSTEM.set(filter.clone()).ok();
let env_filter = EnvFilter::try_from_env("OXIDE_LOG")
.unwrap_or_else(|_| EnvFilter::new("oxide=trace"));
let env_filter = EnvFilter::try_from_env("OXIDE_LOG").unwrap_or_else(|_| EnvFilter::new("oxide=trace"));
subsystem::apply_env_levels();
+3 -2
View File
@@ -36,8 +36,9 @@ pub fn parse<'a>(allocator: &'a Allocator, source: &'a str) -> Result<Program<'a
// 语法早期错误(Early Errors)检查:oxc parser 把复杂早期错误委托给
// oxc_semantic(strict 绑定标识符、重复参数、NSPL、super 位置等)。
let semantic_ret =
oxc_semantic::SemanticBuilder::new().with_check_syntax_error(true).build(&ret.program);
let semantic_ret = oxc_semantic::SemanticBuilder::new()
.with_check_syntax_error(true)
.build(&ret.program);
if !semantic_ret.errors.is_empty() {
return Err(semantic_ret.errors.into_iter().map(OxideError::from).collect());
}
+10
View File
@@ -225,6 +225,16 @@ fn rewrite_inst(
}
ext
}
// GET_PRIVATE/SET_PRIVATE:ext[0] 是 brand 对象寄存器(0 表示跳过检查),须重映射。
OpCode::GET_PRIVATE | OpCode::SET_PRIVATE => {
let mut ext = inst.ext.clone();
if let Some(brand_reg) = ext.first_mut() {
if *brand_reg != 0 {
*brand_reg = remap_ext_reg(*brand_reg, slot_color, map);
}
}
ext
}
_ => inst.ext.clone(),
};
// 调用点首参槽改指 arg_window_base(桥接后)
+12
View File
@@ -774,6 +774,18 @@ impl JsObject {
self.set_meta_at(position, PropMetaEntry::accessor(get, set, attributes));
}
/// 标记私有方法槽为不可写:复用 `hole` 位(私有键在普通属性/枚举路径不可见,
/// 与数组删除标记无冲突)。GET_PRIVATE 仍按数据槽取值,SET_PRIVATE 遇此标记抛错。
pub fn set_private_method_meta(&mut self, position: impl PropIndex) {
self.set_meta_at(
position,
PropMetaEntry {
hole: true,
..PropMetaEntry::data(PropAttributes::DEFAULT_DATA)
},
);
}
/// 判断指定下标的属性是否为访问器属性。
pub fn is_accessor_meta(&self, position: impl PropIndex) -> bool {
self.prop_meta_at(position).is_some_and(|entry| entry.is_accessor)
+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]