Skip to content

Calling undefined as a function silently no-ops instead of throwing TypeError #648

Description

@proggeramlug

Repro

const obj: any = {};
try {
  obj.missingFn(1, 2);
  console.log("did NOT throw");
} catch (e) {
  console.log("threw:", (e as Error).message);
}

Actual (Perry)

did NOT throw

Expected (Node)

threw: obj.missingFn is not a function

Impact

This is the single biggest leverage fix for the parity test suite. Calling a non-existent property as a function should throw TypeError: <expr> is not a function. Perry silently no-ops, which causes a cascading set of failures across the runtime parity tests:

  1. test_parity_timers HANGS forever — the test does await new Promise(r => timers.setTimeout(cb, 0, 2, 3)). Since Namespace import for unrecognized node:* sub-paths resolves to true (TAG_TRUE) #629's partial fix, timers is an empty object instead of a boolean, so timers.setTimeout is undefined. The executor calls undefined(cb, 0, 2, 3) which Perry no-ops; resolve never fires; the await pends forever. Yesterday's binary threw TypeError: (boolean).setTimeout is not a function and the surrounding try/catch handled it cleanly. Today the test must be killed by the runner timeout.

  2. Silent mid-test termination in many parity modules — test_parity_os finishes after 15 of 60 expected lines; test_parity_tls after 36 of 76; test_parity_perf_hooks after 43 of 84; test_parity_http2 after 62 of 137. In each case Perry runs into an undefined call mid-script and silently exits the surrounding async path without surfacing the error to the diff.

  3. Confusing parity output — many user-visible bugs end up looking like "Perry produces fewer lines than Node" rather than the more honest "Perry threw at line X of test Y". A correct TypeError would be caught by per-section try/catch in the parity tests and surface as a one-line ERROR: row, dramatically improving the gap report quality.

Why this changed

Yesterday Perry NaN-boxed unrecognized node:* namespaces as TAG_TRUE (#629). Calling (true).foo() is a property access on a primitive boolean, which Node correctly throws on — and Perry's existing primitive-method-dispatch path threw too. After #629's partial fix flipped the namespace to an empty object, the path now goes through Perry's object-property lookup, which returns undefined. Calling undefined() is a separate code path that silently no-ops.

So the regression isn't in #629's change itself — it's that #629's fix exposed an existing latent bug in Perry's "call undefined as function" handling.

Acceptance

The 6-line repro above prints threw: obj.missingFn is not a function matching Node, AND test_parity_timers runs to completion without timing out (regardless of whether timers.setTimeout is actually implemented).

Related

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions