From 86a20fa1fe6cce0cda076a604a3bad958e32809d 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: Wed, 12 Aug 2026 17:30:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20class=20=E7=B1=BB=E5=90=8D=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E7=BB=8F=20dedicated=20cell=20=E6=8D=95=E8=8E=B7?= =?UTF-8?q?=EF=BC=88C=3D=3D=3DCv=20=E6=81=92=E7=AD=89=EF=BC=89=EF=BC=8B=20?= =?UTF-8?q?upvalue=20=E5=86=99=E5=85=A5=20const=20guard=EF=BC=88getter=20?= =?UTF-8?q?=E8=B5=8B=E5=80=BC=E6=8A=9B=20TypeError=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/class/emit.rs | 31 ++++++++++++++++++- crates/oxide_emit/src/class/method.rs | 14 ++++++--- crates/oxide_emit/src/emit.rs | 2 +- crates/oxide_emit/src/expr/assignment.rs | 6 ++-- crates/oxide_emit/src/expr/identifier.rs | 6 ++-- crates/oxide_emit/src/expr/operator/mod.rs | 3 +- crates/oxide_emit/src/shared/class_field.rs | 5 +-- .../src/stmt/declaration/class_decl.rs | 15 +-------- crates/oxide_vm/src/dispatch/call.rs | 7 ++++- crates/oxide_vm/src/vm.rs | 2 +- 10 files changed, 60 insertions(+), 31 deletions(-) diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index 8e11347..a5438eb 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -122,6 +122,22 @@ impl Emitter { let (ctor_reg, proto_reg, super_reg) = self.emit_class_header(class, ctx)?; let self_binding = ctor_name.as_deref().map(|name| vec![(name, binding_reg)]).unwrap_or_default(); + // Class-name binding cell: class elements (ctor/method/field) that reference the + // class name capture it through this dedicated cell. Reads inside the class then + // resolve via LOAD_UPVALUE (same cell as the binding), so identity with other + // references survives epoch promotion (which rewrites cell values) and inline + // accessor calls (which clear the register file). The synthetic key keeps user + // bindings with the same name (outer scopes) untouched. + let class_self_cell: Option = ctor_name.as_deref().map(|_| { + let cell_idx = ctx + .captured_bindings + .values() + .copied() + .max() + .map_or(0, |m| m.saturating_add(1)); + ctx.captured_bindings.insert(format!("@@class_self_{cell_idx}"), cell_idx); + cell_idx + }); let saved_derived = ctx.in_derived_constructor; let saved_private_names = ctx.scopes.private_name_map.clone(); @@ -178,6 +194,9 @@ impl Emitter { if let Some(c) = brand_cell { extra_upvalue_names.push(("@@class_brand", c)); } + if let (Some(name), Some(c)) = (ctor_name.as_deref(), class_self_cell) { + extra_upvalue_names.push((name, c)); + } let emit_instance_fields = |compiler: &Emitter, field_ctx: &mut CompileCtx| -> Result<(), String> { // 先写 brand 槽(私有方法/访问器的 brand 检查依据),再初始化字段。 @@ -358,7 +377,7 @@ impl Emitter { Operand::None, )); } - self.emit_class_methods(&class.body.body, ctor_reg, proto_reg, &self_binding, &key_slots, ctx)?; + self.emit_class_methods(&class.body.body, ctor_reg, proto_reg, &self_binding, class_self_cell, &key_slots, ctx)?; self.emit_class_static_elements(&class.body.body, ctor_reg, &key_slots, ctx)?; @@ -371,6 +390,16 @@ impl Emitter { Operand::Reg(ctor_reg), Operand::None, )); + if let Some(cell_idx) = class_self_cell { + // Initialize the class-name cell with the constructor and release TDZ + // (the placeholder cell was created by the element closures at hoist time). + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(binding_reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); + } } if pushed_scope { diff --git a/crates/oxide_emit/src/class/method.rs b/crates/oxide_emit/src/class/method.rs index d2106f6..0b03a6b 100644 --- a/crates/oxide_emit/src/class/method.rs +++ b/crates/oxide_emit/src/class/method.rs @@ -12,9 +12,10 @@ use oxide_parser::{ClassElement, MethodDefinitionKind, PropertyKey}; const CLASS_METHOD_ATTRS: u32 = 0b101; impl Emitter { + #[allow(clippy::too_many_arguments)] pub(crate) fn emit_class_methods( &self, elements: &[ClassElement], ctor_reg: u32, proto_reg: u32, self_binding: &[(&str, u32)], - key_slots: &[Option], ctx: &mut CompileCtx, + class_self_cell: Option, key_slots: &[Option], ctx: &mut CompileCtx, ) -> Result<(), String> { for (element, &key_slot) in elements.iter().zip(key_slots) { if let ClassElement::MethodDefinition(method) = element { @@ -24,7 +25,7 @@ impl Emitter { } if matches!(method.key, PropertyKey::PrivateIdentifier(_)) { let home_reg = if method.r#static { ctor_reg } else { proto_reg }; - self.emit_private_method_init(Operand::Reg(home_reg), method, Operand::Reg(home_reg), ctx)?; + self.emit_private_method_init(Operand::Reg(home_reg), method, Operand::Reg(home_reg), self_binding, class_self_cell, ctx)?; continue; } let home_reg = if method.r#static { ctor_reg } else { proto_reg }; @@ -39,7 +40,7 @@ impl Emitter { self.class_property_name(&method.key)? }; let accessor_reg = - self.emit_class_method_function(method, &method_name, Operand::Reg(home_reg), ctx, self_binding)?; + self.emit_class_method_function(method, &method_name, Operand::Reg(home_reg), ctx, self_binding, class_self_cell)?; match method.kind { MethodDefinitionKind::Method => { // class 方法按规范为非枚举数据属性(DefineMethod:writable/configurable,enumerable=false)。 @@ -79,7 +80,7 @@ impl Emitter { pub(crate) fn emit_class_method_function( &self, method: &oxide_parser::MethodDefinition, method_name: &str, home_reg: Operand, ctx: &mut CompileCtx, - self_binding: &[(&str, u32)], + self_binding: &[(&str, u32)], class_self_cell: Option, ) -> Result { let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?; let saved_instance = ctx.in_instance_method; @@ -88,12 +89,15 @@ impl Emitter { ctx.in_static_method = method.r#static; // 私有方法/访问器访问需对接收者做 brand 检查:方法函数捕获类 brand 对象 // (@@class_brand upvalue,值 = 类原型)。 - let extra_uv: Vec<(&str, u8)> = ctx + let mut extra_uv: Vec<(&str, u8)> = ctx .captured_bindings .get("@@class_brand") .map(|c| ("@@class_brand", *c)) .into_iter() .collect(); + if let (Some((name, _)), Some(c)) = (self_binding.first(), class_self_cell) { + extra_uv.push((name, c)); + } let method_value = method.value.as_ref(); let mut method_module = if method_value.r#async { self.compile_function_body_with_field_hooks_gen( diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index a8cb595..3a067c3 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -1130,7 +1130,7 @@ impl Emitter { ); // 类字段 computed key 数组等合成捕获:直接追加 upvalue(cell_idx 由父分配)。 for (name, cell_idx) in extra_upvalue_names { - if !ctx.current_upvalue_captures.iter().any(|u| u.name == *name) { + if !ctx.own_bindings.contains(*name) && !ctx.current_upvalue_captures.iter().any(|u| u.name == *name) { ctx.current_upvalue_captures.push(UpvalueCapture { name: (*name).to_string(), enclosing_reg: 0, diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index d666270..cae8676 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -28,6 +28,7 @@ impl Emitter { } let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name); let captured_cell = ctx.captured_bindings.get(name).copied(); + let const_flag = if ctx.lookup_const_flag(name) { 1 } else { 0 }; if let Some(uv) = uv_idx { let val_reg = ctx.alloc_reg(); ctx.inst(Inst::new( @@ -39,7 +40,7 @@ impl Emitter { ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(rhs), Operand::None)); ctx.inst(Inst::new( OpCode::STORE_UPVALUE, - Operand::None, + Operand::Imm(const_flag), Operand::Reg(val_reg), Operand::Imm(uv as u16), )); @@ -266,10 +267,11 @@ impl Emitter { self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; ctx.labels.set_label_pos(store_label, ctx.insts.len()); let val_reg = self.emit_expression(&assign.right, ctx)?; + let const_flag = if ctx.lookup_const_flag(name) { 1 } else { 0 }; if let Some(uv) = uv_idx { ctx.inst(Inst::new( OpCode::STORE_UPVALUE, - Operand::None, + Operand::Imm(const_flag), Operand::Reg(val_reg), Operand::Imm(uv as u16), )); diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index faa0b4b..284df81 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -116,8 +116,8 @@ impl Emitter { /// 静态标识符写入:upvalue / 被捕获 cell / 普通槽(含 const guard)。 /// /// # 边界与前提 - /// - `const_flag` 为 1 时走 STORE_VAR 运行时 const guard(对 const 再赋值报错)。 - /// - upvalue 与 cell 写穿共享单元,不设 guard。 + /// - `const_flag` 为 1 时走 STORE_VAR / STORE_UPVALUE 运行时 const guard(对 const 再赋值报错)。 + /// - cell 写穿共享单元,不设 guard(类名/闭包捕获的初始化写入路径);upvalue 按 const_flag 设 guard。 pub(crate) fn emit_identifier_store(&self, name: &str, val_reg: u32, const_flag: u16, ctx: &mut CompileCtx) { // 循环 update 段:被捕获绑定走寄存器而非 cell(C 风格 for 每迭代 fresh, // update 写寄存器供下一迭代 fresh 拷贝,不污染本迭代闭包捕获的 cell)。 @@ -136,7 +136,7 @@ impl Emitter { if let Some(uv_idx) = ctx.current_upvalue_captures.iter().position(|u| u.name == name) { ctx.inst(Inst::new( OpCode::STORE_UPVALUE, - Operand::None, + Operand::Imm(const_flag), Operand::Reg(val_reg), Operand::Imm(uv_idx as u16), )); diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index a50e8a4..3c858ac 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -455,9 +455,10 @@ impl Emitter { }; let new_reg = ctx.alloc_reg(); ctx.inst(Inst::new(op, Operand::Reg(new_reg), Operand::Reg(old_reg), Operand::Reg(one_reg))); + let const_flag = if ctx.lookup_const_flag(name) { 1 } else { 0 }; ctx.inst(Inst::new( OpCode::STORE_UPVALUE, - Operand::None, + Operand::Imm(const_flag), Operand::Reg(new_reg), Operand::Imm(uv as u16), )); diff --git a/crates/oxide_emit/src/shared/class_field.rs b/crates/oxide_emit/src/shared/class_field.rs index 6f81e0d..c669115 100644 --- a/crates/oxide_emit/src/shared/class_field.rs +++ b/crates/oxide_emit/src/shared/class_field.rs @@ -66,13 +66,14 @@ impl Emitter { } pub(crate) fn emit_private_method_init( - &self, target: Operand, method: &oxide_parser::MethodDefinition, home_reg: Operand, ctx: &mut CompileCtx, + &self, target: Operand, method: &oxide_parser::MethodDefinition, home_reg: Operand, + self_binding: &[(&str, u32)], class_self_cell: Option, ctx: &mut CompileCtx, ) -> Result<(), String> { let PropertyKey::PrivateIdentifier(private) = &method.key else { return Err("expected private method key".into()); }; let name = private.name.as_str(); - let method_reg = self.emit_class_method_function(method, name, home_reg, ctx, &[])?; + let method_reg = self.emit_class_method_function(method, name, home_reg, ctx, self_binding, class_self_cell)?; match method.kind { MethodDefinitionKind::Method => { let key_reg = self.emit_private_id_reg(name, ctx)?; diff --git a/crates/oxide_emit/src/stmt/declaration/class_decl.rs b/crates/oxide_emit/src/stmt/declaration/class_decl.rs index a5d6e81..421bfed 100644 --- a/crates/oxide_emit/src/stmt/declaration/class_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/class_decl.rs @@ -1,9 +1,6 @@ //! 类声明语句 emit:`emit_class_declaration_statement` 声明类名并初始化类对象。 use crate::{CompileCtx, Emitter}; -use oxide_bytecode::opcode::OpCode; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; use oxide_parser::{Statement, VariableDeclarationKind}; impl Emitter { @@ -28,17 +25,7 @@ impl Emitter { }; // 类名绑定在 emit_class_with_binding 内于 extends 求值后初始化: // 先声明未初始化绑定(TDZ),extends 引用类名按规范抛 ReferenceError。 - let ctor_reg = self.emit_class_with_binding(class, ctx, Some(var_reg))?; - if let Some(&cell_idx) = ctx.captured_bindings.get(&name) { - ctx.inst(Inst::new( - OpCode::MAKE_CELL, - Operand::Reg(var_reg), - Operand::Imm(cell_idx as u16), - Operand::None, - )); - } else { - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(ctor_reg), Operand::None)); - } + self.emit_class_with_binding(class, ctx, Some(var_reg))?; Ok(None) } } diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 601fb7b..de2c4c6 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -323,7 +323,8 @@ impl Vm { } #[allow(dead_code)] - pub(crate) fn dispatch_store_upvalue(&mut self, a: usize, b: usize) -> Result<(), String> { + pub(crate) fn dispatch_store_upvalue(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { + let const_flag = rd; let uv_idx = b; let src_val = self.regs[a]; if let Some(callee) = self.current_callee() { @@ -334,6 +335,10 @@ impl Vm { if !upvals[uv_idx].is_null() { unsafe { let cell = &mut *upvals[uv_idx]; + if const_flag != 0 && !cell.value.is_undefined() { + self.raise_error_kind("TypeError", "Assignment to constant variable")?; + return Ok(()); + } cell.value = src_val; cell.set_initialized(true); } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 61d219d..36d32d5 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1244,7 +1244,7 @@ impl Vm { self.dispatch_load_upvalue(rd, instr)?; } OpCode::STORE_UPVALUE => { - self.dispatch_store_upvalue(a, b)?; + self.dispatch_store_upvalue(rd, a, b)?; } OpCode::CREATE_REGEXP => match self.dispatch_create_regexp(rd, a, b) { Ok(Some(result)) => return Ok(result),