Repository navigation
Commit f9c1e9e
fix: prevent unparser stack overflow on deeply nested expressions (#23058)
## Which issue does this PR close?
- Closes #23056.
## Rationale for this change
`Unparser::expr_to_sql` overflows the OS stack on deeply nested
expressions, and — surprisingly — it does so **even with the
`recursive_protection` feature enabled** (the default for the
`datafusion` crate).
While investigating I found the root cause is subtler than "a function
is missing `#[recursive]`":
`expr_to_sql_inner` **is** already annotated and **is** re-entered on
the function-argument / dialect-override paths, so the trampoline does
fire. The real problem is that the `recursive` crate's default red zone
is **128 KiB**, but a single `expr_to_sql_inner` stack frame in debug
builds is **~130 KiB** — *larger than the red zone*.
`stacker::maybe_grow` only grows when `remaining < red_zone`, so a frame
can pass the check and then overflow before the next checkpoint. The
unparser installed **no `StackGuard`**, unlike the planner, which
already does exactly this in `query.rs`:
```rust
// query.rs
let _guard = StackGuard::new(256 * 1024);
```
with a comment noting the same debug-frame-size issue (PR #13310).
Secondary leaks: several internal recursion sites recursed through the
public, un-annotated `expr_to_sql` (function arguments, `make_array`,
`array_element`, `named_struct`, `map`, and the `array_has` /
`date_part` dialect overrides), and `remove_unnecessary_nesting` (pretty
mode) was not annotated at all — it would be the next overflow site once
the first is fixed.
## What changes are included in this PR?
The recursive design is kept (an iterative rewrite of the tree-building
unparser would be a large, high-risk change for no benefit over the
trampoline). The fix mirrors the planner:
- Install `StackGuard::new(256 * 1024)` once at the top-level
`expr_to_sql`.
- Add an annotated `pub(crate) expr_to_sql_with_nesting` recursion core
that all internal recursion sites call. This keeps pretty-mode behavior
identical (each level still runs `remove_unnecessary_nesting`) while
making every nesting level a stack-growth checkpoint.
- Annotate `remove_unnecessary_nesting` with `#[recursive]`.
The PR is split into two commits for review: the failing regression test
first, then the fix.
## Are these changes tested?
Yes. `test_deeply_nested_expr_does_not_overflow_stack` (gated on
`recursive_protection`) unparses a depth-2000 `array_has` chain on
`PostgreSqlDialect` and a depth-2000 binary chain on a 2 MiB thread. It
aborts the process on `main` and passes with the fix. I verified
empirically that with the default 128 KiB red zone the test still
aborts, and that 256 KiB carries both paths past depth 5000 on a
realistic thread.
Note: the guarantee holds on realistically-sized thread stacks (≥1–2
MiB). The 512 KiB-stack figure in the issue's repro is too small to
bootstrap the heavier `array_has` override path even after the fix, but
that is the same memory floor the planner side has and is not a
configuration used by DataFusion in practice.
## Are there any user-facing changes?
No public API changes. Deeply nested expressions that previously aborted
the process now unparse successfully when `recursive_protection` is
enabled.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01QyHc4z88pC8nQLpFx7z2vM
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>1 parent 073e9ed commit f9c1e9e
3 files changed
Lines changed: 103 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
434 | 434 | | |
435 | 435 | | |
436 | 436 | | |
437 | | - | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
438 | 440 | | |
439 | | - | |
| 441 | + | |
440 | 442 | | |
441 | 443 | | |
442 | 444 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
97 | 118 | | |
98 | 119 | | |
99 | 120 | | |
| |||
681 | 702 | | |
682 | 703 | | |
683 | 704 | | |
684 | | - | |
| 705 | + | |
685 | 706 | | |
686 | 707 | | |
687 | 708 | | |
| |||
708 | 729 | | |
709 | 730 | | |
710 | 731 | | |
711 | | - | |
712 | | - | |
| 732 | + | |
| 733 | + | |
713 | 734 | | |
714 | 735 | | |
715 | 736 | | |
| |||
732 | 753 | | |
733 | 754 | | |
734 | 755 | | |
735 | | - | |
| 756 | + | |
736 | 757 | | |
737 | 758 | | |
738 | 759 | | |
| |||
802 | 823 | | |
803 | 824 | | |
804 | 825 | | |
805 | | - | |
| 826 | + | |
| 827 | + | |
806 | 828 | | |
807 | 829 | | |
808 | 830 | | |
809 | 831 | | |
810 | 832 | | |
811 | 833 | | |
812 | 834 | | |
813 | | - | |
| 835 | + | |
| 836 | + | |
814 | 837 | | |
815 | 838 | | |
816 | 839 | | |
| |||
944 | 967 | | |
945 | 968 | | |
946 | 969 | | |
947 | | - | |
| 970 | + | |
948 | 971 | | |
949 | 972 | | |
950 | 973 | | |
| |||
989 | 1012 | | |
990 | 1013 | | |
991 | 1014 | | |
| 1015 | + | |
992 | 1016 | | |
993 | 1017 | | |
994 | 1018 | | |
| |||
3307 | 3331 | | |
3308 | 3332 | | |
3309 | 3333 | | |
| 3334 | + | |
| 3335 | + | |
| 3336 | + | |
| 3337 | + | |
| 3338 | + | |
| 3339 | + | |
| 3340 | + | |
| 3341 | + | |
| 3342 | + | |
| 3343 | + | |
| 3344 | + | |
| 3345 | + | |
| 3346 | + | |
| 3347 | + | |
| 3348 | + | |
| 3349 | + | |
| 3350 | + | |
| 3351 | + | |
| 3352 | + | |
| 3353 | + | |
| 3354 | + | |
| 3355 | + | |
| 3356 | + | |
| 3357 | + | |
| 3358 | + | |
| 3359 | + | |
| 3360 | + | |
| 3361 | + | |
| 3362 | + | |
| 3363 | + | |
| 3364 | + | |
| 3365 | + | |
| 3366 | + | |
| 3367 | + | |
| 3368 | + | |
| 3369 | + | |
| 3370 | + | |
| 3371 | + | |
| 3372 | + | |
| 3373 | + | |
| 3374 | + | |
| 3375 | + | |
| 3376 | + | |
| 3377 | + | |
| 3378 | + | |
| 3379 | + | |
| 3380 | + | |
| 3381 | + | |
| 3382 | + | |
| 3383 | + | |
| 3384 | + | |
| 3385 | + | |
| 3386 | + | |
| 3387 | + | |
| 3388 | + | |
| 3389 | + | |
| 3390 | + | |
| 3391 | + | |
| 3392 | + | |
| 3393 | + | |
| 3394 | + | |
| 3395 | + | |
| 3396 | + | |
| 3397 | + | |
| 3398 | + | |
| 3399 | + | |
3310 | 3400 | | |
3311 | 3401 | | |
3312 | 3402 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
448 | 448 | | |
449 | 449 | | |
450 | 450 | | |
451 | | - | |
| 451 | + | |
452 | 452 | | |
453 | 453 | | |
454 | 454 | | |
| |||
468 | 468 | | |
469 | 469 | | |
470 | 470 | | |
471 | | - | |
| 471 | + | |
472 | 472 | | |
473 | 473 | | |
474 | 474 | | |
| |||
0 commit comments