Skip to content

SIGSEGV: factory returning class D extends <param>, chained two levels, then instantiated (zod v4 $constructor shape) #9364

Description

@proggeramlug

Repro (5 lines, SIGSEGV)

function mk(P) { class D extends (P ?? Object) {} return D; }
const A = mk(null);
const B = mk(A);
console.log("ok " + typeof new B());
node : ok object
perry: Segmentation fault (core dumped)   rc=139

The ?? is not the trigger — the explicit form crashes identically:

function mk(P) { const Base = P === null ? Object : P; class D extends Base {} return D; }
const A = mk(null);
const B = mk(A);
console.log("ok " + typeof new B());   // perry: SIGSEGV

What narrows it

variant perry
one level — mk(null), then new A() ok, node-identical
two levels — mk(null), mk(A), then new B() SIGSEGV

So the trigger is a factory function that returns class D extends <parameter>, invoked twice, where the second call's superclass is the class produced by the first — then instantiating the second. One level is fine; the second level is what dies. new B() is required (constructing the derived class); building the classes alone does not crash.

Not a regression: identical SIGSEGV on 83754818e (#9242) and on a03be729c (#9336), so it predates the whole #9341 window.

Why this shape matters

This is the exact idiom zod v4's $constructor uses, and zod is the largest single consumer of it in the claude-code bundle:

function $constructor(name, initializer, params) {
  class Definition extends (params?.Parent ?? Object) {}   // <- the factory
  
  return _;
}
const $ZodType   = $constructor("$ZodType",   init);
const $ZodString = $constructor("$ZodString", init, { Parent: $ZodType });   // <- second level

Instrumenting Object.defineProperty on the real node cli_2.1.112.js --help run shows this constructor factory executing 161 times, so the bundle drives this path hard. It does not crash there today (cc --help is byte-identical to node on 83754818e), which means perry is getting through the bundle's instance of the shape by some route the reduced form does not take — worth understanding, because it is a hard crash sitting directly under the most heavily used schema library in the corpus.

Found while bisecting #9341; filed separately because it is not that regression — both the green and the broken build crash on it.

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