Skip to content

Calling the Function constructor through a value (F(...), a local var Function, Function.bind, module.exports = Function) returns undefined; fn.constructor(...) returns an object #10423

Closed

Description

@proggeramlug

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 (main lodash.js, which import _ from 'lodash' resolves to under compilePackages: ["lodash"]) throws TypeError: Function.prototype.apply was called on a value that is not a function. runInContext binds a local Function = context.Function (lodash.js:1456). _.template then calls Function(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 returns undefined. Fixing this alone is not enough for lodash: importsKeys is an array, which is new Function drops 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 = Function whose importer calls it (verified: returns undefined). The same goes for Function.bind(...) partial application and for x.constructor(...) used to build functions from code strings.

Notes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions