Skip to content

[parser] Drop deep expression chains iteratively to avoid stack overflow#25480

Closed
samuelcolvin wants to merge 1 commit into
astral-sh:mainfrom
samuelcolvin:iterative-expr-drop
Closed

[parser] Drop deep expression chains iteratively to avoid stack overflow#25480
samuelcolvin wants to merge 1 commit into
astral-sh:mainfrom
samuelcolvin:iterative-expr-drop

Conversation

@samuelcolvin

Copy link
Copy Markdown
Contributor

Alternative solution to #25462 using a custom drop implementation, and thereby doesn't limit left-associative binary chain limits.

AI Summary

The parser builds left-associative binary chains (a + b + c + ...) and postfix chains (f()()..., a[0][0]..., a.b.c...) with iterative loops, so their depth is bounded only by the input length rather than by the recursion guard. Such a tree parses fine, but the compiler-derived drop glue recurses once per level (~150 bytes of stack each) and overflows the native stack when the tree is dropped — a denial-of-service vector for any consumer that parses untrusted source on a normally-sized stack.

Give the four node types that form these spines a manual Drop that walks the recursive edge (left/func/value), detaching each node and replacing the edge with a trivial placeholder before the node drops, so no drop ever recurses and a spine of any depth is torn down in O(1) stack. A single iter_expr_drop helper handles all four edges in one match, so a chain that mixes kinds is torn down in one flat loop.

Unlike a parse-time depth cap, this rejects no valid Python and keeps ty's support for large flat expressions intact.

The parser builds left-associative binary chains (`a + b + c + ...`) and
postfix chains (`f()()...`, `a[0][0]...`, `a.b.c...`) with iterative loops, so
their depth is bounded only by the input length rather than by the recursion
guard. Such a tree parses fine, but the compiler-derived drop glue recurses
once per level (~150 bytes of stack each) and overflows the native stack when
the tree is dropped — a denial-of-service vector for any consumer that parses
untrusted source on a normally-sized stack.

Give the four node types that form these spines a manual `Drop` that walks the
recursive edge (`left`/`func`/`value`), detaching each node and replacing the
edge with a trivial placeholder before the node drops, so no drop ever recurses
and a spine of any depth is torn down in O(1) stack. A single `iter_expr_drop`
helper handles all four edges in one `match`, so a chain that mixes kinds is
torn down in one flat loop.

Unlike a parse-time depth cap, this rejects no valid Python and keeps ty's
support for large flat expressions intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@astral-sh-bot

astral-sh-bot Bot commented May 30, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented May 30, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Full report with detailed diff (timing results)

@astral-sh-bot

astral-sh-bot Bot commented May 30, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@MichaReiser

Copy link
Copy Markdown
Member

Thank you. We can consider that if we end up deciding against #25464. For now, I'd prefer focusing the conversation on that PR (I plan to open it for review soon).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants