From 46464efede63778a2c84260aba54425fe1afb5d8 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 18 Sep 2026 01:29:53 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8D=95=E8=8E=B7=E5=9D=97=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=90=8D=E5=85=A5=E5=8F=A3=E5=AE=9E=E4=BE=8B=E5=8C=96?= =?UTF-8?q?=E4=B8=BA=20undefined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/function_body.rs | 28 +++++++++++++++---- .../tests/block_fn_outer_var_entry_tests.rs | 9 +++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index 87ede52c..4b017026 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -743,12 +743,26 @@ impl Emitter { )); } - // var 绑定函数入口实例化:被捕获的 var 名统一 MAKE_CELL(undefined)。 - // 规范上 var 在函数入口即初始化为 undefined(HoistDeclaration),声明语句 - // 只是赋值;否则声明语句前创建的闭包读取占位 cell → TDZ 误报。参数与 - // arguments 已在上方初始化(跳过以免覆盖参数值);let/const/class 保持 - // TDZ 语义不动。声明语句的 MAKE_CELL 按占位更新语义覆盖此初值。 - let mut var_names: Vec = collect_var_binding_names(body_stmts) + // var 与块级函数外层 var 绑定函数入口实例化:被捕获名统一 + // MAKE_CELL(undefined)。规范上 var 在函数入口即初始化为 undefined + // (HoistDeclaration),sloppy 下块级函数名同建外层 var 绑定;声明语句只是 + // 赋值。缺此实例化时,声明点前创建的闭包读取占位 cell → TDZ 误报,非捕获面 + // 则取调用方寄存器残留。参数与 arguments 已在上方初始化(跳过以免覆盖参数 + // 值);let/const/class 保持 TDZ 语义不动。声明语句的 MAKE_CELL 按占位更新 + // 语义覆盖此初值。 + let mut captured_entry_names = collect_var_binding_names(body_stmts); + // 块级函数外层 var 名与 instantiate_var_bindings 同源并入;抑制名(形参/ + // 词法声明同名)无外层绑定,不并入。抑制集在此独立计算:本循环早于函数体 + // 的 block_fn_suppressed 构建。 + if !ctx.is_strict { + let suppressed = self.collect_block_fn_suppressed_names(body_stmts, &ctx.param_names); + for name in self.collect_block_function_names(body_stmts) { + if !suppressed.contains(&name) { + captured_entry_names.insert(name); + } + } + } + let mut var_names: Vec = captured_entry_names .into_iter() .filter(|n| !param_names.contains(&n.as_str()) && n != "arguments") .filter(|n| ctx.captured_bindings.contains_key(n)) @@ -792,6 +806,8 @@ impl Emitter { // 块级函数外层 var 绑定同样只预声明不发射定义指令,缺失入口写会让块前读取 // 取到调用方残留;入口写须并入(strict 与抑制名除外)。 if !ctx.is_strict { + // 直接子标签函数声明在函数入口首 sub-pass 物化闭包,声明点前读即为函数; + // 此处按名排除只省一次冗余的非捕获 undefined 写,非正确性必需。 let labeled_hoisted: HashSet<&str> = body_stmts .iter() .filter_map(|stmt| Self::labeled_function_decl(stmt)) diff --git a/crates/oxide_vm/tests/block_fn_outer_var_entry_tests.rs b/crates/oxide_vm/tests/block_fn_outer_var_entry_tests.rs index e3d61c7b..b58a4db6 100644 --- a/crates/oxide_vm/tests/block_fn_outer_var_entry_tests.rs +++ b/crates/oxide_vm/tests/block_fn_outer_var_entry_tests.rs @@ -3,7 +3,7 @@ //! //! 覆盖:显式块主形(块前读/块后读/调用)、if 支臂形、显式块内 if 支臂形、 //! 嵌套块形、strict 不泄漏守卫、裸读形、形参同名抑制守卫、直接子标签函数 -//! 声明守卫。 +//! 声明守卫、被嵌套函数捕获形。 use std::sync::Arc; @@ -73,3 +73,10 @@ fn block_fn_direct_labeled_decl_before_read_stays_function() { // 直接子标签函数声明守卫:入口物化闭包,声明点前读仍是函数对象。 truthy("function t(){ var r = typeof g; l: function g(){return 1}; return r === 'function' && typeof g === 'function' } t()"); } + +#[test] +fn block_fn_captured_before_read_is_undefined() { + // 捕获形:块函数名被嵌套函数引用时同样在入口实例化为 undefined,块前读不得 + // 取调用方寄存器残留;块求值后闭包读到写回的函数对象。 + truthy("function t(){ var r = typeof g; var f = function(){ return g }; { function g(){} } return r === 'undefined' && typeof f() === 'function' } t()"); +}