Summary
A SIGSEGV (rc 139) occurs when a proxy construct trap that returns a non-object (which correctly throws a TypeError) is followed by an instanceof and a method call on an instance built via new Proxy(<class>, {}). Individually each step is fine; the crash needs the specific sequence, and it does not fire before the descriptor/proxy GC-scanner landed — so it looks like a hole in that scanner.
Minimal repro
function show(label: string, fn: () => unknown) {
try { console.log(label, "ok", JSON.stringify(fn())); }
catch (e: any) { console.log(label, "throw", e?.constructor?.name); }
}
// A proxy whose construct trap returns a non-object → TypeError (per spec).
const badReturn: any = new Proxy(function BadReturn() {}, { construct() { return 1 as any; } });
show("badret", () => Reflect.construct(badReturn, [])); // prints "throw TypeError"
class Thing { value: string; constructor(v: string) { this.value = v; } method() { return "m:" + this.value; } }
show("case9", () => {
const W: any = new Proxy(Thing, {});
const i: any = new W("x");
return [i instanceof Thing, i.method()]; // ← SIGSEGV here
});
console.log("END"); // never reached under Perry
Perry prints badret throw TypeError then segfaults (rc 139); Node prints both lines + END.
Narrowing (all verified):
- Remove the
badReturn construct-trap-throw case → no crash.
- Change
case9 to return i.value only (drop instanceof/method) → no crash.
i instanceof Thing alone, or i.method() alone → no crash. Only the two together after the bad-return throw crash.
- Replacing
new Proxy(Thing, {}) with a plain new Thing(...) → no crash.
The "needs a specific op sequence + a prior throwing path + GC-triggering work" signature is a classic dangling-GC-root / stale-side-table-pointer.
Suspected cause
#5937 (fix(gc): scan descriptor/proxy side tables; root static-field globals, 53e4336) added the proxy/descriptor side-table scanners. This repro lives squarely in that path: the PROXIES side table holds ProxyEntry { target, handler } (raw NaN-box bits), the bad-return construct throws mid-trap, and the later Proxy(class) construct + instanceof/method triggers a minor GC whose scan of the proxy/descriptor tables appears to leave a stale/rewritten pointer that the subsequent access dereferences. Likely an incomplete rewrite (a target/handler slot, or a descriptor owner key, not fixed up after the throwing trap).
Provenance
Reproduces on origin/main (e2815e3), rc 139 — NOT specific to any feature branch. Surfaces the node-suite case test-parity/node-suite/object/reflect-proxy-construct.ts (object module 24→23). Does NOT reproduce at the pre-#5937 baseline bfa0f258d (object 24/24), so it was introduced between that point and current main — the #5937 scanner is the prime suspect.
Summary
A SIGSEGV (rc 139) occurs when a proxy construct trap that returns a non-object (which correctly throws a
TypeError) is followed by aninstanceofand a method call on an instance built vianew Proxy(<class>, {}). Individually each step is fine; the crash needs the specific sequence, and it does not fire before the descriptor/proxy GC-scanner landed — so it looks like a hole in that scanner.Minimal repro
Perry prints
badret throw TypeErrorthen segfaults (rc 139); Node prints both lines +END.Narrowing (all verified):
badReturnconstruct-trap-throw case → no crash.case9to returni.valueonly (dropinstanceof/method) → no crash.i instanceof Thingalone, ori.method()alone → no crash. Only the two together after the bad-return throw crash.new Proxy(Thing, {})with a plainnew Thing(...)→ no crash.The "needs a specific op sequence + a prior throwing path + GC-triggering work" signature is a classic dangling-GC-root / stale-side-table-pointer.
Suspected cause
#5937(fix(gc): scan descriptor/proxy side tables; root static-field globals, 53e4336) added the proxy/descriptor side-table scanners. This repro lives squarely in that path: thePROXIESside table holdsProxyEntry { target, handler }(raw NaN-box bits), the bad-return construct throws mid-trap, and the laterProxy(class)construct +instanceof/methodtriggers a minor GC whose scan of the proxy/descriptor tables appears to leave a stale/rewritten pointer that the subsequent access dereferences. Likely an incomplete rewrite (atarget/handlerslot, or a descriptor owner key, not fixed up after the throwing trap).Provenance
Reproduces on
origin/main(e2815e3), rc 139 — NOT specific to any feature branch. Surfaces the node-suite casetest-parity/node-suite/object/reflect-proxy-construct.ts(object module 24→23). Does NOT reproduce at the pre-#5937 baselinebfa0f258d(object 24/24), so it was introduced between that point and current main — the #5937 scanner is the prime suspect.