Skip to content
Open
Show file tree
Hide file tree
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
4 changes: 2 additions & 2 deletions examples/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ $ cargo run --example cli - [--dialectname]
.expect("failed to read from stdin");
String::from_utf8(buf).expect("stdin content wasn't valid utf8")
} else {
println!("Parsing from file '{}' using {:?}", &filename, dialect);
println!("Parsing from file '{}' using {:?}", filename, dialect);
fs::read_to_string(&filename)
.unwrap_or_else(|_| panic!("Unable to read the file {}", &filename))
.unwrap_or_else(|_| panic!("Unable to read the file {}", filename))
};
let without_bom = if contents.chars().next().unwrap() as u64 != 0xfeff {
contents.as_str()
Expand Down
2 changes: 1 addition & 1 deletion src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4780,7 +4780,7 @@ impl fmt::Display for AlterTable {
if self.only {
write!(f, "ONLY ")?;
}
write!(f, "{} ", &self.name)?;
write!(f, "{} ", self.name)?;
if let Some(cluster) = &self.on_cluster {
write!(f, "ON CLUSTER {cluster} ")?;
}
Expand Down
2 changes: 1 addition & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11842,7 +11842,7 @@ impl fmt::Display for AlterUser {
let has_props = !self.set_props.options.is_empty();
if has_props {
write!(f, " SET")?;
write!(f, " {}", &self.set_props)?;
write!(f, " {}", self.set_props)?;
}
if !self.unset_props.is_empty() {
write!(f, " UNSET {}", display_comma_separated(&self.unset_props))?;
Expand Down
2 changes: 1 addition & 1 deletion src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3545,7 +3545,7 @@ pub struct LockClause {

impl fmt::Display for LockClause {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "FOR {}", &self.lock_type)?;
write!(f, "FOR {}", self.lock_type)?;
if let Some(ref of) = self.of {
write!(f, " OF {of}")?;
}
Expand Down
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");
}
}
8 changes: 4 additions & 4 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4845,7 +4845,7 @@ impl<'a> Parser<'a> {
if self.parse_keyword(expected) {
Ok(self.get_current_token().clone())
} else {
self.expected_ref(format!("{:?}", &expected).as_str(), self.peek_token_ref())
self.expected_ref(format!("{:?}", expected).as_str(), self.peek_token_ref())
}
}

Expand All @@ -4858,7 +4858,7 @@ impl<'a> Parser<'a> {
if self.parse_keyword(expected) {
Ok(())
} else {
self.expected_ref(format!("{:?}", &expected).as_str(), self.peek_token_ref())
self.expected_ref(format!("{:?}", expected).as_str(), self.peek_token_ref())
}
}

Expand Down Expand Up @@ -13726,7 +13726,7 @@ impl<'a> Parser<'a> {
token => {
return Err(ParserError::ParserError(format!(
"Unexpected token in identifier: {token}"
)))?;
)));
}
}
}
Expand Down Expand Up @@ -17018,7 +17018,7 @@ impl<'a> Parser<'a> {
tok.token
),
tok.span.start
)?;
);
}
}

Expand Down
6 changes: 3 additions & 3 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1731,7 +1731,7 @@ fn parse_json_ops_without_colon() {
];

for (str_op, op, dialects) in binary_ops {
let select = dialects.verified_only_select(&format!("SELECT a {} b", &str_op));
let select = dialects.verified_only_select(&format!("SELECT a {} b", str_op));
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("a"))),
Expand Down Expand Up @@ -2441,7 +2441,7 @@ fn parse_bitwise_ops() {
];

for (str_op, op, dialects) in bitwise_ops {
let select = dialects.verified_only_select(&format!("SELECT a {} b", &str_op));
let select = dialects.verified_only_select(&format!("SELECT a {} b", str_op));
assert_eq!(
SelectItem::UnnamedExpr(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("a"))),
Expand Down Expand Up @@ -19100,7 +19100,7 @@ fn parse_generic_unary_ops() {
("+", UnaryOperator::Plus),
];
for (str_op, op) in unary_ops {
let select = verified_only_select(&format!("SELECT {}expr", &str_op));
let select = verified_only_select(&format!("SELECT {}expr", str_op));
assert_eq!(
UnnamedExpr(UnaryOp {
op: *op,
Expand Down
4 changes: 2 additions & 2 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2574,7 +2574,7 @@ fn parse_pg_unary_ops() {
("@", UnaryOperator::PGAbs),
];
for (str_op, op) in pg_unary_ops {
let select = pg().verified_only_select(&format!("SELECT {}a", &str_op));
let select = pg().verified_only_select(&format!("SELECT {}a", str_op));
assert_eq!(
SelectItem::UnnamedExpr(Expr::UnaryOp {
op: *op,
Expand All @@ -2590,7 +2590,7 @@ fn parse_pg_postfix_factorial() {
let postfix_factorial = &[("!", UnaryOperator::PGPostfixFactorial)];

for (str_op, op) in postfix_factorial {
let select = pg().verified_only_select(&format!("SELECT a{}", &str_op));
let select = pg().verified_only_select(&format!("SELECT a{}", str_op));
assert_eq!(
SelectItem::UnnamedExpr(Expr::UnaryOp {
op: *op,
Expand Down
Loading