Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ impl<T> Spanned for Parens<T> {
}

impl Spanned for Query {
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
fn span(&self) -> Span {
let Query {
with,
Expand Down Expand Up @@ -217,6 +218,7 @@ impl Spanned for Cte {
///
/// [SetExpr::Table] is not implemented.
impl Spanned for SetExpr {
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
fn span(&self) -> Span {
match self {
SetExpr::Select(select) => select.span(),
Expand Down Expand Up @@ -320,6 +322,7 @@ impl Spanned for Values {
/// - [Statement::Unload]
/// - [Statement::OptimizeTable]
impl Spanned for Statement {
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
fn span(&self) -> Span {
match self {
Statement::Analyze(analyze) => analyze.span(),
Expand Down Expand Up @@ -1480,6 +1483,7 @@ impl Spanned for AssignmentTarget {
/// - [Expr::Map] # DuckDB specific
/// - [Expr::Lambda]
impl Spanned for Expr {
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
fn span(&self) -> Span {
match self {
Expr::Identifier(ident) => ident.span,
Expand Down Expand Up @@ -1852,6 +1856,7 @@ impl Spanned for SelectItemQualifiedWildcardKind {
}

impl Spanned for SelectItem {
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
fn span(&self) -> Span {
match self {
SelectItem::UnnamedExpr(expr) => expr.span(),
Expand Down Expand Up @@ -1958,6 +1963,7 @@ impl Spanned for ReplaceSelectElement {
/// Missing spans:
/// - [TableFactor::JsonTable]
impl Spanned for TableFactor {
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
fn span(&self) -> Span {
match self {
TableFactor::Table {
Expand Down Expand Up @@ -2303,6 +2309,7 @@ impl Spanned for TableWithJoins {
}

impl Spanned for Select {
#[cfg_attr(feature = "recursive-protection", recursive::recursive)]
fn span(&self) -> Span {
let Select {
select_token,
Expand Down Expand Up @@ -3116,4 +3123,34 @@ WHERE id = 1
Span::new(Location::new(2, 8), Location::new(4, 52))
);
}
// Regression: the `Spanned::span` walk is otherwise unbounded recursion, so
// a deeply nested expression (the shape a machine-generated query or a broad
// access-control predicate can produce) overflows the stack and aborts the
// process. With the `recursive-protection` feature the walk grows the stack
// on demand and completes.
//
// The AST is built directly (the parser is not the subject here) and walked
// on a deliberately small (512 KiB) thread stack, so the chosen depth far
// exceeds what the stack holds unprotected — the walk overflows without the
// fix and completes with it.
#[test]
fn test_deeply_nested_expr_span_does_not_overflow() {
std::thread::Builder::new()
.stack_size(512 * 1024)
.spawn(|| {
let depth = 200_000;
let mut expr = Expr::Identifier(crate::ast::Ident::new("x"));
for _ in 0..depth {
expr = Expr::Nested(Box::new(expr));
}
// Must not overflow the stack while walking the deep AST.
let _ = expr.span();
// The nested `Box<Expr>` chain has a recursive `Drop`; forget it
// to keep the test fast and isolate the span walk under test.
core::mem::forget(expr);
})
.unwrap()
.join()
.expect("span walk overflowed the small-stack thread");
}
}