Files
eternalwrt-mt798x/package/utils/ucode/patches/100-compiler-scope-named-function-expression-names-to-th.patch
T
Felix Fietkau 7987b1f799 ucode: fix two compiler issues
- When working with deeply nested imports, compile errors led to long
  error messages or complete hangs by compiling the same module over
  and over again.
- Fix for a function expression scope issue.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-07-07 10:46:12 +02:00

81 lines
3.1 KiB
Diff

From: Felix Fietkau <nbd@nbd.name>
Date: Mon, 6 Jul 2026 18:23:08 +0200
Subject: [PATCH] compiler: scope named function expression names to their own
body
A named function expression (e.g. `!function e(){...}()`) shares its parsing
path with function declarations, and declared its name in the enclosing scope
just as a declaration does. For an expression this is wrong: the name is only
meant to be visible inside its own body for self-reference. Binding it in the
enclosing scope leaked the name and, by pushing an extra local, shifted the
slot count so the initialize_local for a let/const initialised by the
expression marked the wrong slot -- the real variable stayed uninitialised and
its first use raised "Can't access lexical declaration 'x' before
initialization". Self-recursion was likewise broken.
Bind the name in the enclosing scope only for declarations. For a named
expression, rename the function's own callee slot to the name after
initialising its compiler, so the body can reference itself while the enclosing
scope is left untouched.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
--- a/compiler.c
+++ b/compiler.c
@@ -1949,20 +1949,27 @@ uc_compiler_compile_funcexpr_common(uc_c
return;
}
- slot = uc_compiler_resolve_funcstub(compiler, name);
+ /* A function declaration binds its name in the enclosing scope. A named
+ * function expression must not: its name is only visible inside its own
+ * body (for self-reference), and declaring it in the enclosing scope
+ * would both leak it and shift the local slots, corrupting the
+ * initialisation state of a `let`/`const` the expression initialises. */
+ if (require_name) {
+ slot = uc_compiler_resolve_funcstub(compiler, name);
- if (slot > -1) {
- compiler->locals.entries[slot].funcstub = false;
- }
- else {
- slot = uc_compiler_declare_local(compiler, name, false);
+ if (slot > -1) {
+ compiler->locals.entries[slot].funcstub = false;
+ }
+ else {
+ slot = uc_compiler_declare_local(compiler, name, false);
- if (slot == -1)
- uc_compiler_initialize_local(compiler);
- else if (compiler->locals.entries[slot].constant)
- uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
- "Redeclaration of constant '%s'",
- ucv_string_get(name));
+ if (slot == -1)
+ uc_compiler_initialize_local(compiler);
+ else if (compiler->locals.entries[slot].constant)
+ uc_compiler_syntax_error(compiler, compiler->parser->prev.pos,
+ "Redeclaration of constant '%s'",
+ ucv_string_get(name));
+ }
}
}
else if (require_name) {
@@ -1976,6 +1983,14 @@ uc_compiler_compile_funcexpr_common(uc_c
compiler->program,
uc_compiler_is_strict(compiler));
+ /* Bind a named function expression's name to its own callee slot so the
+ * body can reference itself for recursion, without touching the enclosing
+ * scope. Declarations keep the name in the enclosing scope (handled above). */
+ if (name && !require_name) {
+ ucv_put(fncompiler.locals.entries[0].name);
+ fncompiler.locals.entries[0].name = ucv_get(name);
+ }
+
fncompiler.parent = compiler;
fncompiler.parser = compiler->parser;
fncompiler.exprstack = compiler->exprstack;