From: Felix Fietkau Date: Tue, 7 Jul 2026 08:31:42 +0000 (+0200) Subject: ucode: fix two compiler issues X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7987b1f799b77cdae2355c5c904c5770491d5bf1;p=thirdparty%2Fopenwrt.git 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 --- diff --git a/package/utils/ucode/patches/100-compiler-scope-named-function-expression-names-to-th.patch b/package/utils/ucode/patches/100-compiler-scope-named-function-expression-names-to-th.patch new file mode 100644 index 00000000000..d788a7c7685 --- /dev/null +++ b/package/utils/ucode/patches/100-compiler-scope-named-function-expression-names-to-th.patch @@ -0,0 +1,80 @@ +From: Felix Fietkau +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 +--- + +--- 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; diff --git a/package/utils/ucode/patches/101-lib-avoid-allocating-print-buffer-before-NULL-check-.patch b/package/utils/ucode/patches/101-lib-avoid-allocating-print-buffer-before-NULL-check-.patch new file mode 100644 index 00000000000..1902006cdbb --- /dev/null +++ b/package/utils/ucode/patches/101-lib-avoid-allocating-print-buffer-before-NULL-check-.patch @@ -0,0 +1,31 @@ +From: Felix Fietkau +Date: Tue, 7 Jul 2026 09:34:16 +0200 +Subject: [PATCH] lib: avoid allocating print buffer before NULL check in + uc_error_message_indent + +Move the print buffer allocation after the guard that returns early on a +NULL message, so no buffer is allocated on that path. This keeps the +function leak-free when callers invoke it without having produced an error +string. + +Signed-off-by: Felix Fietkau +--- + +--- a/lib.c ++++ b/lib.c +@@ -220,13 +220,14 @@ uc_error_context_format(uc_stringbuf_t * + + void + uc_error_message_indent(char **msg) { +- uc_stringbuf_t *buf = xprintbuf_new(); ++ uc_stringbuf_t *buf; + char *s, *p, *nl; + size_t len; + + if (!msg || !*msg) + return; + ++ buf = xprintbuf_new(); + s = *msg; + len = strlen(s); + diff --git a/package/utils/ucode/patches/102-compiler-prevent-unbounded-recompilation-on-failed-m.patch b/package/utils/ucode/patches/102-compiler-prevent-unbounded-recompilation-on-failed-m.patch new file mode 100644 index 00000000000..02590f6a36f --- /dev/null +++ b/package/utils/ucode/patches/102-compiler-prevent-unbounded-recompilation-on-failed-m.patch @@ -0,0 +1,53 @@ +From: Felix Fietkau +Date: Mon, 6 Jul 2026 11:12:32 +0200 +Subject: [PATCH] compiler: prevent unbounded recompilation on failed + module imports + +A source's exports.offset of -1 marks a module whose compilation has been +entered but not completed. When a module failed to compile, uc_compiler_finish +freed its function and the error cascaded up through the importers via error +recovery, leaving each involved source at offset -1 with its function gone. The +re-entry guard only recognised an in-progress module by finding its function, so +those freed modules were recompiled again on every subsequent import. Across a +wide import graph this recompilation multiplies and the compiler appears to hang. + +Detect the offset -1 marker directly instead of relying on the presence of a +function: report a circular dependency when the function is still around, and +propagate the failure otherwise, rather than recursing into the module again. + +Signed-off-by: Felix Fietkau +--- + +--- a/compiler.c ++++ b/compiler.c +@@ -3479,15 +3479,26 @@ uc_compiler_compile_module_source(uc_com + + uc_program_function_foreach(compiler->program, fn) { + if (uc_program_function_source(fn) == source) { +- if (source->exports.offset == (size_t)-1) +- uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, +- "Circular dependency"); +- + loaded = true; + break; + } + } + ++ /* An offset of -1 marks a source whose compilation was entered but not yet ++ * completed. Re-entering it is either a circular dependency (its function is ++ * still present) or a module whose earlier compilation failed and freed its ++ * function. Recompiling in either case recurses without bound across a large ++ * import graph, so report the cycle and propagate the failure instead. */ ++ if (source->exports.offset == (size_t)-1) { ++ if (loaded) ++ uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, ++ "Circular dependency"); ++ else if (errp) ++ xasprintf(errp, "Module previously failed to compile\n"); ++ ++ return false; ++ } ++ + if (!loaded) { + /* We do not yet support linking precompiled modules at compile time, + turn static import operation into dynamic load one. */