Carry the parse depth through proc and UDF descent - #484
Conversation
#456's StoredProc/UDF descents called ParseStatementAndChildren without the depth argument, so recursion depth silently reset to zero at every procedure boundary. The MaxParseDepth guard - which exists so a maliciously deep plan throws a catchable error instead of an uncatchable StackOverflowException - could then never fire across StoredProc/UDF nesting: a crafted .sqlplan alternating StmtSimple > StoredProc > Statements a few thousand levels deep (about sixty bytes per level) killed the whole process, from any plan-open route that reaches the parser. ParseStatement now takes the caller's depth and both descents pass depth + 1, so the guard sees the true nesting. The same pass closes the sibling gap: synchronous Parse had no document-size ceiling at all, while ParseAsync capped at 16MB via XmlReaderSettings.MaxCharactersInDocument. Parse is the path the app's PlanViewerControl, the web viewer, and the analysis pipeline actually use, so it now enforces the same MaxParseCharacters limit with a length check (the input is already a string; the limit is characters, not bytes) thrown as the parser's usual catchable InvalidOperationException. Tests generate the plan XML instead of shipping a fixture - a depth bomb is three elements repeated 1,100 times. The bomb parses on a deliberately large-stack thread with nesting just past the guard, so both outcomes are deterministic: fixed, the guard fires at depth 1,001; regressed, the parse completes in the headroom and the test fails on a null ParseError instead of killing the test host (verified against the unfixed parser). A 50-level companion pins that legitimate nesting still parses every level, and an oversized well-formed document pins the sync size cap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PvAv72Pwb8czsjDWsCCk7n
|
Reviewed. The depth-threading fix is correct: Tests are well-targeted: the depth-bomb test isolates the parse on a dedicated 8MB-stack thread so it fails on the assertion rather than the test host if the guard regresses, and the 50-level positive test walks the actual One thing worth a sentence of awareness rather than a fix: No version bump needed here (this PR targets |
What does this PR do?
Fixes the highest-severity finding from the 2026-09-02 adversarial review of the Aug 12 - Sep 2 range.
#456's StoredProc/UDF descents called
ParseStatementAndChildrenwithout the depth argument, so recursion depth silently reset to zero at every procedure boundary and theMaxParseDepthguard could never fire across procedure nesting. A crafted .sqlplan alternatingStmtSimple > StoredProc > Statementsa few thousand levels deep (~60 bytes per level) killed the process with an uncatchable StackOverflowException, from any plan-open route. Shipped in v1.22.0+.ParseStatement(where the descent sites live) and both descents passdepth + 1.Parsepath (app UI, web viewer, analysis pipeline) gains the same 16MB ceilingParseAsyncalways had, via the shared const.How was this tested?
Full suite at dev tip + fix: 408 tests, 407 passed, 1 platform skip, 0 failed, on Windows. Red-green verified against the unfixed parser. Web and App projects build clean.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PvAv72Pwb8czsjDWsCCk7n