Parent: #934 Related: #936 (parser), #935 (flat chains), #932 (a compiler quadratic on the same shapes)
Confirmed by execution
Reproduced with the current test262 --eval binary; recursion from an lldb backtrace at the crash. In several rows the parser succeeds and the compiler is the first phase to overflow, so a parser-only guard would not prevent these.
| source | first crash | recursion |
|---|---|---|
destructuring catch nested in a function | ~7,390 deep | compileStmtList > compileStmt > compileTry > compileStmt > compileHoistedStmtList |
{ { … } } blocks, top level or in a function | ~14,773 deep | stmtListHasDisposableDecl <-> stmtHasDisposableDecl — the using-declaration pre-scan, which overflows before code generation starts |
a=a=… assignment chain | 15,000 | compileExpr > compileNamedExpr; parses fine |
x=>x=>… arrow chain | 10,000 | shadowAdd > functionBindingInventory > functionNeedsTdzChecks > compileFunction; parses fine |
!!!… unary chain | 30,000 | compileExpr > compileExpr; parses fine |
The destructuring-catch row fails at roughly half the depth the parser survives, so the compiler's per-level stack cost is the binding constraint for that shape.
Why this is broad
A mapping pass found 40 recursion chokepoints and no existing guard in compiler.zig. They fall into two groups:
- Code generation dispatchers:
compileStmt(3172),compileExpr(5155),compilePattern(4178),compileTailExpr(4552),compileOptionalValue(4924),compileFunction(6350). - Independent pre-scans, each re-walking full subtree depth before code generation reaches it:
stmtHasDisposableDecl/stmtListHasDisposableDecl(1517/1526),stmtContainsDisposableDeclDeep(1531),nodeHasYield(802),nameRefInClosure(1222),collectRepeatedBodyBindings(1136),shadowScanStmt(1871),tdzScanStmt(1983),collectFunctionLocals(6714),stmtCanEscapeAbruptly(1389), the pattern predicates (patternBindsName626,collectPatternBindingNames1066,patternHasEvaluationExpressions1102), and the environment-lexical pattern emitters (2650–2923).
Guarding only the dispatchers leaves the pre-scans: the blocks row above crashes in a pre-scan while compileStmt has plenty of headroom.
Design constraints
- Surface
RangeError: Maximum call stack size exceeded. It must not come back aserror.Unsupported: that silently falls back to the tree-walker, which then recurses over the same tree and crashes in evaluation instead (see the tree-walker issue). - Use
stack_scan.nearLimitwith a depth floor, asInterpreter.stackGuarddoes, so shallow code pays a single comparison. - Re-measure the #926 and #928 frozen workloads and the #932 curve after adding guards, to show they cost nothing on normal code.