Repository navigation
Closed
Labels
package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindingsFound by the 2026 package audit: compiling real npm packages from source instead of native bindings
Description
Found by the package audit (compiling real npm packages from source instead of Perry's native bindings) on
Perry e6dcb62 (v0.5.1587), Linux x64. The Function constructor must apply ToString to every argument, so new Function(['a','b'], body) has parameters a,b. Perry treats any non-string argument as an empty string. The resulting function has no parameters (ReferenceError: a is not defined when called), or no body when the arguments are spread. The bare-call form with a constant body returns undefined.
Reproduction
main.ts:
const body = ["return a", "+", "b"].join(" "); // any runtime-built body
const t = (label: string, mk: () => any) => {
try { const f = mk(); console.log(label, typeof f, typeof f === "function" ? f(2, 3) : ""); }
catch (e) { console.log(label, "THREW", String(e)); }
};
t("new Function('a,b', body) ", () => new Function("a,b", body)); // control
t("new Function(['a','b'], body) ", () => new Function(["a", "b"] as any, body));
t("new Function(['a','b'], 'return a + b') ", () => new Function(["a", "b"] as any, "return a + b"));
t("Function(['a','b'], 'return a + b') ", () => Function(["a", "b"] as any, "return a + b"));
t("new Function({toString: () => 'a,b'}, body)", () => new Function({ toString: () => "a,b" } as any, body));
t("new Function(...['a', 'b', body]) ", () => new Function(...["a", "b", body]));
t("new Function('a', ...['b', body]) ", () => new Function("a", ...["b", body]));perry compile main.ts -o out && ./out
PERRY_NO_AUTO_OPTIMIZE=1 perry compile main.ts -o out2 && ./out2 # identical resultExpected (Node 26.5.1)
new Function('a,b', body) function 5
new Function(['a','b'], body) function 5
new Function(['a','b'], 'return a + b') function 5
Function(['a','b'], 'return a + b') function 5
new Function({toString: () => 'a,b'}, body) function 5
new Function(...['a', 'b', body]) function 5
new Function('a', ...['b', body]) function 5
Actual (Perry, both modes)
new Function('a,b', body) function 5
new Function(['a','b'], body) THREW ReferenceError: a is not defined
new Function(['a','b'], 'return a + b') THREW ReferenceError: a is not defined
Function(['a','b'], 'return a + b') undefined
new Function({toString: () => 'a,b'}, body) THREW ReferenceError: a is not defined
new Function(...['a', 'b', body]) function undefined
new Function('a', ...['b', body]) function undefined
Impact
- lodash 4.18.1
_.templatepasses the import names as an array:Function(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues)atlodash.js:14992andlodash/template.js:271. Today the call fails earlier: through the local alias inlodash.js(Calling theFunctionconstructor through a value (F(...), a localvar Function,Function.bind,module.exports = Function) returnsundefined;fn.constructor(...)returns an object #10423) and as a bare runtime-bodyFunction(...)intemplate.js(Function(...),Function.apply(...)andFunction.call(...)with a runtime body compile to a stub that always throws, while the notice says "→ runtime interpreter" #10422). Once those are fixed, the template function would still throwReferenceError: _ is not defined, because it loses its_/escapeparameters (inferred from the repro above). - Any code generator that assembles
new Function(...paramsArray, body)or passes a parameter array is affected. Spread is the common way to forward a runtime-length parameter list.
Notes
- Works: string parameter lists in every spelling, including
'a, b','a,b', separate'a','b', defaults ('a = 1','b = 2'), destructuring ('{x, y}') andString(['a','b']). Both constant and runtime bodies work. - Code (verified by reading):
- Runtime:
crates/perry-runtime/src/object/global_this/builtin_thunks.rs:432-445.js_function_ctor_from_strings_impl'sarg_strreads each argument withstr_bytes_from_jsvalueand maps anything that is not a string (_ => String::new(),:443) to""instead of running ToString. The parameter list becomes empty, or the body does when the last argument is not a string. - Spread: the constant fold bails on any spread (
crates/perry-hir/src/lower/const_fold_fn.rs:267), and codegen'snew Functionpath (crates/perry-codegen/src/lower_call/new.rs:456-470) passes the lowered arguments as they are. The results fit a spread array reaching the runtime as one non-string argument, turning into an empty body and a function that returnsundefined. That mechanism is inferred, not traced. - Constant fold:
resolve_fn_ctor_arg(crates/perry-hir/src/lower/const_fold_fn.rs:590) has no array-literal case, soFunction(['a','b'], 'return a + b')is not folded. The site is classifiedconst-foldablefrom its body, getsProceed, and falls through to the generic call lowering, which returnsundefined(the same path as Calling theFunctionconstructor through a value (F(...), a localvar Function,Function.bind,module.exports = Function) returnsundefined;fn.constructor(...)returns an object #10423).
- Runtime:
- Separately observed, not part of this issue: a rest parameter with a runtime body is rejected by the interpreter.
new Function('...a', body)throwsTypeError: perry runtime interpreter (new Function, #6559): unsupported construct: rest parameter (...args), while the constant-body formFunction('...a', 'return a.length')works. - Sibling issues:
Function(...),Function.apply(...)andFunction.call(...)with a runtime body compile to a stub that always throws, while the notice says "→ runtime interpreter" #10422, Calling theFunctionconstructor through a value (F(...), a localvar Function,Function.bind,module.exports = Function) returnsundefined;fn.constructor(...)returns an object #10423. Related closed issues: runtime: dynamic code evaluation (new Function) for schema-codegen libs (ajv / fast-json-stringify / find-my-way) — blocks kimi-code #6559 (interpreter), feat(codegen): const-fold literal new Function bodies to native functions #1679 (constant fold).
Metadata
Metadata
Assignees
Labels
package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindingsFound by the 2026 package audit: compiling real npm packages from source instead of native bindings
Activity
fix(runtime): build functions from every Function constructor call shape