Parent: #934 (robustness: deep structures crash instead of throwing RangeError) Related: #938, whose review found these; that fix covers only recursion that follows source nesting
Confirmed by execution
Each program builds a deep runtime structure iteratively (no deep source, no deep JS call stack), then performs one operation. Run with test262 --eval built at b895723 (ReleaseFast), and with node v24.4.1 as the oracle:
| operation | zig-js | node |
|---|---|---|
a.flat(Infinity) where a contains itself | SIGSEGV | RangeError: Maximum call stack size exceeded |
b.flat(Infinity) on a 300,000-deep array | SIGSEGV | RangeError |
c.flat(Infinity) on a 50-deep array (control) | 1 | 1 |
Object.getPrototypeOf(p), p a chain of 200,000 trap-less Proxy objects | SIGSEGV | RangeError |
Object.isExtensible(p), same chain | SIGSEGV | RangeError |
Object.defineProperty(p, 'x', {value: 1}), same chain | SIGSEGV | RangeError |
Object.prototype.toString.call(p), chain of 200,000 proxies over an array | SIGSEGV | RangeError |
f(), f bound 100,000 times | SIGSEGV | 7 (node runs it, at 300,000 links too) |
({}) instanceof g, g bound 100,000 times | SIGSEGV | RangeError |
The whole operation runs under one native built-in call (or one instanceof), so stackGuard runs at most once, before the recursion starts. #938 guards recursion over source nesting; none of these involve deep source.
Recursion sites (from code reading; the table above is what was executed)
- Array.prototype.flat:
flattenInto(src/interpreter.zig:19624) <->flattenIntoLen(19636), once per nested array level. Neither is a tail call, because each keeps temp roots under adefer.depthis a Number, soInfinity - 1never stops a self-referential array. - Proxy forwarding without a trap:
proxyGetProto(15031),proxyIsExtensible(15055),proxyPreventExt(15074) andproxySetProto(15097) recurse once per proxy link. So do builtins.zigapplyDescriptor(1771, via [[DefineOwnProperty]]) and the [[GetOwnProperty]] forwarding. - IsArray over proxies:
objectToStringIsArray(23667) recurses per link. It runs from Object.prototype.toString, ArraySpeciesCreate, concat's IsConcatSpreadable, JSON.stringify's replacer check and flat.Array.isArray(builtins.zigisArrayValue) is already iterative. - Bound functions: [[Call]]/[[Construct]] follow
boundFunctiontargets recursively throughcallValueWithThisAtSite/constructNTAtSite.instanceofgoes throughordinaryHasInstance(21935) ->instanceOf(21892) per bound link without crossing a call boundary.
Direction
- Where the spec algorithm is recursive but the chain has no user-observable step between links (a trap-less proxy, a bound function, IsArray), a loop with a hop count can replace the recursion. A bound call then works as it does in node, and a proxy chain throws once the count exceeds what the stack could hold.
- Where user code can run between levels (flat reads elements, which can hit getters), keep the recursion but check the stack with
Interpreter.checkNesting(#938), so it throws the same catchable RangeError.
A regression case should build each structure iteratively in one process per shape. Several of them crash, and a crash in one would hide the rest.