Parent: #934 Related: #937 (compiler), #936 (parser), #935 (flat chains)
The gap
Interpreter.stackGuard protects JavaScript function calls: each call is charged to depth and probed with stack_scan.nearLimit. But the tree-walker also recurses over AST depth inside a single call — nested statements, hoisting, Annex B scanning, destructuring, AST copying — and none of that is charged. So deeply nested source crashes the process from inside one function body, with no JS call involved.
Confirmed by execution
Reproduced with the current test262 --eval FILE tree binary. For the last two rows the frame on top of the stack at the crash is an incidental leaf (getIteratorFor, alloc) — whatever happened to be running when the guard page was hit. The recursion that consumed the stack is the cycle given.
| source (tree mode) | depth | recursion |
|---|---|---|
{ { … x=1; } } blocks, sloppy | 10,000 | Annex B analysis: annex_b.Analysis.scanStatement via Interpreter.collectAnnexBLegacy |
same, 'use strict' (skips Annex B) | 10,000 | Interpreter.hoistVarsIn > hoistVarNames > evalStatements |
var [[[…z…]]] = o array pattern | 16,000 | bindPattern > destructureArray > bindPatternWithReference |
| class with a private field, method body nested 10,000 blocks | 10,000 | deepCopyNodes > deepCopyNode from buildClass |
Blocks nest just as deeply in strict code, so fixing only the Annex B scan would leave strict code crashing in hoisting.
Chokepoints
From a mapping pass, unguarded:
- Evaluation:
beginNodeEvaluation(interpreter.zig:5662),bindPattern(15889),tdzBindPattern(4576),predeclareForLexicalPattern(7053),checkRestrictedGlobalLexicalPattern(4602). - Hoisting and analysis done at evaluation time:
hoistVarsIn(7442),hoistPatternVars(7479),collectEvalVarNamesNode(4427),evalPatternVarNames(4458),collectPatternNames(7075),patternBindsName(10189),labeledStatementTargetsIteration(5323),annexbStmtMayNeedScan(7517), andannex_b.Analysis.scanStatement/appendPattern(annex_b.zig:121/54). - AST rewriting and copying:
deepCopyNode(8388),rewritePrivateNamesInNode(8167).
Host value formatting — narrow
print(a) on a 300,000-deep nested array segfaults in every execution mode. This is not a general toString bug: String(a), ""+a, a.join() and [a].toString() all correctly throw RangeError: Maximum call stack size exceeded. in every mode. Only the host print path formats values without the guard. Embedders exposing a similar host logging function would hit the same gap.
Not yet confirmed by execution
gc.traceChunk and gc.relocateChunk (gc.zig:1755/1761) were flagged as recursive over structure depth, but no crash has been reproduced. They are listed so they are not lost, and are not claimed.
Design constraints
Charge each of these recursions to the existing depth and stackGuard — the same contract #515 used for JSON — so failures are the standard catchable RangeError, and preserve OutOfMemory, Throw and internal control-flow errors rather than relabelling them.