Stack overflow when dropping a flat operator chain AST (parse succeeds)

オープン
#2,909 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
58/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
rust

調査の方向性

Start with src/ast/mod.rs and the parse_subexpr precedence loop mentioned in the issue; trace how operator chains are folded and how recursion limits are counted. Reproduce with the supplied Rust example, then check the existing recursion-limit tests. Done means dropping a long flat operator chain no longer overflows and the behavior respects the recursion limit (or safely drops the AST).

索引モデルが issue の本文から書いたものです。

説明

Summary

Parser::parse_sql accepts a flat left-associative operator chain and returns successfully, but dropping the returned Vec<Statement> then overflows the stack: the derived drop glue for the left-deep Box<Expr> tree recurses once per level with no guard. The process aborts (rc=134), and abort cannot be caught by the caller.

On a 2 MB stack (the default for spawned threads, including tokio workers) roughly 35,000 + operators — about 70 KB of input — is enough:

stack operators input result
8 MB (main) 130,625 261,258 B parses and drops, rc=0
8 MB (main) 132,000 264,008 B prints parsed 1 statement(s) then aborts
2 MB (thread) 30,000 60,008 B parses and drops, rc=0
2 MB (thread) 35,000 70,008 B prints parsed 1 statement(s) then aborts

The parsed ... line is printed before the abort, so the overflow is in drop, not in parsing.

thread 'main' has overflowed its stack
fatal runtime error: stack overflow, aborting

Root cause

Expr and the rest of the AST derive Drop (src/ast/mod.rs). The only recursion guard on these types is

#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
impl Display for Expr

at src/ast/mod.rs:1795; there is no impl Drop and no ManuallyDrop.

A flat chain such as SELECT 1+1+1... is built by the iterative precedence-climbing loop in parse_subexpr, so the recursion counter never fires, and the result is a left-deep BinaryOp { left: Box<Expr>, .. } spine. Dropping it walks that spine recursively, one stack frame per operator. Parser::with_recursion_limit does not help because parsing never recursed.

Reproduction

[dependencies]
sqlparser = "=0.63.0"
fn main() {
    let n: usize = std::env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(132_000);
    let mut sql = String::from("SELECT 1");
    for _ in 0..n { sql.push_str("+1"); }
    let dialect = sqlparser::dialect::GenericDialect {};
    let stmts = sqlparser::parser::Parser::parse_sql(&dialect, &sql).expect("parse");
    println!("parsed {} statement(s), {} bytes", stmts.len(), sql.len());
    drop(stmts);
    println!("dropped ok");
}
$ cargo run --release -- 130625
parsed 1 statement(s), 261258 bytes
dropped ok
$ cargo run --release -- 132000
parsed 1 statement(s), 264008 bytes
thread 'main' has overflowed its stack
fatal runtime error: stack overflow, aborting   # rc=134

SELECT 1=1 AND 1=1 ... shows the same behaviour.

Suggested fix

Count the chain length against the existing recursion accounting: when the precedence loop folds another operator onto the same chain, add it to RecursionCounter and return RecursionLimitExceeded past the limit, matching what deeply nested parentheses already do. Alternatively, implement an iterative Drop for Expr with an explicit work stack, which also covers trees built programmatically.

主要言語
Rust
スター
3.5k
フォーク
805
平均マージ
2日 3時間
マージ済み PR(30日)
116

環境構築

  • Dockerfile・Docker Compose ファイルなし
  • プルリクエストのテンプレートあり
  • コントリビューションガイドなし

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

apache/datafusion-sqlparser-rs のほかの issue

apache/datafusion-sqlparser-rs の issue をすべて見る

似ている issue

Rust の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。