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. new F(...) works when F holds the Function constructor, but a plain call F(...) silently returns undefined, and (function(){}).constructor(...) returns a plain object. There is no throw and no compile-time notice, so the failure shows up later as Function.prototype.apply was called on a value that is not a function.
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)); }
};
const F: any = Function;
t("new Function(a, b, body) ", () => new Function("a", "b", body)); // control
t("new F(a, b, body) ", () => new F("a", "b", body)); // control
t("F(a, b, body) ", () => F("a", "b", body));
t("F(a, b, 'return a + b') ", () => F("a", "b", "return a + b"));
t("Function.bind(null, a, b)(body) ", () => Function.bind(null, "a", "b")(body));
t("(function () {}).constructor(...) ", () => (function () {}).constructor("a", "b", body));
function lodashShape(context: any) {
var Function = context.Function; // lodash.js:1456
t("var Function = ctx.Function; Function(...)", () => Function("a", "b", body));
}
lodashShape(globalThis);perry compile main.ts -o out && ./out
PERRY_NO_AUTO_OPTIMIZE=1 perry compile main.ts -o out2 && ./out2 # identical result(The literal new Function control line also records a dynamic-code site, so the auto-optimize build includes the interpreter and both modes behave the same. See #10421.)
Expected (Node 26.5.1)
new Function(a, b, body) function 5
new F(a, b, body) function 5
F(a, b, body) function 5
F(a, b, 'return a + b') function 5
Function.bind(null, a, b)(body) function 5
(function () {}).constructor(...) function 5
var Function = ctx.Function; Function(...) function 5
Actual (Perry, both modes)
new Function(a, b, body) function 5
new F(a, b, body) function 5
F(a, b, body) undefined
F(a, b, 'return a + b') undefined
Function.bind(null, a, b)(body) undefined
(function () {}).constructor(...) object
var Function = ctx.Function; Function(...) undefined
Impact
- lodash 4.18.1
_.template(mainlodash.js, whichimport _ from 'lodash'resolves to undercompilePackages: ["lodash"]) throwsTypeError: Function.prototype.apply was called on a value that is not a function.runInContextbinds a localFunction = context.Function(lodash.js:1456)._.templatethen callsFunction(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues)(lodash.js:14992). Because the identifier is a local, the eval classifier skips it (eval_strict.rs:80), and the call returnsundefined. Fixing this alone is not enough for lodash:importsKeysis an array, which isnew Functiondrops non-string arguments instead of calling ToString:new Function(['a','b'], body)loses its parameters, and spread argument lists produce an empty body #10424. - Any CJS indirection that re-exports the constructor has the same problem, for example a module with
module.exports = Functionwhose importer calls it (verified: returnsundefined). The same goes forFunction.bind(...)partial application and forx.constructor(...)used to build functions from code strings.
Notes
- Construction through the same value works, because
crates/perry-runtime/src/object/class_registry/construct.rs:1145-1150identifies the intrinsicFunctionby closure identity and routes it tojs_function_ctor_from_strings. No equivalent routing was found for a plain [[Call]] of that value, so the call falls through and producesundefined. That location is inferred: the call path was not traced to a specific line. - The constant-body variant
F("a", "b", "return a + b")fails the same way. The constant fold only handles literalFunction/new Functioncallees, so this is not about runtime bodies. (function () {}).constructor("a", "b", <body>)returns an object (typeofis"object") for a constant body"return a + b"too.new ((function () {}).constructor)("a", "b", "return a + b")works and returns 5.- A bare
Function(["a","b"], "return a + b")also returnsundefined. The constant fold rejects the array argument, and the site then falls to the same generic call lowering. Seenew Functiondrops non-string arguments instead of calling ToString:new Function(['a','b'], body)loses its parameters, and spread argument lists produce an empty body #10424. - Sibling issues: Auto-optimize drops the
new Functioninterpreter when the only Function-constructor sites are aliases or known codegen libraries (find-my-way / ajv / fast-json-stringify) #10421 (auto-optimize omits the interpreter),Function(...),Function.apply(...)andFunction.call(...)with a runtime body compile to a stub that always throws, while the notice says "→ runtime interpreter" #10422 (literalFunction(...)/Function.applywith a runtime body compiles to a throwing stub),new Functiondrops non-string arguments instead of calling ToString:new Function(['a','b'], body)loses its parameters, and spread argument lists produce an empty body #10424 (array / non-string parameter arguments). - Related closed issues: runtime: dynamic code evaluation (new Function) for schema-codegen libs (ajv / fast-json-stringify / find-my-way) — blocks kimi-code #6559, AOT: compile eval / new Function to a deferred runtime error (with compile-time notice) instead of refusing the build; add strict mode to block #5206.
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