From 320c660620704df5c44d933b8b0e6779d5e9818c Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 15 Sep 2026 03:48:19 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9D=97=E7=BA=A7=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=E8=A1=A5=20web-compat=20=E5=A4=96=E5=B1=82?= =?UTF-8?q?=20var=20=E7=BB=91=E5=AE=9A=E5=AE=9E=E4=BE=8B=E5=8C=96=E4=B8=8E?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=E7=82=B9=E5=86=99=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/emit.rs | 63 +++++- crates/oxide_emit/src/prepass.rs | 196 +++++++++++++++++- .../src/stmt/declaration/function_decl.rs | 29 ++- crates/oxide_emit/src/symbol_table.rs | 2 +- .../oxide_vm/tests/block_fn_var_head_tests.rs | 127 ++++++++++++ .../tests/block_function_decl_tests.rs | 10 +- 6 files changed, 418 insertions(+), 9 deletions(-) create mode 100644 crates/oxide_vm/tests/block_fn_var_head_tests.rs diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index c33f2d3b..22c1293b 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -216,6 +216,13 @@ pub struct CompileCtx { pub(crate) current_upvalue_captures: Vec, /// 本函数作用域声明的绑定名(参数 + 变量/函数声明,AST 收集,emit 前确定)。 pub(crate) own_bindings: HashSet, + /// 本函数形参名集(含解构形参叶子与 rest,编译入口收集):块级函数名与 + /// 形参同名时不建外层绑定、不求值写回(规范 paramNames 守卫面)。子函数 + /// ctx 为新建,不继承。 + pub(crate) param_names: HashSet, + /// 块级函数名 web-compat 外层绑定抑制集(形参 ∪ 函数作用域树内词法声明名): + /// 抑制集内名字退化为纯块作用域。预声明期定稿,实例化与声明点写回两侧同查。 + pub(crate) block_fn_suppressed: HashSet, /// 本函数被嵌套函数捕获的绑定名 → cell_idx(名字排序分配,稳定跨 run)。 /// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射, /// 消除符号表时序依赖与 cell 索引错位。 @@ -362,6 +369,8 @@ impl CompileCtx { class_keys_reg: None, current_upvalue_captures: Vec::new(), own_bindings: HashSet::new(), + param_names: HashSet::new(), + block_fn_suppressed: HashSet::new(), captured_bindings: BTreeMap::new(), global_tier_names: HashSet::new(), upvalue_const_flags: HashSet::new(), @@ -1147,6 +1156,16 @@ impl Emitter { // 类元素方法/构造器恒 strict(ClassBody 是严格模式代码)。 ctx.is_strict = own_strict || matches!(body_context, FunctionBodyContext::ClassElement) || parent_ctx.is_strict; + // 形参名集(含解构叶子与 rest):块级函数名同名的 web-compat 守卫查此集。 + let mut param_names = HashSet::new(); + for spec in param_specs { + param_names.insert(spec.register_name().to_string()); + if let ParamSpec::Pattern { pattern, .. } = spec { + self.collect_binding_pattern_names(pattern, &mut param_names); + } + } + ctx.param_names = param_names; + // length = 第一个带默认值形参之前的形参数(解构默认与标识符默认同规则); // rest 参数不计入 length(以 0 结尾即止)。 ctx.function_length = param_specs @@ -1253,6 +1272,19 @@ impl Emitter { // 局部绑定,不做受限全局名检查;重复声明错在 emit 期报。 let _ = self.predeclare_lexical_declarations(body_stmts, &mut ctx, false); + // 块级函数名 web-compat 外层 var 绑定(sloppy):块内函数声明名在实例化 + // 期于函数作用域建外层 var 绑定——无同名绑定则新建 var 槽;名在抑制集 + // (形参/词法声明同名,该形退化为纯块作用域)则不建。求值期声明点把 + // 函数对象写回此绑定(见函数声明 emit)。 + if !ctx.is_strict { + ctx.block_fn_suppressed = self.collect_block_fn_suppressed_names(body_stmts, &ctx.param_names); + for name in self.collect_block_function_names(body_stmts) { + if !ctx.block_fn_suppressed.contains(&name) { + self.predeclare_var_name(&name, &mut ctx); + } + } + } + // 生成器:body 起点标记——调用时参数初始化(emit_params_prologue)结束后挂起于此, // 参数副作用/异常在 `g()` 调用时刻生效,首次 next() 从这继续执行 body。 if is_generator { @@ -1801,6 +1833,20 @@ impl Emitter { let global_lexical = !ctx.is_eval_script; self.predeclare_lexical_declarations(&program.body, &mut ctx, global_lexical)?; + // 顶层块级函数名 web-compat 外层绑定(sloppy、非 eval):实例化 var 绑定 + // (新建 var 槽;顶层 builtin 名不可声明不建)、并入顶层 var 名集(裸读/ + // 写路由全局对象属性)、GDI 序言建属性(define-if-absent,既有属性零动作)。 + let mut block_fn_names: Vec = Vec::new(); + if !ctx.is_strict && !ctx.is_eval_script { + ctx.block_fn_suppressed = self.collect_block_fn_suppressed_names(&program.body, &ctx.param_names); + block_fn_names = self.collect_block_function_names(&program.body); + for name in &block_fn_names { + if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_known_builtin(name) { + self.predeclare_var_name(name, &mut ctx); + } + } + } + // 闭包捕获分析(AST 级,emit 前确定) ctx.own_bindings = self.collect_own_binding_names(&[], &program.body); // 顶层已声明名(A 侧单一真值):裸读走全局对象属性、裸写走描述符感知 @@ -1811,6 +1857,12 @@ impl Emitter { let var_names = self.collect_var_binding_names(&program.body); let mut tier_names = var_names.clone(); tier_names.extend(self.collect_top_level_function_names(&program.body)); + // 块级函数泄漏名并入:求值期写回与头写同走全局对象属性(A 侧单一真值)。 + for name in &block_fn_names { + if !ctx.block_fn_suppressed.contains(name) && !CompileCtx::is_known_builtin(name) { + tier_names.insert(name.clone()); + } + } ctx.global_tier_names = tier_names; ctx.captured_bindings = self.collect_captured_bindings(&program.body, &[], &ctx.own_bindings); ctx.captured_bindings.retain(|n, _| !ctx.global_tier_names.contains(n)); @@ -1846,8 +1898,15 @@ impl Emitter { // 新建,可写/不可写/可配置既有属性(含值)一律保留。builtin 名的全局属性 // 运行期预存(session 绑定):缺失分支写入值取 builtin 镜像槽(run 起点 // 预载全局属性值),既有属性零动作,值幂等保留。 - // 序言名集 var-only,不随 tier 集扩宽。 - let gdi_var_names = self.collect_var_binding_names(&program.body); + // 序言名集 = 顶层 var 名 ∪ 顶层块级函数泄漏名(web-compat 外层绑定同样 + // 在求值前实例化,同走 define-if-absent)。 + let mut gdi_var_names = self.collect_var_binding_names(&program.body); + gdi_var_names.extend( + block_fn_names + .iter() + .filter(|n| !ctx.block_fn_suppressed.contains(*n) && !CompileCtx::is_known_builtin(n)) + .cloned(), + ); if !gdi_var_names.is_empty() { let undef_reg = self.emit_undefined(&mut ctx); for name in &gdi_var_names { diff --git a/crates/oxide_emit/src/prepass.rs b/crates/oxide_emit/src/prepass.rs index c69e11de..b6b80f92 100644 --- a/crates/oxide_emit/src/prepass.rs +++ b/crates/oxide_emit/src/prepass.rs @@ -4,6 +4,8 @@ //! (builtin_reg_map),避免与临时寄存器池冲突;同时预声明函数/var 声明, //! 支持提升语义。 +use std::collections::HashSet; + use crate::{CompileCtx, Emitter}; use oxide_parser::{ BindingPattern, Declaration, ExportDefaultDeclarationKind, Expression, Statement, VariableDeclarationKind, @@ -393,7 +395,7 @@ impl Emitter { /// (run 起点预载全局属性值),使 GDI 序言与声明点同步都用入口原值而非 /// fresh undefined 槽,现存值(NaN 等)不被抹;函数体内 builtin 名仍是局部 /// var 遮蔽(fresh 槽,不命中只读内置拦截);其余名 fresh var 槽。 - fn predeclare_var_name(&self, name: &str, ctx: &mut CompileCtx) { + pub(crate) fn predeclare_var_name(&self, name: &str, ctx: &mut CompileCtx) { if ctx.is_global_scope && CompileCtx::is_known_builtin(name) { let _ = ctx.lookup_or_builtin(name); } else { @@ -749,3 +751,195 @@ impl Emitter { Ok(()) } } + +impl Emitter { + /// 递归收集语句树内块级函数声明名(源序去重),供 web-compat 外层 var 绑定 + /// 实例化。 + /// + /// # 边界与前提 + /// - 调用点直接子级(函数体/程序顶层直接子句)是提升 var 声明,非块级 + /// 函数,不收集;进入嵌套语句(块/if/循环/switch case/try 各区)后的 + /// 函数声明才是块级。 + /// - 不递归进嵌套函数体:那是独立编译单元,其内部块函数名由该单元自身收集。 + /// - switch 不推作用域(case 内函数声明随外层块),收集须进入 case 才能到达 + /// case 内的 for/块等块级形。 + pub(crate) fn collect_block_function_names(&self, statements: &[Statement]) -> Vec { + let mut names = Vec::new(); + self.collect_block_function_names_into(statements, &mut names, false); + names + } + + fn collect_block_function_names_into(&self, statements: &[Statement], names: &mut Vec, block_level: bool) { + for statement in statements { + match statement { + Statement::FunctionDeclaration(f) => { + if block_level { + if let Some(id) = &f.id { + let name = id.name.to_string(); + if !names.contains(&name) { + names.push(name); + } + } + } + } + Statement::BlockStatement(bs) => self.collect_block_function_names_into(&bs.body, names, true), + Statement::IfStatement(is) => { + self.collect_block_function_names_into(std::slice::from_ref(&is.consequent), names, true); + if let Some(alt) = &is.alternate { + self.collect_block_function_names_into(std::slice::from_ref(alt), names, true); + } + } + Statement::WhileStatement(wh) => { + self.collect_block_function_names_into(std::slice::from_ref(&wh.body), names, true) + } + Statement::DoWhileStatement(dw) => { + self.collect_block_function_names_into(std::slice::from_ref(&dw.body), names, true) + } + Statement::ForStatement(fs) => { + self.collect_block_function_names_into(std::slice::from_ref(&fs.body), names, true) + } + Statement::ForInStatement(fi) => { + self.collect_block_function_names_into(std::slice::from_ref(&fi.body), names, true) + } + Statement::ForOfStatement(fo) => { + self.collect_block_function_names_into(std::slice::from_ref(&fo.body), names, true) + } + Statement::SwitchStatement(sw) => { + for case in &sw.cases { + self.collect_block_function_names_into(&case.consequent, names, true); + } + } + Statement::TryStatement(ts) => { + self.collect_block_function_names_into(&ts.block.body, names, true); + if let Some(handler) = &ts.handler { + self.collect_block_function_names_into(&handler.body.body, names, true); + } + if let Some(finalizer) = &ts.finalizer { + self.collect_block_function_names_into(&finalizer.body, names, true); + } + } + Statement::LabeledStatement(ls) => { + self.collect_block_function_names_into(std::slice::from_ref(&ls.body), names, true) + } + Statement::WithStatement(ws) => { + self.collect_block_function_names_into(std::slice::from_ref(&ws.body), names, true) + } + _ => {} + } + } + } + + /// 收集语句树内的词法声明名(let/const/class 含解构叶、for 头词法名、catch + /// 参数):块级函数名与树内任意词法声明同名时退化为纯块作用域(无外层 + /// 绑定、无求值写回)。 + /// + /// # 边界与前提 + /// - 不递归进嵌套函数体/class 元素体:独立编译单元,其词法名不撞本单元的 + /// 块函数名。 + /// - var 声明是 var 绑定,不撞词法环境,不入集。 + fn collect_lexical_name_suppressions(&self, statements: &[Statement], names: &mut HashSet) { + for statement in statements { + match statement { + Statement::VariableDeclaration(vd) => { + if !matches!(vd.kind, VariableDeclarationKind::Var) { + for d in &vd.declarations { + self.collect_binding_pattern_names(&d.id, names); + } + } + } + Statement::ClassDeclaration(cd) => { + if let Some(id) = &cd.id { + names.insert(id.name.to_string()); + } + } + Statement::ForStatement(fs) => { + if let Some(oxide_parser::ForStatementInit::VariableDeclaration(decl)) = &fs.init { + if !matches!(decl.kind, VariableDeclarationKind::Var) { + for d in &decl.declarations { + self.collect_binding_pattern_names(&d.id, names); + } + } + } + self.collect_lexical_name_suppressions(std::slice::from_ref(&fs.body), names); + } + Statement::ForInStatement(fi) => { + if let oxide_parser::ForStatementLeft::VariableDeclaration(decl) = &fi.left { + if !matches!(decl.kind, VariableDeclarationKind::Var) { + for d in &decl.declarations { + self.collect_binding_pattern_names(&d.id, names); + } + } + } + self.collect_lexical_name_suppressions(std::slice::from_ref(&fi.body), names); + } + Statement::ForOfStatement(fo) => { + if let oxide_parser::ForStatementLeft::VariableDeclaration(decl) = &fo.left { + if !matches!(decl.kind, VariableDeclarationKind::Var) { + for d in &decl.declarations { + self.collect_binding_pattern_names(&d.id, names); + } + } + } + self.collect_lexical_name_suppressions(std::slice::from_ref(&fo.body), names); + } + Statement::TryStatement(ts) => { + // catch 参数是 handler 环境的词法绑定。 + if let Some(handler) = &ts.handler { + if let Some(param) = &handler.param { + self.collect_binding_pattern_names(¶m.pattern, names); + } + self.collect_lexical_name_suppressions(&handler.body.body, names); + } + self.collect_lexical_name_suppressions(&ts.block.body, names); + if let Some(finalizer) = &ts.finalizer { + self.collect_lexical_name_suppressions(&finalizer.body, names); + } + } + Statement::BlockStatement(bs) => self.collect_lexical_name_suppressions(&bs.body, names), + Statement::IfStatement(is) => { + self.collect_lexical_name_suppressions(std::slice::from_ref(&is.consequent), names); + if let Some(alt) = &is.alternate { + self.collect_lexical_name_suppressions(std::slice::from_ref(alt), names); + } + } + Statement::WhileStatement(wh) => { + self.collect_lexical_name_suppressions(std::slice::from_ref(&wh.body), names) + } + Statement::DoWhileStatement(dw) => { + self.collect_lexical_name_suppressions(std::slice::from_ref(&dw.body), names) + } + Statement::SwitchStatement(sw) => { + for case in &sw.cases { + self.collect_lexical_name_suppressions(&case.consequent, names); + } + } + Statement::LabeledStatement(ls) => { + self.collect_lexical_name_suppressions(std::slice::from_ref(&ls.body), names) + } + Statement::WithStatement(ws) => { + self.collect_lexical_name_suppressions(std::slice::from_ref(&ws.body), names) + } + Statement::ExportNamedDeclaration(exp) => { + if let Some(Declaration::VariableDeclaration(vd)) = &exp.declaration { + if !matches!(vd.kind, VariableDeclarationKind::Var) { + for d in &vd.declarations { + self.collect_binding_pattern_names(&d.id, names); + } + } + } + } + _ => {} + } + } + } + + /// 块级函数名 web-compat 外层绑定抑制集:形参名(含解构叶/rest)∪ 语句树内 + /// 词法声明名。命中抑制的名字不建外层 var 绑定、不求值写回。 + pub(crate) fn collect_block_fn_suppressed_names( + &self, statements: &[Statement], param_names: &HashSet, + ) -> HashSet { + let mut names = param_names.clone(); + self.collect_lexical_name_suppressions(statements, &mut names); + names + } +} diff --git a/crates/oxide_emit/src/stmt/declaration/function_decl.rs b/crates/oxide_emit/src/stmt/declaration/function_decl.rs index 74f9963a..8731481b 100644 --- a/crates/oxide_emit/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/function_decl.rs @@ -68,8 +68,35 @@ impl Emitter { } else { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None)); } + // 块内函数声明求值写回外层 var 绑定(sloppy web-compat):预声明期为该 + // 名实例化的外层 var 绑定(函数作用域)在此写入块槽位里的函数对象, + // 求值一次写回一次(循环体内每迭代覆写头绑定)。形参/词法声明同名 + // (抑制集)与顶层 builtin 名不写回;顶层 A 侧补全局对象属性写(与 + // for 头写同形)。 + if !ctx.is_strict + && ctx.scopes.symbols.scopes.len() > 1 + && !ctx.block_fn_suppressed.contains(&name) + && !(ctx.is_global_scope && CompileCtx::is_known_builtin(&name)) + { + let var_target = ctx.scopes.symbols.find_var_target_scope(); + if let Some(outer) = ctx.scopes.symbols.scopes[var_target].bindings.get(&name) { + ctx.inst(Inst::new( + OpCode::STORE_VAR, + Operand::Reg(outer.reg), + Operand::Reg(var_reg), + Operand::None, + )); + // 顶层 A 侧同步写以外层绑定是否落全局作用域为准:此处块内 + // Let 绑定已占内层作用域,按名解析(is_global_tier_name)会 + // 命中块绑定误判为非顶层;嵌套函数作用域同名局部 var 不在 + // 全局 ctx 上,经 is_global_scope 门禁不走全局对象写。 + if ctx.is_global_scope && var_target == 0 && ctx.global_tier_names.contains(&name) { + self.emit_tier_global_write(&name, var_reg, ctx); + } + } + } // 脚本顶层(非块内)函数声明:同步写全局对象,使 globalThis 可反射函数名。 - // 块内函数声明是块级绑定,不得落全局对象(作用域隔离,块外不可见)。 + // 块内声明不走本臂(其全局可见性只经上方外层 var 写回面建立)。 // 严格 eval 代码函数声明绑定 eval 自身 lexical 环境、不落全局对象:抑制 A // 侧写(局部绑定读面未暴露),同时避免不可写全局内置在 0x98 上误抛 strict。 if ctx.is_global_scope && ctx.scopes.symbols.scopes.len() == 1 && !(ctx.is_eval_script && ctx.is_strict) { diff --git a/crates/oxide_emit/src/symbol_table.rs b/crates/oxide_emit/src/symbol_table.rs index cbaef8eb..bffb84d1 100644 --- a/crates/oxide_emit/src/symbol_table.rs +++ b/crates/oxide_emit/src/symbol_table.rs @@ -70,7 +70,7 @@ impl SymbolTable { } } - fn find_var_target_scope(&self) -> usize { + pub(crate) fn find_var_target_scope(&self) -> usize { for (i, scope) in self.scopes.iter().enumerate().rev() { if scope.kind == ScopeKind::FunctionScope { return i; diff --git a/crates/oxide_vm/tests/block_fn_var_head_tests.rs b/crates/oxide_vm/tests/block_fn_var_head_tests.rs new file mode 100644 index 00000000..e12f9888 --- /dev/null +++ b/crates/oxide_vm/tests/block_fn_var_head_tests.rs @@ -0,0 +1,127 @@ +//! 块级函数声明与 for-in/of var 头同名(Annex B 块级函数绑定面): +//! 求值期把函数对象写回外层 var 绑定,无外层绑定的名字在声明实例化期建绑定。 +//! +//! 覆盖:顶层/函数作用域 for-in 头形、嵌套块/switch case/for-of 变体、 +//! 无碰撞泄漏名、while 块、arguments 名守卫、既有全局属性保留、形参/词法 +//! 碰撞守卫、strict 无写回对照。 + +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)) +} + +fn truthy(source: &str) { + let result = eval(source).unwrap_or_else(|e| panic!("{source} -> {e}")); + assert!(result.as_bool(), "{source} -> 期望 true,得 {:?}", result); +} + +#[test] +fn for_in_var_head_same_name_rebinds_head() { + // 顶层 for-in var 头同名:循环后头绑定被函数对象覆写。 + truthy("var o={a:1,b:2}; for (var x in o){function x(){}} typeof x === 'function'"); +} + +#[test] +fn for_in_var_head_same_name_reaches_global_property() { + // 顶层形同时同步全局对象属性(脚本环境记录 var 绑定)。 + truthy("var o={a:1,b:2}; for (var x in o){function x(){}} typeof globalThis.x === 'function'"); +} + +#[test] +fn for_in_var_head_same_name_in_function_scope() { + // 函数作用域 for-in 头同名:写回函数作用域 var 绑定,不落全局对象。 + truthy("function f(){ var o={a:1,b:2}; for (var x in o){function x(){}} return typeof x === 'function' } f()"); +} + +#[test] +fn nested_blocks_inside_for_body() { + // 循环体内再嵌块层的块函数声明同样写回头绑定。 + truthy("var o={a:1}; for (var x in o){ { { function x(){} } } } typeof x === 'function'"); +} + +#[test] +fn assignment_head_same_name() { + // 赋值头(未声明名)同名:声明实例化期建外层绑定,写回后为函数。 + truthy("var o={a:1,b:2}; for (x in o){function x(){}} typeof x === 'function'"); +} + +#[test] +fn for_of_var_head_same_name() { + // for-of var 头同名孪生形。 + truthy("for (var y of [1,2]){function y(){}} typeof y === 'function'"); +} + +#[test] +fn switch_case_for_head_same_name() { + // switch case 内 for-in 头同名(case 不推作用域,块函数随外层块)。 + truthy("var o={a:1,b:2}; switch(1){case 1: for (var x in o){function x(){}}} typeof x === 'function'"); +} + +#[test] +fn for_of_destructuring_var_head_same_name() { + // for-of 解构 var 头同名:写回后头绑定为函数而非末迭代值。 + truthy("for (var [x] of [[1],[2]]){function x(){}} typeof x === 'function'"); +} + +#[test] +fn no_collision_block_fn_in_for_body_instantiates_outer_var() { + // 无碰撞泄漏形:体内块函数名无既有 var 绑定,声明实例化期新建外层绑定。 + truthy("var o={a:1,b:2}; for (var x in o){function f(){}} typeof f === 'function'"); +} + +#[test] +fn while_body_block_fn_instantiates_outer_var() { + // while 体内块函数(非循环头碰撞形):外层绑定实例化 + 每迭代写回, + // 顶层同步全局对象属性。 + truthy( + "var n=0; while(n<2){ n++; {function w(){}} } typeof w === 'function' && typeof globalThis.w === 'function'", + ); +} + +#[test] +fn function_scope_block_fn_named_arguments_overwrites_arguments_object() { + // 函数作用域块函数名 arguments:arguments 对象被函数对象覆写。 + truthy("function f(){ {function arguments(){}} return typeof arguments === 'function' } f()"); +} + +#[test] +fn top_level_block_fn_named_arguments_reaches_global() { + // 脚本顶层块函数名 arguments:外层绑定落全局对象属性。 + truthy("{function arguments(){}} typeof globalThis.arguments === 'function'"); +} + +#[test] +fn existing_global_property_kept_before_block_fn_decl() { + // 既有全局属性(值 7)在块函数声明前被读为 number,声明后该属性被覆写为 + // 函数(声明实例化对既有可写数据属性零动作,求值期写回才覆写)。 + truthy("globalThis.w=7; var r=typeof w; {function w(){}} r === 'number' && typeof w === 'function'"); +} + +#[test] +fn param_name_collision_no_write_back() { + // 形参同名守卫:块函数退化为纯块作用域,形参值不受写回影响。 + truthy("function f(x){ {function x(){}} return String(x) } f(42) === '42'"); +} + +#[test] +fn lexical_name_collision_no_outer_binding() { + // 词法声明同名守卫:不建外层 var 绑定、不求值写回,词法值保持。 + truthy("function f(){ let g='L'; {function g(){}} return g } f() === 'L'"); +} + +#[test] +fn strict_for_body_no_write_back() { + // strict 无 web-compat 写回:循环后头绑定保留末迭代值。 + truthy( + "function f(){ \"use strict\"; var o={a:1,b:2}; for (var x in o){function x(){}} return typeof x } f() === 'string'", + ); +} diff --git a/crates/oxide_vm/tests/block_function_decl_tests.rs b/crates/oxide_vm/tests/block_function_decl_tests.rs index 105b5459..29c1f828 100644 --- a/crates/oxide_vm/tests/block_function_decl_tests.rs +++ b/crates/oxide_vm/tests/block_function_decl_tests.rs @@ -56,10 +56,12 @@ fn block_function_decl_capture_escape() { } #[test] -fn block_function_decl_scope_isolated() { - // 嵌套块内函数是块级绑定:块外不可见(typeof 未声明标识符返回 undefined)。 - let result = eval("{ { function g(){return 2;} } } typeof g === 'undefined'").unwrap(); - assert!(result.as_bool()); +fn block_function_decl_leaks_outer_in_sloppy_script() { + // sloppy 块级函数声明名泄漏外层(web-compat 外层 var 绑定 + 求值写回): + // 块后读见函数对象而非未声明名。块内声明前读仍为 undefined(块入口初始化 + // 未建,见 hoisted_call 测试),块内声明后读为函数。 + let result = eval("{ { function g(){return 2;} } } typeof g === 'function'").unwrap(); + assert!(result.as_bool(), "sloppy 块函数名应泄漏外层,got {:?}", result); } #[test]