diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index 45f43c05..5d02b411 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -402,6 +402,7 @@ impl Emitter { .map(|i| i as u8); self.predeclare_function_declarations(body_stmts, &mut ctx); + self.predeclare_labeled_function_declarations(body_stmts, &mut ctx); // 预注册 builtin 引用(先于任何临时寄存器),builtin 槽不与被复用的临时值冲突。 self.pre_register_builtin_references(body_stmts, &mut ctx); @@ -720,18 +721,48 @@ impl Emitter { } } + /// 标签链直接包裹的函数声明(Annex B.3.2 Labelled Function Declarations): + /// 展开任意层 `LabeledStatement` 后取内层函数声明。至少经过一层标签才返回 + /// `Some`;直接子函数声明由调用方单独匹配。 + /// + /// # 边界与前提 + /// - 标签体为块/分支等其它语句时返回 `None`(归块面或普通标签发射处理)。 + /// - 不递归进函数体:嵌套函数是独立编译单元。 + pub(crate) fn labeled_function_decl<'a, 'b>(stmt: &'b Statement<'a>) -> Option<&'b oxide_parser::Function<'a>> { + let mut cur: &'b Statement<'a> = match stmt { + Statement::LabeledStatement(labeled) => &labeled.body, + _ => return None, + }; + loop { + match cur { + Statement::LabeledStatement(labeled) => cur = &labeled.body, + Statement::FunctionDeclaration(fd) => return Some(fd), + _ => return None, + } + } + } + + /// 函数体内参与入口提升的函数声明语句:直接子函数声明,或标签链直接包裹的 + /// 函数声明(sloppy 下标签不改变执行流,二者同等在函数入口物化闭包)。 + fn is_body_hoisted_function_decl(stmt: &Statement) -> bool { + matches!(stmt, Statement::FunctionDeclaration(_)) || Self::labeled_function_decl(stmt).is_some() + } + /// 双 sub-pass emit body:先函数声明(hoisting),再其余语句。返回最后结果寄存器。 + /// + /// 首 sub-pass 覆盖直接子函数声明与标签链直接包裹的函数声明:二者均在函数 + /// 入口物化闭包,次 sub-pass 跳过声明语句本身,声明点不重编以保函数对象同一性。 fn emit_body_stmts(&self, body_stmts: &[Statement], ctx: &mut CompileCtx) -> Result, String> { let mut last_result_reg = None; for stmt in body_stmts { - if matches!(stmt, Statement::FunctionDeclaration(_)) { + if Self::is_body_hoisted_function_decl(stmt) { if let Some(reg) = self.emit_statement(stmt, ctx)? { last_result_reg = Some(reg); } } } for stmt in body_stmts { - if matches!(stmt, Statement::FunctionDeclaration(_)) { + if Self::is_body_hoisted_function_decl(stmt) { continue; } if let Some(reg) = self.emit_statement(stmt, ctx)? { diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index 74e543d9..233147b1 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -27,6 +27,28 @@ impl Emitter { } } + /// 预声明函数体直接子级中标签链直接包裹的函数声明名(Annex B.3.2 Labelled + /// Function Declarations):sloppy 下标签不改变执行流,该声明的名与直接子 + /// 函数声明一样是函数作用域 `var` 绑定,须在入口物化闭包前完成预登记。 + /// + /// # 边界与前提 + /// - 仅扫描直接子语句(标签链展开后须是函数声明);标签包块等其它形归块面 + /// 块级函数预声明路径。 + /// - 与块级函数名收集路径可能对同名重复建绑定:`declare_initialized` 命中 + /// 已存在绑定时只补初始化标志,不改变既有寄存器分配,语义幂等。 + pub(crate) fn predeclare_labeled_function_declarations(&self, statements: &[Statement], ctx: &mut CompileCtx) { + for statement in statements { + let Some(function) = Self::labeled_function_decl(statement) else { + continue; + }; + let Some(identifier) = &function.id else { + continue; + }; + let reg = ctx.alloc_reg(); + let _ = ctx.declare_initialized(identifier.name.as_str(), reg, VariableDeclarationKind::Var, false); + } + } + /// 预声明单个 `var` 名:仅顶层(全局作用域)的内置名落内置镜像槽(预载全局 /// 属性值的固定寄存器槽,本轮求值开始时预载),使 GlobalDeclarationInstantiation /// (脚本顶层声明实例化)序言与声明点同步都用入口原值,而非程序求值前 diff --git a/crates/oxide_vm/tests/function_body_label_fn_tests.rs b/crates/oxide_vm/tests/function_body_label_fn_tests.rs new file mode 100644 index 00000000..8a5d530c --- /dev/null +++ b/crates/oxide_vm/tests/function_body_label_fn_tests.rs @@ -0,0 +1,157 @@ +//! 函数体直接子级标签声明:sloppy 下标签链直接包裹的函数声明(Annex B.3.2 +//! Labelled Function Declarations)与直接子函数声明同等参与函数入口实例化, +//! 声明点前读即见函数对象,声明点不重编以保函数对象同一性。 +//! +//! 覆盖:声明前读(typeof/裸读/可调用)、声明前后读、嵌套标签链、函数对象 +//! 同一性、与形参同名覆盖、嵌套块词法同名抑制形的编译成功、混合直接子声明的 +//! 源序末位生效、逻辑表达式自调用不重入(双输出症状闭合)、块内标签形不回归、 +//! 重复标签与 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 labeled_fn_before_read_is_function() { + // 主形:声明点前 `typeof g` 读到函数对象。 + let source = "(function(){ var t = typeof g; l: function g(){return 1}; return t })()"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn labeled_fn_before_and_after_read() { + // 声明点前后读一致为 function。 + let source = "(function(){ var a = typeof g; l: function g(){return 1}; var b = typeof g; \ + return a === 'function' && b === 'function' })()"; + assert!(eval(source).unwrap().as_bool()); +} + +#[test] +fn labeled_fn_bare_read_yields_callable_closure() { + // 声明点前裸读 `g` 得到可调用闭包(非寄存器残留)。 + let source = "(function(){ var a = g; l: function g(){return 7}; return a() })()"; + assert_eq!(eval(source).unwrap().as_int(), 7); +} + +#[test] +fn labeled_fn_identity_with_declaration_point() { + // 声明点前读到的函数对象与声明名绑定同一(声明点不重编闭包)。 + let source = "(function(){ var a = g; l: function g(){return 1}; return a === g })()"; + assert!(eval(source).unwrap().as_bool()); +} + +#[test] +fn labeled_fn_nested_label_chain_hoists() { + // 嵌套标签链:多层标签直接包裹同样参与函数入口提升。 + let source = "(function(){ var t = typeof g; l1: l2: function g(){return 1}; return t })()"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn labeled_fn_overrides_parameter() { + // 与形参同名:函数声明入口实例化覆盖形参值,读面见函数对象。 + let source = "(function f(g){ var t = typeof g; l: function g(){return 1}; return t })(42)"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn labeled_fn_suppressed_by_nested_lexical_compiles() { + // 嵌套块内词法同名不压制函数体顶层标签函数声明的绑定:编译成功且读面为函数。 + let source = "(function(){ l: function g(){return 1}; { let g; } return typeof g })()"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn labeled_fn_mixed_with_direct_child_last_wins() { + // 直接子与标签形混合:源序末位声明生效,声明点前读与末位绑定同一。 + let source = "(function(){ var a = g; l: function g(){return 1}; function g(){return 2}; \ + return a === g && g() === 2 })()"; + assert!(eval(source).unwrap().as_bool()); +} + +#[test] +fn labeled_fn_logical_call_value() { + // 声明点前读入变量后在逻辑表达式右操作数调用,值等于函数返回值。 + let source = "(function(){ var a = g; l: function g(){return 1}; return a && a() })()"; + assert_eq!(eval(source).unwrap().as_int(), 1); +} + +#[test] +fn labeled_fn_logical_call_no_reentry() { + // 入口持有真闭包后自调用不重入:计数与返回值同帧收敛为 11;若重入读到非 + // 函数,`globalThis.n * 10 + (a && a())` 会得 NaN。 + let source = "(function(){ var a = g; l: function g(){return 1}; \ + globalThis.n = (globalThis.n||0)+1; return globalThis.n * 10 + (a && a()) })()"; + let value = eval(source).unwrap(); + let number = if value.is_int() { value.as_int() as f64 } else { value.as_double() }; + assert_eq!(number, 11.0, "got {value:?}"); +} + +#[test] +fn labeled_fn_after_read_guard() { + // 守卫:声明点后读仍为 function。 + let source = "(function(){ l: function g(){return 1}; return typeof g })()"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn labeled_fn_other_name_before_read_undefined_guard() { + // 守卫:标签声明其它名字时,未声明名前读仍为 undefined。 + let source = "(function(){ var t = typeof g; l: function h(){return 1}; return t })()"; + assert_eq!(eval_str(source).unwrap(), "undefined"); +} + +#[test] +fn direct_child_fn_before_read_guard() { + // 守卫:直接子函数声明前读保持函数对象,与本修路径无关。 + let source = "(function(){ var a = h; function h(){return 1}; return a && a() })()"; + assert_eq!(eval(source).unwrap().as_int(), 1); +} + +#[test] +fn labeled_fn_in_block_still_entry_visible_guard() { + // 守卫:块内标签形走块面入口物化,不因函数面改动回归。 + let source = "{ var t = typeof g; l: function g(){return 1}; t }"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn labeled_fn_in_block_leaks_outer_guard() { + // 守卫:块内标签形经外层 var 写回泄漏块外。 + assert_eq!(eval("{ l: function g(){return 1} } g()").unwrap().as_int(), 1); +} + +#[test] +fn labeled_fn_duplicate_label_is_early_error() { + // 重复标签是早期错误:函数面提升不放过非法标签形。 + let err = eval("(function(){ a: a: function g(){} })").unwrap_err(); + assert!(err.contains("already been declared"), "got: {err}"); +} + +#[test] +fn labeled_fn_strict_is_early_error() { + // strict 代码中标签函数声明是早期错误。 + let err = eval("(function(){ 'use strict'; l: function g(){} })").unwrap_err(); + assert!(err.contains("Invalid function declaration"), "got: {err}"); +}