diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index 233147b1..af25ee10 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -174,6 +174,11 @@ impl Emitter { /// # 边界与前提 /// - 只处理块内函数声明;switch 不推 scope(其 case 内函数声明随 switch /// 作用域修复一并处理);export 声明不可能出现在块内。 + /// - `if` 支臂的裸函数声明属 Annex B 隐式支臂块:名已有外层 var 绑定承载 + /// (非 eval 且未被形参/词法同名抑制)时不建外层块绑定,声明点物化进该 + /// var 槽(建外层块 Let 会遮蔽同名 var);名无外层 var 绑定(形参/词法抑制 + /// 或 eval 脚本顶层)时保留块级绑定承载支臂闭包,避免声明点覆写外层绑定。 + /// 支臂为块时由该块自身的块入口流程处理。 /// - 与 lexical 预声明的顺序:本函数在前,`{ let g; function g(){} }` 时 /// lexical 的 `declare_predeclared` 命中已存在的函数绑定自然报重复声明错, /// 避免函数预声明被 lexical 占位静默覆盖而破坏 let 的 TDZ。 @@ -195,9 +200,9 @@ impl Emitter { ctx.pop_scope(); } Statement::IfStatement(is) => { - self.predeclare_block_function_declarations(std::slice::from_ref(&is.consequent), ctx); + self.predeclare_if_arm_block_function(&is.consequent, ctx); if let Some(alt) = &is.alternate { - self.predeclare_block_function_declarations(std::slice::from_ref(alt), ctx); + self.predeclare_if_arm_block_function(alt, ctx); } } Statement::WhileStatement(wh) => { @@ -248,6 +253,36 @@ impl Emitter { } } + /// 预声明 `if` 支臂裸函数声明的块级绑定(展开标签链后取函数声明):仅当该 + /// 名没有外层 var 绑定承载时才在当前块建块 Let,承载支臂闭包,避免声明点 + /// 覆写外层形参/词法绑定;名已有外层 var 绑定(非 eval 且未被形参/词法同名 + /// 抑制)时不建块级绑定,声明点物化进该 var 槽。 + /// + /// # 边界与前提 + /// - 支臂为块时返回:该块自身的块入口流程预声明其函数声明。 + /// - 支臂为其它语句(含嵌套 `if`)时返回:嵌套 `if` 由 emit 到达时经本函数 + /// 的 `IfStatement` 臂处理。 + /// - 须在 `block_fn_suppressed` 已构建之后调用(函数体/程序声明实例化阶段 + /// 先于任何块 emit);`is_eval_script` 仅标记 eval 脚本顶层程序,其顶层 + /// 不建块级函数外层 var 绑定。 + fn predeclare_if_arm_block_function(&self, stmt: &Statement, ctx: &mut CompileCtx) { + let mut cur = stmt; + while let Statement::LabeledStatement(ls) = cur { + cur = &ls.body; + } + let Statement::FunctionDeclaration(f) = cur else { + return; + }; + let Some(identifier) = &f.id else { + return; + }; + if !ctx.is_eval_script && !ctx.block_fn_suppressed.contains(identifier.name.as_str()) { + return; + } + let reg = ctx.alloc_reg(); + let _ = ctx.declare_initialized(identifier.name.as_str(), reg, VariableDeclarationKind::Let, false); + } + /// 预声明当前作用域直接子语句中的 `let`/`const`/`class` 绑定(未初始化, /// 建立 TDZ 占位)。使声明点之前的读取在编译期可分辨为 TDZ 而非隐式全局, /// 声明点复用预登记槽位。 diff --git a/crates/oxide_vm/tests/block_fn_if_arm_var_shadow_tests.rs b/crates/oxide_vm/tests/block_fn_if_arm_var_shadow_tests.rs new file mode 100644 index 00000000..ae3fdea4 --- /dev/null +++ b/crates/oxide_vm/tests/block_fn_if_arm_var_shadow_tests.rs @@ -0,0 +1,104 @@ +//! 块内 `if` 支臂裸函数声明与同名 `var` 的绑定归属:Annex B 下支臂函数声明属 +//! 隐式支臂块,其名在 var 面落函数/全局作用域 `var`,不得在外层块建块级绑定 +//! 遮蔽该 `var`;块内前读须见 `var` 值、外层读须见支臂求值写回。 +//! +//! 覆盖:块内前读/typeof 前读/后读、`if(false)` 未求值支、标签包裹支臂、支臂 +//! 前 `var` 重赋值、块外后读、外层词法/形参同名不覆写、strict 早期错误。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> Result { + let allocator = 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)) +} + +fn eval_str(source: &str) -> Result { + let value = eval(source)?; + if value.is_string() { + // SAFETY: 值已确认字符串类别,指针指向 VM 存活期内的 JsString。 + Ok(unsafe { &*value.as_string_ptr() }.as_str().to_string()) + } else { + Err(format!("expected string, got {value:?}")) + } +} + +#[test] +fn if_arm_fn_var_read_before_branch_sees_var() { + // 主形:支臂前读与支臂同名的块内 `var`,须见 `var` 已写的 1, + // 不得取到支臂块绑定槽的陈旧值。 + let source = "function f(){ var g=1; { var r=g; if(true) function g(){return 2}; return r } } f()"; + assert_eq!(eval(source).unwrap().as_int(), 1); +} + +#[test] +fn if_arm_fn_var_typeof_before_branch_sees_var() { + // 主形变体:支臂前 `typeof` 读同名 `var`,须见 "number"。 + let source = "function f(){ var g=1; { var t=typeof g; if(true) function g(){return 2}; return t } } f()"; + assert_eq!(eval_str(source).unwrap(), "number"); +} + +#[test] +fn if_arm_fn_var_read_before_untaken_branch_sees_var() { + // if(false) 支臂不求值:块外后读仍见 `var` 值 1。 + let source = "function f(){ var g=1; { if(false) function g(){}; } return g } f()"; + assert_eq!(eval(source).unwrap().as_int(), 1); +} + +#[test] +fn if_arm_fn_var_read_after_taken_branch_calls_arm_fn() { + // 支臂求值后块内读:闭包已写回同名 `var`,调用得支臂返回值 2。 + let source = "function f(){ var g=1; { if(true) function g(){return 2}; return g() } } f()"; + assert_eq!(eval(source).unwrap().as_int(), 2); +} + +#[test] +fn labeled_if_arm_fn_var_read_before_branch_sees_var() { + // 标签包裹的 if 支臂走同一路径:支臂前 `typeof` 见 `var` 值 "number"。 + let source = "function f(){ var g=1; { var t=typeof g; l: if(true) function g(){return 2}; return t } } f()"; + assert_eq!(eval_str(source).unwrap(), "number"); +} + +#[test] +fn if_arm_fn_var_reassigned_between_reads() { + // 前读记为 `var` 原值、支臂求值写回后读为函数:读数与求值顺序一致。 + // 结果经布尔比对,避免在 VM 释放后读取拼接字符串指针。 + let source = "function f(){ var g=1; { var r=g; var g=2; if(true) function g(){return 2}; \ + return r + ' ' + typeof g === '1 function' } } f()"; + assert!(eval(source).unwrap().as_bool()); +} + +#[test] +fn if_arm_fn_var_outer_read_after_block_is_fn() { + // 块外后读:支臂闭包经外层 `var` 写回泄漏块外,`typeof` 见 "function"。 + let source = "function f(){ var g=1; { if(true) function g(){return 2}; } return typeof g } f()"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn if_arm_fn_keeps_outer_let_binding() { + // 守卫:函数体顶层 `let` 与嵌套块内支臂函数同名时,支臂不覆写该词法绑定。 + let source = "function f(){ let g=1; { if(true) function g(){} } return g } f()"; + assert_eq!(eval(source).unwrap().as_int(), 1); +} + +#[test] +fn if_arm_fn_keeps_outer_param_binding() { + // 守卫:形参与嵌套块内支臂函数同名时,支臂不覆写形参值。 + let source = "function f(g){ { if(true) function g(){} } return g } f(42)"; + assert_eq!(eval(source).unwrap().as_int(), 42); +} + +#[test] +fn if_arm_fn_strict_is_early_error() { + // strict 代码中 if 子句内的函数声明是早期错误(Annex B 仅 sloppy)。 + let err = eval("'use strict'; if(true) function g(){}").unwrap_err(); + assert!(err.contains("Invalid function declaration"), "got: {err}"); +}