Skip to content

Generic: apply recursive-protection to the Spanned::span walk - #2400

Open
fmcmac wants to merge 1 commit into
apache:mainfrom
fmcmac:upstream-span-recursive-protection
Open

fmcmac wants to merge 1 commit into
apache:mainfrom
fmcmac:upstream-span-recursive-protection

Conversation

@fmcmac

@fmcmac fmcmac commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Dialects: none

Spanned::span recurses once per AST node without stack protection. A query can parse, then abort when span() walks it:

SELECT * FROM t WHERE id = 0 OR id = 1 OR ... OR id = 9999

On main this parses and prints, then span() overflows an 8 MB stack. DataFusion's SQL planner calls span() while planning each query.

This adds recursive::recursive to span(), as the parser already does. It covers Query, SetExpr, Statement, Expr, SelectItem, TableFactor, Select and ObjectNamePart. It applies only with the existing recursive-protection feature. With it, 100,000 terms complete.

2 tests walk 200,000-deep ASTs on a 512 KiB thread. Each overflows without the change. Adds 1 fuzz seed.

I wrote this PR with Claude Code, and I have read and reviewed every line.

@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?

@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
span() recurses once per AST node with no stack protection. A query
can parse, then overflow the stack in span(). A generated 10,000-term
OR chain does this on an 8 MB thread.

Annotate span() with recursive::recursive, as the parser does. This
covers Query, SetExpr, Statement, Expr, SelectItem, TableFactor, Select
and ObjectNamePart. The attribute applies only with the
recursive-protection feature.

Generated-by: Claude Code
@fmcmac
fmcmac force-pushed the upstream-span-recursive-protection branch from 4aa3bc9 to 2fd0d26 Compare October 30, 2026 03:38
@fmcmac fmcmac changed the title Apply recursive-protection to the Spanned::span walk Generic: apply recursive-protection to the Spanned::span walk Oct 30, 2026
@fmcmac
fmcmac marked this pull request as ready for review October 30, 2026 03:38
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.26%. Comparing base (50813ee) to head (2fd0d26).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2400      +/-   ##
==========================================
+ Coverage   82.54%   83.26%   +0.72%     
==========================================
  Files          42       42              
  Lines       35553    35146     -407     
  Branches    35553    35146     -407     
==========================================
- Hits        29346    29264      -82     
+ Misses       2672     2360     -312     
+ Partials     3535     3522      -13     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fmcmac

fmcmac commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor Author

@iffyio @alamb

Rebased onto main as 1 commit.
The unrelated commits and the main merge are gone, and CI is green.

A real use case is an OR chain, such as SELECT * FROM t WHERE id = 0 OR id = 1 OR ... OR id = 9999. It parses and prints, then span() aborts the process with a stack overflow. DataFusion's planner calls span() while planning each query.

@alamb

alamb commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

run benchmark sql_planner

@alamb alamb 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.

Thank you for the help @fmcmac but I don't think this is the right direction. It patches a symptom not hte root cause

Comment thread src/ast/spans.rs
expr = Expr::Nested(Box::new(expr));
}
let _ = expr.span();
// Dropping the chain recurses too; leak it so the test measures only the span walk.

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.

If you can't drop the expression without overflowing the stack, I don't think allowing "spanned" to work is of much value

If we really want to support OR chains with 200,000 items, I think we need to use a non tree representation (there is an open ticket for this)

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.

Or maybe I am missing something 🤔

What do you think @LucaCappelletti94 ?

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.

Dropping a deep OR chain recurses too, but its frames are far smaller than those of span(). On a 2 MiB thread in release, span() on main overflows at about 2,400 OR terms, while dropping the same tree only overflows at about 32,000. In debug the figures are 313 against about 21,000. Of course all of these inputs are only adversarial stuff.

I agree with you that a different representation is the long term solution, and I think it is also worth adding that most of the spans current implementations are different flavours of wrong, so a refactoring regarding them will need to take place at some point in the future.

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.

5 participants