From 3de3b639d9d3139fcda21e5cd04cc7a25cb962cf Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 17 Sep 2026 19:09:22 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20switch=20=E8=AF=AD=E5=8F=A5=E8=A1=A5=20c?= =?UTF-8?q?ase=20=E5=9D=97=E4=BD=9C=E7=94=A8=E5=9F=9F=E4=B8=8E=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E9=A2=84=E5=A3=B0=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/compile_ctx.rs | 3 +- crates/oxide_emit/src/prepass/declare.rs | 49 +----- crates/oxide_emit/src/stmt/block.rs | 11 +- crates/oxide_emit/src/stmt/switch.rs | 23 +++ .../tests/decl_lex_restricted_global_tests.rs | 7 +- crates/oxide_vm/tests/switch_case_fn_tests.rs | 153 ++++++++++++++++++ 6 files changed, 199 insertions(+), 47 deletions(-) create mode 100644 crates/oxide_vm/tests/switch_case_fn_tests.rs diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index 2ce17141..a2dd1337 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -81,7 +81,8 @@ pub struct CompileCtx { /// (形参 ∪ 函数作用域树内词法声明名):抑制集内名字退化为纯块作用域;预声明期定稿,实例化与声明点写回两侧同查。 pub(crate) block_fn_suppressed: HashSet, /// 块入口已物化声明登记表(按块层级压/弹):块直接子与标签直接子体 - /// 的函数声明在块入口物化闭包后,按 名 → 声明节点 arena 指针 登记。 + /// 的函数声明在块入口物化闭包后,按 名 → 声明节点 arena 指针 登记(同名 + /// 重复声明只登记首个节点,任一直接子声明点都复用块槽取入口当前值)。 /// 声明点据此三分:同节点命中(本声明已入口物化)→ 复用块槽保闭包 /// 同一;仅同名命中(支臂声明与直接子同名共享块槽)→ 闭包落 fresh /// 寄存器只写回外层 var、块槽不动;均未命中 → 执行期自物化入块槽。 diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index d0c447af..f83e349b 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -172,8 +172,8 @@ impl Emitter { /// 与 emit 阶段嵌套块 push_scope 的时机对齐。 /// /// # 边界与前提 - /// - 只处理块内函数声明;switch 不推 scope(其 case 内函数声明随 switch - /// 作用域修复一并处理);export 声明不可能出现在块内。 + /// - 只处理块内函数声明;switch case 的 CaseBlock 环境由 `emit_switch_statement` + /// 自行建立并预声明,不在此递归;export 声明不可能出现在块内。 /// - `if_arm` 标记当前递归是否位于 `if` 支臂隐式块内。Annex B 下该隐式块的 /// 函数声明名已有外层 var 绑定承载(非 eval 脚本顶层、或 sloppy eval 顶层; /// 未被形参/词法同名抑制,且非全局只读三常量)时不建当前块绑定,声明点 @@ -280,15 +280,15 @@ impl Emitter { /// 声明点复用预登记槽位。 /// /// # 边界与前提 - /// - 只扫描直接子语句 + 递归 switch case(switch 不推 scope,case 内 lexical - /// 声明属于外层作用域);不递归块/if/for/while body(嵌套块自预声明; - /// 单语句 body 不接受 lexical 声明——lexical 属 Declaration、非 Statement - /// 子产生式,parser 按语法错误直接拒绝,这些形状不会进入 emit)。 + /// - 只扫描直接子语句;不递归块/if/for/while/switch case/try 区(嵌套块与 + /// switch CaseBlock 各自预声明;单语句 body 不接受 lexical 声明——lexical + /// 属 Declaration、非 Statement 子产生式,parser 按语法错误直接拒绝,这些 + /// 形状不会进入 emit)。 /// - 跳过 for 头声明(循环作用域由 for 分支内联 declare)。 /// - `global_lexical` 为 true 时(仅脚本顶层调用点传 `!is_eval_script`): /// lexical 声明撞受限全局名报 SyntaxError(脚本声明实例化对全局对象受限 - /// 自有属性名做检查,eval 代码声明实例化无此检查);函数体/块/try/模块 - /// 调用点传 false,lexical 声明是局部绑定不查。 + /// 自有属性名做检查,eval 代码声明实例化无此检查);函数体/块/switch case/ + /// try/模块调用点传 false,lexical 声明是局部绑定不查。 pub(crate) fn predeclare_lexical_declarations( &self, statements: &[Statement], ctx: &mut CompileCtx, global_lexical: bool, ) -> Result<(), String> { @@ -310,13 +310,6 @@ impl Emitter { let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); } } - Statement::SwitchStatement(sw) => { - for case in &sw.cases { - for s in &case.consequent { - self.predeclare_lexical_stmt(s, ctx, global_lexical)?; - } - } - } Statement::ExportNamedDeclaration(exp) => { if let Some(decl) = &exp.declaration { match decl { @@ -369,32 +362,6 @@ impl Emitter { Ok(()) } - /// 预声明单个语句中的 lexical 声明(供 switch case 递归;`let`/`const`/`class` 分支)。 - fn predeclare_lexical_stmt( - &self, stmt: &Statement, ctx: &mut CompileCtx, global_lexical: bool, - ) -> Result<(), String> { - match stmt { - Statement::VariableDeclaration(decl) => { - if matches!(decl.kind, VariableDeclarationKind::Var) { - return Ok(()); - } - let is_const = matches!(decl.kind, VariableDeclarationKind::Const); - for d in &decl.declarations { - self.predeclare_lexical_pattern(&d.id, is_const, ctx, global_lexical)?; - } - } - Statement::ClassDeclaration(cd) => { - if let Some(id) = &cd.id { - check_restricted_global_lexical(id.name.as_str(), global_lexical)?; - let reg = ctx.alloc_reg(); - let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); - } - } - _ => {} - } - Ok(()) - } - /// 递归预声明绑定 pattern 内的全部标识符(含数组/对象/默认值解构)。 fn predeclare_lexical_pattern( &self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx, global_lexical: bool, diff --git a/crates/oxide_emit/src/stmt/block.rs b/crates/oxide_emit/src/stmt/block.rs index 09cd512d..46db1229 100644 --- a/crates/oxide_emit/src/stmt/block.rs +++ b/crates/oxide_emit/src/stmt/block.rs @@ -48,7 +48,13 @@ impl Emitter { /// # 副作用 /// - 当前指令流在块入口发 CREATE_CLOSURE(及 MAKE_CELL / STORE_VAR); /// 物化登记写入 `block_fn_entry_mats` 栈顶帧。 - fn emit_block_fn_entry_init_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<(), String> { + /// + /// # 注意事项 + /// - 同名重复的直接子声明只登记首个节点:该名任一直接子声明点都复用块槽 + /// (取其当前值),而非重编本节点闭包——块绑定入口值按源序末次物化, + /// 复用槽才与外层 var 写回后的块槽值一致(仅登记末次会让较早声明点把 + /// 本节点闭包写回外层 var,与规范的块绑定写回口径不符)。 + pub(crate) fn emit_block_fn_entry_init_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<(), String> { match stmt { Statement::FunctionDeclaration(fd) => { let Some(id) = &fd.id else { @@ -61,7 +67,8 @@ impl Emitter { ctx.block_fn_entry_mats .last_mut() .unwrap() - .insert(name, &**fd as *const _ as *const ()); + .entry(name) + .or_insert_with(|| &**fd as *const _ as *const ()); Ok(()) } Statement::LabeledStatement(ls) => self.emit_block_fn_entry_init_stmt(&ls.body, ctx), diff --git a/crates/oxide_emit/src/stmt/switch.rs b/crates/oxide_emit/src/stmt/switch.rs index 7ef8365d..162e2283 100644 --- a/crates/oxide_emit/src/stmt/switch.rs +++ b/crates/oxide_emit/src/stmt/switch.rs @@ -1,4 +1,8 @@ //! switch 语句 emit:case 匹配链 + 穿落(fallthrough)与 default,见 `emit_switch_statement`。 +//! CaseBlock 有独立块作用域:case 选择表达式与全部 case 体共享同一环境,case 内 +//! 函数/词法声明不泄漏到 switch 外。 + +use std::collections::HashMap; use crate::{CompileCtx, Emitter}; use oxide_bytecode::opcode::OpCode; @@ -13,8 +17,25 @@ impl Emitter { }; let end_label = ctx.next_label_id(); ctx.push_switch(end_label); + // 判别式在 switch 外层词法环境求值,CaseBlock 环境尚未建立。 let disc_reg = self.emit_expression(&sw.discriminant, ctx)?; let cases = &sw.cases; + // CaseBlock 环境:case 选择表达式与全部 case 体同处一个块作用域。 + // 块函数预声明先于 lexical,使 `case 0: let g; function g(){}` 的 lexical + // 占位命中已存在的函数绑定,报重复声明错。 + ctx.push_scope(); + for case in cases.iter() { + self.predeclare_block_function_declarations(&case.consequent, ctx, false); + self.predeclare_lexical_declarations(&case.consequent, ctx, false)?; + } + // case 内函数声明的块入口初始化:同名重复声明按源序物化,末次声明成为 + // 入口值;声明点复用块槽写回外层 var(见 emit_function_declaration)。 + ctx.block_fn_entry_mats.push(HashMap::new()); + for case in cases.iter() { + for s in &case.consequent { + self.emit_block_fn_entry_init_stmt(s, ctx)?; + } + } let mut case_labels = Vec::with_capacity(cases.len()); for case in cases.iter() { let case_label = ctx.next_label_id(); @@ -45,7 +66,9 @@ impl Emitter { } } } + ctx.block_fn_entry_mats.pop(); ctx.labels.set_label_pos(end_label, ctx.insts.len()); + ctx.pop_scope(); ctx.pop_switch(); // switch 完成值 = 已执行 case 链最后一次非空完成值(源序最后非空语句的 // 收敛寄存器:命中后穿落到 break/尾即为其值,无命中则该寄存器不被写入而 diff --git a/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs b/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs index 7e6b03d9..e6e515d1 100644 --- a/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs +++ b/crates/oxide_vm/tests/decl_lex_restricted_global_tests.rs @@ -86,9 +86,10 @@ fn top_level_let_destructured_nan_rejected() { } #[test] -fn top_level_switch_case_let_nan_rejected() { - // switch 不推作用域:case 内 lexical 声明属全局 lexical,仍受检。 - assert_compile_err("switch (0) { case 0: let NaN; }"); +fn top_level_switch_case_let_nan_allowed() { + // switch case 有独立 CaseBlock 作用域:case 内 lexical 是块局部绑定,不受限检查。 + let r = assert_ok("switch (0) { case 0: let NaN; } 1"); + assert_eq!(r.as_int(), 1); } // ── eval 门控:eval 代码声明实例化无受限检查 ── diff --git a/crates/oxide_vm/tests/switch_case_fn_tests.rs b/crates/oxide_vm/tests/switch_case_fn_tests.rs new file mode 100644 index 00000000..bbff33c3 --- /dev/null +++ b/crates/oxide_vm/tests/switch_case_fn_tests.rs @@ -0,0 +1,153 @@ +//! switch CaseBlock 块作用域:case 内函数/词法声明按块绑定,switch 外不可见; +//! sloppy 下 case 内函数声明的外层 var 半仍按 Annex B 写回,块绑定在 CaseBlock +//! 入口即持有函数对象(声明点前读命中函数)。 +//! +//! 覆盖:strict 不泄漏、声明点前读、跨 case 可见、同名重复源序末位、lexical +//! (let/const/class)不泄漏与遮蔽、嵌套块隔离、var 穿透提升、标签直接子体、 +//! 捕获逃逸、块绑定与外层 var 独立、重复声明拒绝、完成值守卫。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?; + let mut vm = Vm::new(); + vm.run(&Arc::new(module)) +} + +/// 求值为字符串的用例:断言 `is_string` 后取内容。 +fn eval_str(source: &str) -> String { + let value = eval(source).unwrap(); + assert!(value.is_string(), "expected string, got {value:?}"); + // SAFETY: is_string 已确认值为字符串指针,借用仅在断言语句内消费。 + unsafe { &*value.as_string_ptr() }.as_str().to_string() +} + +#[test] +fn switch_case_function_strict_not_leaked() { + // strict 下 case 内函数声明是 CaseBlock 块绑定,switch 外不可见。 + assert_eq!(eval_str("'use strict'; switch(0){case 0:function g(){}} typeof g"), "undefined"); +} + +#[test] +fn switch_case_function_read_before_decl() { + // 块函数在 CaseBlock 入口物化:声明语句前读为函数对象。 + assert_eq!( + eval_str("function f(){ switch(0){case 0: var t=typeof g; function g(){return 1}; return t } } f()"), + "function" + ); +} + +#[test] +fn switch_case_function_visible_across_cases() { + // 未命中 case 的函数声明同样参与入口物化,case 选择与环境无关。 + assert_eq!(eval_str("switch(1){case 0:function g(){return 1}; default: typeof g }"), "function"); +} + +#[test] +fn switch_case_duplicate_function_last_wins() { + // 同名重复声明共享 CaseBlock 块绑定:入口值取源序末次声明。 + assert_eq!( + eval("switch(0){case 0:function g(){return 1}; break; case 1:function g(){return 2}} g()") + .unwrap() + .as_int(), + 2 + ); +} + +#[test] +fn switch_case_lexical_not_leaked() { + // case 内 lexical 属 CaseBlock,switch 外读取抛 ReferenceError。 + let err = eval("switch(0){default:const x=1;} x").unwrap_err(); + assert!(err.contains("is not defined"), "got: {err}"); +} + +#[test] +fn switch_case_let_shadows_outer() { + // case 内 let 遮蔽外层同名绑定,switch 外读仍是外层值。 + assert_eq!(eval("let x=1; switch(0){case 0:let x=2;} x").unwrap().as_int(), 1); +} + +#[test] +fn switch_case_const_not_leaked() { + let err = eval("switch(0){case 0:const c=1;} c").unwrap_err(); + assert!(err.contains("is not defined"), "got: {err}"); +} + +#[test] +fn switch_case_class_not_leaked() { + assert_eq!(eval_str("switch(0){case 0:class C{}} typeof C"), "undefined"); +} + +#[test] +fn switch_case_lexical_tdz_before_decl() { + // 声明点前读 case 内 let:命中 TDZ 占位而非隐式全局。 + let err = eval("function f(){ switch(0){case 0: return x; case 1: let x=1;} } f()").unwrap_err(); + assert!(err.contains("before initialization"), "got: {err}"); +} + +#[test] +fn switch_case_nested_block_lexical_not_leaked() { + // case 内嵌套块的 lexical 归嵌套块自身,不泄漏到 CaseBlock。 + assert_eq!( + eval_str("function f(){ switch(0){case 0:{ let y=3; }} return typeof y; } f()"), + "undefined" + ); +} + +#[test] +fn switch_case_var_hoists_to_function_scope() { + // var 声明穿透 CaseBlock 提升到函数作用域。 + assert_eq!( + eval("function f(){ switch(0){case 0: var x=1;} return x; } f()") + .unwrap() + .as_int(), + 1 + ); +} + +#[test] +fn switch_case_labeled_function_leaks_outer() { + // 标签直接子体函数声明同样按块函数处理,sloppy 外层 var 写回。 + assert_eq!(eval_str("switch(0){case 0: l: function g(){return 1}} typeof g"), "function"); +} + +#[test] +fn switch_case_function_captured_by_outer() { + // case 内函数被外层引用(捕获路径)后块外可调用。 + assert_eq!( + eval("var f; switch(0){case 0: function g(){return 5}; f=g; } f()") + .unwrap() + .as_int(), + 5 + ); +} + +#[test] +fn switch_case_function_binding_independent_of_outer_var() { + // 块绑定可变且与外层 var 独立:块内对 f 重新赋值不改写外层 var 绑定。 + let src = "var r1, r2; (function(){ var initialBV, currentBV; \ + switch(1){case 1: function f(){ initialBV=f; f=123; currentBV=f; return 'decl'; }} \ + var varBinding=f; f(); r1 = initialBV() === 'decl'; \ + r2 = currentBV === 123 && varBinding() === 'decl'; }()); r1 && r2"; + assert!(eval(src).unwrap().as_bool(), "块绑定应独立于外层 var 绑定"); +} + +#[test] +fn switch_case_function_let_conflict_rejected() { + // case 内 let 与函数声明同名:重复声明早期错误。 + let err = eval("switch(0){case 0:let g; function g(){}}").unwrap_err(); + assert!(err.contains("already been declared"), "got: {err}"); +} + +#[test] +fn switch_case_completion_value_preserved() { + // CaseBlock 作用域压/弹不影响 switch 完成值收敛。 + assert_eq!(eval("switch(1){case 1: 42}").unwrap().as_int(), 42); + assert_eq!(eval("switch(2){case 1: 42; default: 7}").unwrap().as_int(), 7); +}