From c52bf6313e38337f503167b7f6fabd34b166ab47 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: Thu, 6 Aug 2026 16:03:09 +0800 Subject: [PATCH] =?UTF-8?q?fix(emit):=20const=20=E5=A3=B0=E6=98=8E=20STORE?= =?UTF-8?q?=5FVAR=20=E6=81=92=20b=3D0=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=AF=84?= =?UTF-8?q?=E5=AD=98=E5=99=A8=E5=A4=8D=E7=94=A8=E8=A7=A6=E5=8F=91=E8=AF=AF?= =?UTF-8?q?=E6=8A=9B=EF=BC=88B011=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/shared/binding.rs | 6 ++- crates/oxide_vm/tests/const_guard_tests.rs | 52 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 crates/oxide_vm/tests/const_guard_tests.rs diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index 291d098..d400c48 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -49,7 +49,11 @@ impl Emitter { if let Some(&cell_idx) = ctx.captured_bindings.get(name) { ctx.inst(Inst::new(OpCode::MAKE_CELL, Operand::Reg(src_reg as u32), Operand::Imm(cell_idx as u16), Operand::None)); } else { - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg as u32), Operand::Reg(src_reg as u32), Operand::Imm(if is_const { 1 } else { 0 }))); + // B011: const 声明路径 STORE_VAR 恒 b=0,不查运行时 guard。 + // guard 读 regs[rd] 判断槽是否已初始化,依赖槽初始为 undefined; + // 声明冗余检查由编译期 declare() 完成(重复声明编译报错), + // 运行时 guard 只服务"对 const 再赋值"(赋值路径,见 emit_assign_target)。 + ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg as u32), Operand::Reg(src_reg as u32), Operand::Imm(0))); } ctx.init_var(name); Ok(()) diff --git a/crates/oxide_vm/tests/const_guard_tests.rs b/crates/oxide_vm/tests/const_guard_tests.rs new file mode 100644 index 0000000..fddfb03 --- /dev/null +++ b/crates/oxide_vm/tests/const_guard_tests.rs @@ -0,0 +1,52 @@ +//! B011 const guard 回归测试:const 声明路径不依赖寄存器槽新鲜度。 +//! +//! 背景:`dispatch_store_var` 的 const guard(b!=0)读 `regs[rd]` 判断目标槽是否已初始化。 +//! 声明路径带 b=1 时,checkpoint/寄存器复用残留陈旧值会误抛 "Assignment to constant variable"。 +//! Phase 5 RegAlloc 复用寄存器后此 bug 必现——本测试锁死声明路径恒 b=0 的行为。 + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +// B011 复现样例:switch 语句(checkpoint 复用槽)后跟 const 声明。 +// 修复前误抛 "Assignment to constant variable",修复后输出 5。 +#[test] +fn switch_then_const_declaration_no_false_positive() { + assert_eq!(eval("switch (1) { case 1: break; } const x = 5; x"), "5"); +} + +// 锚:对已声明 const 再赋值仍必须抛错(guard 保留在赋值路径)。 +#[test] +fn const_reassignment_still_throws() { + let out = eval("const x = 5; x = 6"); + assert!(out.contains("Assignment to constant variable"), "got: {out}"); +} + +// 锚:块级 const 遮蔽互不影响(内层 const 不污染外层槽)。 +#[test] +fn block_scoped_const_shadowing_independent() { + assert_eq!(eval("const x = 5; { const x = 1; } x"), "5"); +} + +// 锚:解构 const 声明正常(emit_object_binding → emit_bind_target)。 +#[test] +fn const_destructuring_declaration_works() { + assert_eq!(eval("const { a } = { a: 7 }; a"), "7"); +}