From dbdc5556c6ab69ef674ff7c9b4fb4282e7833633 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 9 Oct 2026 23:58:14 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=BB=98=E8=AE=A4=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=87=AA=E5=BC=95=E7=94=A8=E4=B8=8E=E5=90=8E=E4=BD=8D=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E8=AF=BB=E6=8A=9B=20TDZ=20ReferenceError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 +++++------ Cargo.toml | 2 +- crates/oxide_emit/src/compile_ctx.rs | 5 ++ crates/oxide_emit/src/expr/identifier.rs | 17 ++++- crates/oxide_emit/src/function_body.rs | 39 +++++++++++- crates/oxide_vm/tests/function_tests.rs | 80 ++++++++++++++++++++++++ 6 files changed, 157 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6d551b4..16598d11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.13.22" +version = "0.13.23" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.13.22" +version = "0.13.23" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.13.22" +version = "0.13.23" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.13.22" +version = "0.13.23" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.13.22" +version = "0.13.23" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.13.22" +version = "0.13.23" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.13.22" +version = "0.13.23" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.13.22" +version = "0.13.23" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.13.22" +version = "0.13.23" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.13.22" +version = "0.13.23" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.13.22" +version = "0.13.23" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 15171b84..c12803bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.13.22" +version = "0.13.23" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index 1e4911af..36fe0e4d 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -80,6 +80,10 @@ pub struct CompileCtx { /// 形参同名时不建外层绑定、不求值写回(规范 paramNames 守卫面)。子函数 /// ctx 为新建,不继承。 pub(crate) param_names: HashSet, + /// 参数序言默认值发射期的形参名 TDZ 集:当前形参自身名(含解构叶)加其后 + /// 全部形参名。发射期临时置位,每形参处理完即清空;重名形参不建集(规范 + /// 变量环境面无 TDZ)。标识符静态读入口命中此集时发射运行时 ReferenceError。 + pub(crate) param_tdz_names: HashSet, /// 块级函数名按 sloppy 模式下与浏览器/web 实现惯例兼容的行为建外层绑定的抑制集 /// (形参 ∪ 函数作用域树内词法声明名):抑制集内名字退化为纯块作用域;预声明期定稿,实例化与声明点写回两侧同查。 pub(crate) block_fn_suppressed: HashSet, @@ -281,6 +285,7 @@ impl CompileCtx { current_upvalue_captures: Vec::new(), own_bindings: HashSet::new(), param_names: HashSet::new(), + param_tdz_names: HashSet::new(), block_fn_suppressed: HashSet::new(), block_fn_entry_mats: Vec::new(), captured_bindings: BTreeMap::new(), diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index e9760024..a63f4fdb 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -16,6 +16,14 @@ impl Emitter { /// - 活读映射仅模块顶层 ctx 填充:嵌套函数内读 import 名仍走链接期快照 /// (`current_upvalue_captures` 不在此列,闭包内不继承映射)。 fn emit_static_identifier_read(&self, name: &str, ctx: &mut CompileCtx) -> Result { + // 形参名 TDZ:默认值表达式内引用当前形参与其后形参是形参环境未初始化 + // 绑定,运行期抛 ReferenceError(重名形参无 TDZ,编译期集为空)。判定置于 + // 读入口最前:被捕获参数在默认值发射期经 visible_cell 回退命中 CELL_GET, + // 判定后置即漏判。 + if ctx.param_tdz_names.contains(name) { + return self.emit_tdz_throw(&format!("Cannot access '{name}' before initialization"), ctx); + } + // 可重赋依赖的命名/默认导入:读点直接查依赖命名空间当前值,跟随源模块重赋。 if let Some((dep_ns, exported)) = ctx.module_live_imports.get(name).cloned() { let name_reg = self.load_string_const(&exported, ctx); @@ -107,9 +115,12 @@ impl Emitter { ctx.inst(Inst::jmp(end_label)); ctx.labels.set_label_pos(fallback_label, ctx.insts.len()); - // 回退:upvalue / 被捕获 cell / 静态绑定 / 未定义四选一。 - // 对象无该属性时才走此处,语义与静态解析一致。 - if let Some((uv_idx, _)) = ctx.current_upvalue_captures.iter().enumerate().find(|(_, u)| u.name == name) { + // 回退:形参名 TDZ / upvalue / 被捕获 cell / 静态绑定 / 未定义五选一。 + // 对象无该属性时才走此处,语义与静态解析一致。TDZ 判定置于回退入口最前: + // 被捕获参数经 visible_cell 回退命中 CELL_GET,判定后置即漏判。 + if ctx.param_tdz_names.contains(name) { + let _ = self.emit_tdz_throw(&format!("Cannot access '{name}' before initialization"), ctx); + } else if let Some((uv_idx, _)) = ctx.current_upvalue_captures.iter().enumerate().find(|(_, u)| u.name == name) { ctx.inst(Inst::new( OpCode::LOAD_UPVALUE, Operand::Reg(result_reg), diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index 174a5bdf..43fc4b7f 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -860,7 +860,41 @@ impl Emitter { ctx.inst(Inst::create_arguments(Operand::Reg(reg))); } - for spec in param_specs { + // 形参名 TDZ 集:按形参序收集每形参绑定名(含解构叶),重名面不建集 + // (规范变量环境面无 TDZ)。默认值发射期读路径命中此集抛 ReferenceError。 + let param_bound_names: Vec> = param_specs + .iter() + .map(|spec| match spec { + ParamSpec::Identifier { name, .. } | ParamSpec::Rest { name } => vec![name.clone()], + ParamSpec::Pattern { pattern, .. } => { + let mut names = HashSet::new(); + collect_binding_pattern_names(pattern, &mut names); + names.into_iter().collect() + } + }) + .collect(); + let has_param_duplicates = { + let total: usize = param_bound_names.iter().map(|v| v.len()).sum(); + let mut seen = HashSet::new(); + for names in ¶m_bound_names { + for n in names { + seen.insert(n.clone()); + } + } + seen.len() < total + }; + + for (idx, spec) in param_specs.iter().enumerate() { + // 循环首置 TDZ 集:当前形参自身名(含解构叶)加其后全部形参名; + // 重名面保持空集。 + if !has_param_duplicates { + ctx.param_tdz_names.clear(); + ctx.param_tdz_names.extend(param_bound_names[idx].iter().cloned()); + for subsequent in ¶m_bound_names[idx + 1..] { + ctx.param_tdz_names.extend(subsequent.iter().cloned()); + } + } + match spec { ParamSpec::Pattern { synthetic_name, @@ -890,6 +924,9 @@ impl Emitter { ctx.inst(Inst::create_rest_array(Operand::Reg(reg), fixed_count)); } } + + // 每形参处理完即清空,防 TDZ 集泄漏到 body 读取。 + ctx.param_tdz_names.clear(); } // 被捕获的参数也必须建 cell(MAKE_CELL):否则子函数经 lazy upvalue 路径读 diff --git a/crates/oxide_vm/tests/function_tests.rs b/crates/oxide_vm/tests/function_tests.rs index cbca148c..d20d3bfe 100644 --- a/crates/oxide_vm/tests/function_tests.rs +++ b/crates/oxide_vm/tests/function_tests.rs @@ -285,3 +285,83 @@ fn fn_name_recursive_call() { let result = eval(&mut vm, "function fact(n) { return n <= 1 ? 1 : n * fact(n - 1); } fact(5)").unwrap(); assert_num(result, 120.0); } + +// --- 默认参数自引用 TDZ 守卫 --- + +/// 断言运行期抛出未捕获 ReferenceError(默认参数 TDZ 守卫的期望形态)。 +fn assert_dflt_tdz_reference_error(source: &str, msg: &str) { + let err = eval(&mut Vm::new(), source).unwrap_err(); + assert!( + err.contains("ReferenceError"), + "{msg}: expected an uncaught ReferenceError, got: {err}" + ); +} + +#[test] +fn dflt_param_self_ref_throws() { + // 默认值自引用当前形参:形参环境未初始化绑定,运行期抛 ReferenceError。 + assert_dflt_tdz_reference_error("function f(x = x) {} f()", "self-ref"); +} + +#[test] +fn dflt_param_ref_later_throws() { + // 默认值引用后位形参:后位形参在默认值求值期尚未初始化,抛 ReferenceError。 + assert_dflt_tdz_reference_error("function f(x = y, y) {} f()", "ref-later"); +} + +#[test] +fn dflt_param_ref_prior_legal() { + // 默认值引用前位形参:前位形参已初始化,读取合法,y 得 x 的值。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(x, y = x) { return y; } f(3)").unwrap(); + assert_eq!(result.as_int(), 3); +} + +#[test] +fn dflt_param_arg_defined_no_eval() { + // 实参非 undefined:默认值不求值,自引用不抛。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(x = x) { return x; } f(1)").unwrap(); + assert_eq!(result.as_int(), 1); +} + +#[test] +fn dflt_param_object_dstr_self_ref_throws() { + // 对象解构自引用抛 ReferenceError。 + assert_dflt_tdz_reference_error("function f({x = x}) {} f({})", "object-dstr-self"); +} + +#[test] +fn dflt_param_arguments_not_in_tdz() { + // arguments 不在 TDZ 集:默认值引用 arguments 合法。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(x = arguments[1]) { return x; } f(undefined, 2)").unwrap(); + assert_eq!(result.as_int(), 2); +} + +#[test] +fn dflt_param_nested_fn_no_throw() { + // 默认值为嵌套函数:嵌套函数体在子 ctx 编译,经 upvalue 读初始化后的值,不抛。 + // 闭包调用后读回 x 为默认值函数对象本身(非 undefined)。 + let mut vm = Vm::new(); + let result = eval(&mut vm, "function f(x = function() { return x; }) { return x; } f()()").unwrap(); + assert!(is_function_value(result), "expected the default function, got: {:?}", result); +} + +#[test] +fn dflt_param_typeof_self_ref_throws() { + // typeof 对 TDZ 绑定抛 ReferenceError(经 emit_expression 同形)。 + assert_dflt_tdz_reference_error("function f(x = typeof x) {} f()", "typeof-self"); +} + +#[test] +fn dflt_param_generator_self_ref_throws() { + // 生成器默认值自引用抛 ReferenceError(参数初始化在调用时刻)。 + assert_dflt_tdz_reference_error("function* g(x = x) {} g()", "generator-self"); +} + +#[test] +fn dflt_param_strict_self_ref_throws() { + // 严格模式默认值自引用同样抛 ReferenceError(TDZ 与模式无关)。 + assert_dflt_tdz_reference_error("'use strict'; function f(x = x) {} f()", "strict-self"); +}