From 915dedb15102eff1a56b1b5eb4503e95cfea8d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Sun, 9 Aug 2026 11:08:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20class=20=E7=A7=81=E6=9C=89=E6=88=90?= =?UTF-8?q?=E5=91=98=E8=AF=AD=E4=B9=89=EF=BC=88=E5=AD=97=E6=AE=B5/?= =?UTF-8?q?=E6=96=B9=E6=B3=95/=E8=AE=BF=E9=97=AE=E5=99=A8=20+=20brand=20?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=20+=20=E5=A5=91=E7=BA=A6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/object.rs | 15 +- crates/oxide_builtins/src/regexp.rs | 10 +- crates/oxide_builtins/src/string.rs | 13 +- crates/oxide_cfg/src/partition.rs | 10 +- crates/oxide_compiler/src/flatten.rs | 13 +- .../tests/compile_expr_tests.rs | 5 +- crates/oxide_emit/src/class/emit.rs | 109 ++++++++++++-- crates/oxide_emit/src/class/method.rs | 14 +- crates/oxide_emit/src/closure.rs | 11 +- crates/oxide_emit/src/emit.rs | 17 ++- crates/oxide_emit/src/emit_ctx.rs | 6 + crates/oxide_emit/src/expr/assignment.rs | 9 +- crates/oxide_emit/src/expr/call.rs | 9 +- crates/oxide_emit/src/expr/identifier.rs | 21 +-- crates/oxide_emit/src/expr/literal.rs | 6 +- crates/oxide_emit/src/expr/member/mod.rs | 9 +- crates/oxide_emit/src/expr/operator/mod.rs | 7 +- crates/oxide_emit/src/shared/chain.rs | 27 ++-- crates/oxide_emit/src/shared/class_field.rs | 40 ++++- crates/oxide_emit/src/shared/class_key.rs | 32 +++- .../src/stmt/declaration/function_decl.rs | 19 +-- crates/oxide_emit/src/stmt/declaration/var.rs | 1 - crates/oxide_ir/src/contract.rs | 29 +++- crates/oxide_ir/src/inst.rs | 18 +++ crates/oxide_ir/tests/lowering_tests.rs | 1 - crates/oxide_kernel/src/builtin.rs | 9 +- crates/oxide_log/src/init.rs | 3 +- crates/oxide_parser/src/lib.rs | 5 +- crates/oxide_regalloc/src/rewrite.rs | 10 ++ crates/oxide_types/src/object.rs | 12 ++ crates/oxide_vm/src/bindings/bind_regexp.rs | 6 +- crates/oxide_vm/src/dispatch/misc.rs | 9 +- crates/oxide_vm/src/dispatch/property.rs | 141 +++++++++++++++--- crates/oxide_vm/src/ic_helper.rs | 6 +- crates/oxide_vm/src/vm.rs | 30 ++-- crates/oxide_vm/src/vm_dispatch_ctrl.rs | 35 +++-- crates/oxide_vm/src/vm_props.rs | 4 +- .../tests/builtins_typed_array_tests.rs | 12 +- crates/oxide_vm/tests/class_tests.rs | 8 +- .../oxide_vm/tests/property_delete_tests.rs | 6 +- .../tests/try_finally_control_flow_tests.rs | 42 +++++- 41 files changed, 573 insertions(+), 216 deletions(-) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 3fd6b48..c3586a0 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -362,10 +362,7 @@ pub fn object_is(vm: &mut H, args: &[u8]) -> NativeResult { /// # 副作用 /// - 修改 obj 的 shape 链、属性表与 generation fn define_from_descriptor( - 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( /// # 副作用 /// - 修改 target 的 shape 链、属性表与 generation fn define_all_from_properties( - 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( 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)?; diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 972cff6..e266611 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -224,12 +224,9 @@ pub fn regexp_exec(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(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)) } - diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index c6360b9..9634cfc 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -973,10 +973,7 @@ pub fn string_code_point_at(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(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(vm: &mut H, args: &[u8]) -> NativeResult { let result = s.replace(&pattern, &replacement); NativeResult::Ok(vm.new_string(&result)) } - - - diff --git a/crates/oxide_cfg/src/partition.rs b/crates/oxide_cfg/src/partition.rs index 8840f01..eeb0d1c 100644 --- a/crates/oxide_cfg/src/partition.rs +++ b/crates/oxide_cfg/src/partition.rs @@ -25,7 +25,15 @@ pub(super) fn partition_blocks(f: &IRFunction) -> Vec { // 跳转的 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; } } diff --git a/crates/oxide_compiler/src/flatten.rs b/crates/oxide_compiler/src/flatten.rs index ad6a62a..b84f1da 100644 --- a/crates/oxide_compiler/src/flatten.rs +++ b/crates/oxide_compiler/src/flatten.rs @@ -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 { diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index 499d562..e4b8fde 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -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 变体 diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index 9b7898e..8fea5c5 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -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, 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 = 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) } } diff --git a/crates/oxide_emit/src/class/method.rs b/crates/oxide_emit/src/class/method.rs index ac3484a..af4f74b 100644 --- a/crates/oxide_emit/src/class/method.rs +++ b/crates/oxide_emit/src/class/method.rs @@ -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( ¶m_names, body_stmts, ctx, false, self_binding, FunctionBodyContext::ClassElement, + None:: Result<(), String>>, + false, + &[], + &extra_uv, )?; ctx.in_instance_method = saved_instance; ctx.in_static_method = saved_static; diff --git a/crates/oxide_emit/src/closure.rs b/crates/oxide_emit/src/closure.rs index 14e44f8..f464d9e 100644 --- a/crates/oxide_emit/src/closure.rs +++ b/crates/oxide_emit/src/closure.rs @@ -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(¶m.pattern, ref_set, shadow, out, &mut catch_shadow); + self.collect_capture_names_binding_pattern( + ¶m.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, out: &mut HashSet) { match stmt { + fn collect_captured_stmt(&self, stmt: &Statement, own: &HashSet, out: &mut HashSet) { + 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); diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index a933c67..573ac49 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -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, fields_after_super: bool, - extra_capture_exprs: &[&'a Expression<'a>], extra_upvalue_names: &[(&str, u8)], + mut emit_fields: Option, fields_after_super: bool, extra_capture_exprs: &[&'a Expression<'a>], + extra_upvalue_names: &[(&str, u8)], ) -> Result 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() } } - diff --git a/crates/oxide_emit/src/emit_ctx.rs b/crates/oxide_emit/src/emit_ctx.rs index ec0d7ad..9f9d9f3 100644 --- a/crates/oxide_emit/src/emit_ctx.rs +++ b/crates/oxide_emit/src/emit_ctx.rs @@ -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, bool)>, + /// 当前类的私有 brand 私有名 id:私有方法/访问器访问时对实例做 brand 检查。 + pub(crate) private_brand_id: Option, pub(crate) next_private_name_id: u32, } diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index 838a544..c08dd30 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -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 { diff --git a/crates/oxide_emit/src/expr/call.rs b/crates/oxide_emit/src/expr/call.rs index 7c84792..3b7e381 100644 --- a/crates/oxide_emit/src/expr/call.rs +++ b/crates/oxide_emit/src/expr/call.rs @@ -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) } diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index 73a1493..fbe4220 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -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(); diff --git a/crates/oxide_emit/src/expr/literal.rs b/crates/oxide_emit/src/expr/literal.rs index a997280..2e1011c 100644 --- a/crates/oxide_emit/src/expr/literal.rs +++ b/crates/oxide_emit/src/expr/literal.rs @@ -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 { } } } - - diff --git a/crates/oxide_emit/src/expr/member/mod.rs b/crates/oxide_emit/src/expr/member/mod.rs index 9f9ed99..08af0c9 100644 --- a/crates/oxide_emit/src/expr/member/mod.rs +++ b/crates/oxide_emit/src/expr/member/mod.rs @@ -60,13 +60,16 @@ impl Emitter { &self, member: &oxide_parser::PrivateFieldExpression, ctx: &mut CompileCtx, ) -> Result { 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) } diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index d9dfae8..83e7a42 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -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), diff --git a/crates/oxide_emit/src/shared/chain.rs b/crates/oxide_emit/src/shared/chain.rs index affcc77..6770e30 100644 --- a/crates/oxide_emit/src/shared/chain.rs +++ b/crates/oxide_emit/src/shared/chain.rs @@ -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) } diff --git a/crates/oxide_emit/src/shared/class_field.rs b/crates/oxide_emit/src/shared/class_field.rs index 906e5da..6f81e0d 100644 --- a/crates/oxide_emit/src/shared/class_field.rs +++ b/crates/oxide_emit/src/shared/class_field.rs @@ -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, ctx: &mut CompileCtx, + &self, target: Operand, key: &PropertyKey, computed: bool, value: Option<&Expression>, key_slot: Option, + 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, ctx: &mut CompileCtx) -> Result { 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(()) } } diff --git a/crates/oxide_emit/src/shared/class_key.rs b/crates/oxide_emit/src/shared/class_key.rs index c7ec9ed..c842f2e 100644 --- a/crates/oxide_emit/src/shared/class_key.rs +++ b/crates/oxide_emit/src/shared/class_key.rs @@ -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 { + pub(crate) fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result { 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 { diff --git a/crates/oxide_emit/src/stmt/declaration/function_decl.rs b/crates/oxide_emit/src/stmt/declaration/function_decl.rs index 237e0fb..9ba4472 100644 --- a/crates/oxide_emit/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/function_decl.rs @@ -21,17 +21,15 @@ impl Emitter { let mut param_names = Vec::new(); for (idx, param) in fd.params.items.iter().enumerate() { match ¶m.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) } } - diff --git a/crates/oxide_emit/src/stmt/declaration/var.rs b/crates/oxide_emit/src/stmt/declaration/var.rs index 94427c0..3b6a5c8 100644 --- a/crates/oxide_emit/src/stmt/declaration/var.rs +++ b/crates/oxide_emit/src/stmt/declaration/var.rs @@ -67,4 +67,3 @@ impl Emitter { Ok(r) } } - diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index dc0c26f..92b8567 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -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 => { diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 8c6a594..99f2f05 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -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]) diff --git a/crates/oxide_ir/tests/lowering_tests.rs b/crates/oxide_ir/tests/lowering_tests.rs index 6093b91..1255d72 100644 --- a/crates/oxide_ir/tests/lowering_tests.rs +++ b/crates/oxide_ir/tests/lowering_tests.rs @@ -307,4 +307,3 @@ fn operand_this_is_semantic_not_physical_index() { assert_eq!(Operand::This, Operand::This); assert_eq!(Operand::NewTarget, Operand::NewTarget); } - diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 5bb9a2e..8d3713c 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -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 }; diff --git a/crates/oxide_log/src/init.rs b/crates/oxide_log/src/init.rs index a31c569..debc4e5 100644 --- a/crates/oxide_log/src/init.rs +++ b/crates/oxide_log/src/init.rs @@ -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(); diff --git a/crates/oxide_parser/src/lib.rs b/crates/oxide_parser/src/lib.rs index 1a3e477..0492ba2 100644 --- a/crates/oxide_parser/src/lib.rs +++ b/crates/oxide_parser/src/lib.rs @@ -36,8 +36,9 @@ pub fn parse<'a>(allocator: &'a Allocator, source: &'a str) -> Result { + 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(桥接后) diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index f424d71..783554f 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -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) diff --git a/crates/oxide_vm/src/bindings/bind_regexp.rs b/crates/oxide_vm/src/bindings/bind_regexp.rs index b511ac1..1bdcbed 100644 --- a/crates/oxide_vm/src/bindings/bind_regexp.rs +++ b/crates/oxide_vm/src/bindings/bind_regexp.rs @@ -31,7 +31,11 @@ pub fn bind_regexp(core: &Arc, session: &KernelSession, global: &mut ), ("@@search", oxide_builtins::regexp::regexp_symbol_search:: as *const (), 1), ("@@split", oxide_builtins::regexp::regexp_symbol_split:: as *const (), 2), - ("@@matchAll", oxide_builtins::regexp::regexp_symbol_match_all:: as *const (), 1), + ( + "@@matchAll", + oxide_builtins::regexp::regexp_symbol_match_all:: as *const (), + 1, + ), ], ); diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index 24f7bbe..36930ec 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -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); } diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 7cd9c3b..36e5fb9 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -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); diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index 62aafcb..0667931 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -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 } } - diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index bc066e1..5a23e48 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -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, + }, } } } diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 773d06e..bc547ef 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -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() { diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 6886376..d074e92 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -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 { 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); diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index 617bd44..eb5e83e 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -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()); } diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index cddc614..30c73ca 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -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); } diff --git a/crates/oxide_vm/tests/property_delete_tests.rs b/crates/oxide_vm/tests/property_delete_tests.rs index 9739ecc..724d3a7 100644 --- a/crates/oxide_vm/tests/property_delete_tests.rs +++ b/crates/oxide_vm/tests/property_delete_tests.rs @@ -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 ── diff --git a/crates/oxide_vm/tests/try_finally_control_flow_tests.rs b/crates/oxide_vm/tests/try_finally_control_flow_tests.rs index 0f72a69..78f4adf 100644 --- a/crates/oxide_vm/tests/try_finally_control_flow_tests.rs +++ b/crates/oxide_vm/tests/try_finally_control_flow_tests.rs @@ -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]