From f045adba2bd884961200b62943672febe142a3d8 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 13:09:41 -0800 Subject: [PATCH] JavaScript: a function made by the Function constructor does not bind the name anonymous The constructor compiles its source as (function anonymous(...) {...}), and the compiled function bound anonymous to itself as a named function expression does. That binding shadowed a global variable of the same name. ES5.1 section 15.3.2.1 creates the function with the global environment as its scope and no name binding, so the constructed function now leaves the name unbound. --- crates/js/src/compiler.rs | 14 ++++++++------ crates/js/src/interp_tests.rs | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/js/src/compiler.rs b/crates/js/src/compiler.rs index 75ee257af..6e16437a5 100644 --- a/crates/js/src/compiler.rs +++ b/crates/js/src/compiler.rs @@ -284,7 +284,7 @@ impl<'a> FnCompiler<'a> { /// Compiles a nested function and returns its index in the function table. fn function(&mut self, fid: FunctionId) -> CResult { - let code = compile_function(self.ast, fid, self.source, self.recursion)?; + let code = compile_function(self.ast, fid, self.source, self.recursion, true)?; self.functions.push(code); Ok(u32::try_from(self.functions.len() - 1).unwrap_or(u32::MAX)) } @@ -1208,8 +1208,9 @@ fn len_u32(n: usize) -> u32 { u32::try_from(n).unwrap_or(u32::MAX) } -/// Compiles function `fid` of `ast`. -pub(crate) fn compile_function(ast: &Ast, fid: FunctionId, source: &Rc, recursion: u32) -> CResult> { +/// Compiles function `fid` of `ast`. With `bind_name`, a function expression's name is bound +/// inside it. +pub(crate) fn compile_function(ast: &Ast, fid: FunctionId, source: &Rc, recursion: u32, bind_name: bool) -> CResult> { let Some(f) = ast.function(fid) else { return internal("function out of range") }; let mut c = FnCompiler::new(ast, source, CodeKind::Function, f.strict, recursion); c.at(f.span.start); @@ -1221,7 +1222,7 @@ pub(crate) fn compile_function(ast: &Ast, fid: FunctionId, source: &Rc, c.emit(Op::Return); let name = f.name.as_ref().map(|n| key_of(&n.name)); let meta = Meta { - self_name: if f.kind == FunctionKind::Expression { name } else { None }, + self_name: if bind_name && f.kind == FunctionKind::Expression { name } else { None }, params: f.params.iter().map(|p| key_of(&p.name)).collect(), span: (f.span.start, f.span.end), }; @@ -1239,11 +1240,12 @@ pub(crate) fn compile_program(program: &crate::Program, source: &Rc, kin } /// The function of a `Function` constructor's source, `(function anonymous(…) {…})`: the program -/// must be that one parenthesized function expression and nothing else. +/// must be that one parenthesized function expression and nothing else. The name `anonymous` is +/// not bound inside the function (section 15.3.2.1). pub(crate) fn compile_constructed_function(program: &crate::Program, source: &Rc) -> CResult>> { let [stmt] = program.body.as_slice() else { return Ok(None) }; let Some(Stmt::Expr(e)) = program.ast.stmt(*stmt) else { return Ok(None) }; let Some(Expr::Paren(inner)) = program.ast.expr(*e) else { return Ok(None) }; let Some(Expr::Function(fid)) = program.ast.expr(*inner) else { return Ok(None) }; - compile_function(&program.ast, *fid, source, 0).map(Some) + compile_function(&program.ast, *fid, source, 0, false).map(Some) } diff --git a/crates/js/src/interp_tests.rs b/crates/js/src/interp_tests.rs index e4904a1a7..f94597d60 100644 --- a/crates/js/src/interp_tests.rs +++ b/crates/js/src/interp_tests.rs @@ -173,6 +173,7 @@ fn with_eval_and_new_function() { assert_eq!(num("eval('1; 2; if (false) {}')"), 2.0); assert_eq!(num("new Function('a', 'b', 'return a * b;')(6, 7)"), 42.0); assert_eq!(num("Function('return 3')()"), 3.0); + assert_eq!(num("var anonymous = 42; Function('return anonymous')()"), 42.0); let e = fail("new Function('a){}; (function(', 'return 1')"); assert!(e.message.starts_with("SyntaxError"), "{}", e.message); let e = fail("new Function('}); (function() {')");