fix(ts-runtime): bound GLR stack-merge recursion to prevent stack overflow#1002
fix(ts-runtime): bound GLR stack-merge recursion to prevent stack overflow#1002halindrome wants to merge 1 commit into
Conversation
…rflow tree-sitter's GLR parser merges ambiguous parse-stack heads recursively in stack_node_add_link (internal/cbm/vendored/ts_runtime/src/stack.c), once per nesting level. On pathologically nested input whose grammar is ambiguous (Perl's paren-optional calls, f(f(f(...)))), that recursion overflows the native stack during the parse — a small ~1 MB Windows thread stack, and even an 8 MB POSIX stack at extreme depth — before any extraction runs. Grammars that are unambiguous here (Java, C++) never trigger the recursive merge. Cap the recursive merge at CBM_TS_STACK_MERGE_MAX_DEPTH (512) via a depth-tracked inner worker; the public stack_node_add_link is a thin wrapper seeding depth 0, so every existing call site is unchanged. Past the cap the ambiguity is left on the GLR stack instead of merged — a valid parse, never a wrong one. 512 frames is well within a 1 MB stack while far exceeding any realistic source nesting. Add perl_glr_deep_parse_recursion_capped: a crash-isolating regression that parses deep ambiguous Perl directly (a forked child on POSIX, in-process on Windows), bypassing cbm_extract_file's CBM_PERL_MAX_PARSE_NESTING pre-parse guard so the vendored cap itself is exercised rather than the guard. Narrow vendored-runtime change; no other parsing behavior is affected. The higher-level Perl pre-parse guard (issue DeusData#913 context) stays in place and can be retired in a follow-up once this cap is proven in the field. Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
QA Round 1Contract source: issue #913. Reviewer: Claude Code (claude-opus-4-8). Base Contract Verification
FindingsContract: none. Regression: none. Verified in depth: the recursion is genuinely bounded (only the depth-guarded call recurses); the non-recursive fall-through — dedup, Schema changeNone. Touches only a vendored C runtime file and a C test — no DDL, no migrations, no code reading/writing a DB column. Pre-existing issue discovered (observation — non-blocking)
Notes (already disclosed in the PR description)
Summary
SAST: skipped — GitHub code scanning is not accessible for this fork-contributor context (API 403); CI CodeQL / security-static checks run on GitHub. Verdict: clean round. The change minimally and correctly bounds the only recursive merge path, preserves the wrapper signature and sole call site, builds clean, and its new regression passes. The single suite failure is in an untouched file and unrelated. QA performed by Claude Code (claude-opus-4-8) |
Summary
Bounds the recursive GLR stack-merge in the vendored tree-sitter runtime (
internal/cbm/vendored/ts_runtime/src/stack.c) so pathologically nested, grammar-ambiguous input can no longer overflow the native stack during parsing. Split out of #461 at maintainer request — runtime patches warrant separate provenance/risk review.Closes #913.
Problem
stack_node_add_linkmerges ambiguous parse-stack heads recursively — once per nesting level. Perl's paren-optional call grammar (f(f(f(...)))) makes every level ambiguous, so a deep chain drives that recursion until it overflows a small (~1 MB Windows) thread stack, and even an 8 MB POSIX stack at extreme depth — before any extraction runs. Unambiguous grammars (Java, C++) never hit the recursive merge.Fix
stack_node_add_link_innercaps the recursive merge atCBM_TS_STACK_MERGE_MAX_DEPTH(512). The publicstack_node_add_linkis a thin wrapper seeding depth 0, so every existing call site is unchanged.Regression
perl_glr_deep_parse_recursion_capped(tests/test_stack_overflow.c) parses deep ambiguous Perl directly — a forked child on POSIX (crash isolation), in-process on Windows — bypassingcbm_extract_file'sCBM_PERL_MAX_PARSE_NESTINGpre-parse guard so the vendored cap itself is exercised rather than the guard. Without the cap this overflows the 1 MB Windows stack (Windows CLANG64 CI is the negative-case arbiter, as with #461); with it, the parse returns cleanly.Scope
The higher-level Perl pre-parse guard added in #461 stays in place — this PR is scoped to the vendored runtime cap only. That guard can be retired in a follow-up once this cap is proven in the field.
Provenance
Self-contained depth guard in the vendored runtime; no upstream-grammar regeneration. Verified:
scripts/build.shclean under-Werror;scripts/test.shgreen (the one unrelated red,cli_hook_gate_script_no_predictable_tmp_issue384, is pre-existing/environmental in the local sandbox and does not touch this code);clang-formatapplied to the test.