Skip to content

Apply recursive-protection to the Spanned::span walk - #2400

Draft
fmcmac wants to merge 4 commits into
apache:mainfrom
fmcmac:upstream-span-recursive-protection
Draft

fmcmac wants to merge 4 commits into
apache:mainfrom
fmcmac:upstream-span-recursive-protection

Conversation

@fmcmac

@fmcmac fmcmac commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What

The Spanned::span implementations are plain unbounded recursion — one stack frame per AST node. Computing the span of a deeply nested AST (a deep expression tree, nested subqueries, or a long left-associative boolean chain) recurses as deep as the AST and can overflow the thread stack, aborting the process.

Notably, a query can parse successfully — the parser is already guarded by the recursive-protection feature — and then overflow later, when its span is computed by a downstream consumer (e.g. during query planning). We hit this in production via a downstream planner that calls .span() on user-supplied ASTs.

Fix

The parser already opts into stack-overflow protection behind the recursive-protection feature (recursive::recursive, backed by stacker::maybe_grow); the span walk was simply never given the same treatment. This annotates the recursive span() implementations for the container nodes — Expr, SetExpr, Query, Select, SelectItem, TableFactor, Statement — so at least one node on every deep-recursion cycle grows the stack on demand.

  • No new dependency (the recursive crate and feature already exist).
  • A no-op when recursive-protection is disabled (same #[cfg_attr(...)] gating the parser uses).
  • Annotating one node per cycle is sufficient for stacker::maybe_grow to fire; these seven cover the deep axes (expression nesting, set-op/subquery/join nesting, statement nesting).

Test

Adds a regression test that builds a deeply nested AST and walks its span on a deliberately small (512 KiB) thread stack: it overflows without the annotations and completes with them.

The `Spanned::span` implementations are plain unbounded recursion — one
stack frame per AST node. Computing the span of a deeply nested AST (a deep
expression tree, nested subqueries, or a long left-associative boolean
chain) therefore recurses as deep as the AST and can overflow the thread
stack, aborting the process. A query can parse successfully — the parser is
already guarded — and then overflow later when its span is computed (e.g.
during query planning by a downstream consumer).

The parser already opts into stack-overflow protection behind the
`recursive-protection` feature (`recursive::recursive`, backed by
`stacker::maybe_grow`); the span walk was simply never given the same
treatment. Annotate the recursive `span()` implementations for the container
nodes (`Expr`, `SetExpr`, `Query`, `Select`, `SelectItem`, `TableFactor`,
`Statement`) so at least one node on every deep-recursion cycle grows the
stack on demand. No new dependency and a no-op when the feature is disabled.

Adds a regression test that walks a deeply nested AST on a small thread
stack: it overflows without the annotations and completes with them.

Signed-off-by: fmcmac <[email protected]>
@fmcmac
fmcmac force-pushed the upstream-span-recursive-protection branch from ac9702d to b7cbd09 Compare July 10, 2026 02:04
@iffyio

iffyio commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@fmcmac could you take a look at the ci failures when you have the time?

fmcmac and others added 2 commits July 15, 2026 09:05
Resolve 7 clippy errors that fail the lint CI job under a newer
toolchain:

- useless_borrows_in_formatting: drop redundant `&` in write!/format!
  args (ast/ddl.rs, ast/query.rs, ast/mod.rs, parser/mod.rs x2)
- needless_return_with_question_mark: drop redundant `?` on returned
  Err/parser_err! in parser/mod.rs x2

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The lib-only fixes let clippy proceed past the library to --all-targets,
surfacing the same lint in examples and tests under clippy 0.1.97:

- examples/cli.rs: drop redundant `&` in println!/panic! args
- tests/sqlparser_postgres.rs x2, tests/sqlparser_common.rs x3: drop
  redundant `&str_op` in format! args

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

@iffyio iffyio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @fmcmac! Could you fix up the check failures on the branch (hopefully the unrelated changes are due to the clippy and should go away after rebasing on main)

@alamb

alamb commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Is there a real issue / usecase (like a sql query you parsed) that this is fixing?

@alamb

alamb commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Marking as draft as I think this PR is no longer waiting on feedback and I am trying to make it easier to find PRs in need of review. Please mark it as ready for review when it is ready for another look

@alamb
alamb marked this pull request as draft September 3, 2026 14:30
@LucaCappelletti94 LucaCappelletti94 added the waiting on contributor The review needs further refinements by its author label Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting on contributor The review needs further refinements by its author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants