From 4e145b9d9efacd7c2effe7e1676756d1ed5e6059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danilo=20G=C3=B3mez?= Date: Thu, 9 Jul 2026 00:30:51 +0200 Subject: [PATCH] Bound nested SELECT parsing to MAX_PARSER_DEPTH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `enter_depth()` was only checked in `parse_expression_rec`, so deeply nested `FROM (SELECT …)` subqueries could still overflow the parser stack. Route subquery entry points through `parse_nested_select_statement`. Test uses `SELECT *` so the limit is exercised by subquery nesting, not by `parse_expression_rec` on a `SELECT 1` literal. --- src/parser/grammar/expressions.rs | 29 ++++++++++++++++++++++++++--- src/parser/grammar/select.rs | 15 +++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/parser/grammar/expressions.rs b/src/parser/grammar/expressions.rs index cba7c57..c9bba52 100644 --- a/src/parser/grammar/expressions.rs +++ b/src/parser/grammar/expressions.rs @@ -1,6 +1,6 @@ use crate::parser::syntax_kind::SyntaxKind; use crate::parser::grammar::common::parse_optional_settings_clause; -use crate::parser::grammar::select::{at_end_of_column_list, at_select_statement, parse_select_statement}; +use crate::parser::grammar::select::{at_end_of_column_list, at_select_statement, parse_nested_select_statement}; use crate::parser::grammar::types::parse_column_type; use crate::parser::interval_unit::IntervalUnit; use crate::parser::keyword::Keyword; @@ -428,7 +428,7 @@ fn parse_in_rhs(p: &mut Parser) { // Check if it's a subquery if at_select_statement(p) { let m = p.start(); - parse_select_statement(p); + parse_nested_select_statement(p); p.complete(m, SyntaxKind::SubqueryExpression); } else { parse_expression(p); @@ -528,7 +528,7 @@ fn expr_delimited(p: &mut Parser) -> Option { // Subquery starting with SELECT/FROM/WITH else if at_select_statement(p) { let m = p.start(); - parse_select_statement(p); + parse_nested_select_statement(p); p.complete(m, SyntaxKind::SubqueryExpression) } // Regular identifier / column reference @@ -925,6 +925,29 @@ mod tests { check_no_errors(&format!("SELECT {}1 + 2{}", "(".repeat(50), ")".repeat(50))); } + #[test] + fn bounds_deeply_nested_subqueries() { + // Use SELECT * so depth is counted by subquery nesting, not by + // parse_expression_rec on a literal in SELECT 1. + fn nested_subqueries(wrappers: usize) -> String { + let mut sql = "SELECT *".to_string(); + for _ in 0..wrappers { + sql = format!("SELECT * FROM ({sql})"); + } + sql + } + + let deep = nested_subqueries(1001); + let result = parse(&deep); + assert!( + result.errors.iter().any(|e| e.message.contains("nesting depth")), + "expected a nesting-depth error, got: {:?}", + result.errors, + ); + + check_no_errors(&nested_subqueries(999)); + } + #[test] fn binary_precedence() { check("SELECT 1 + 2 * 3", expect![[r#" diff --git a/src/parser/grammar/select.rs b/src/parser/grammar/select.rs index c8921f3..fac3522 100644 --- a/src/parser/grammar/select.rs +++ b/src/parser/grammar/select.rs @@ -177,6 +177,17 @@ pub fn parse_select_statement(p: &mut Parser) { } } +/// Parse a SELECT nested inside parentheses (FROM subquery, CTE, IN, scalar subquery). +pub fn parse_nested_select_statement(p: &mut Parser) { + if p.enter_depth() { + p.advance_with_error("Maximum expression nesting depth exceeded"); + p.leave_depth(); + return; + } + parse_select_statement(p); + p.leave_depth(); +} + /// True if the current position marks the end of a column list /// (i.e. we've hit a clause keyword or statement boundary). pub fn at_end_of_column_list(p: &mut Parser) -> bool { @@ -297,7 +308,7 @@ fn parse_with_items(p: &mut Parser) { p.expect(SyntaxKind::OpeningRoundBracket); if at_select_statement(p) { let subq = p.start(); - parse_select_statement(p); + parse_nested_select_statement(p); p.complete(subq, SyntaxKind::SubqueryExpression); } else { parse_expression(p); @@ -513,7 +524,7 @@ fn parse_subquery_table_ref(p: &mut Parser) { let m = p.start(); p.expect(SyntaxKind::OpeningRoundBracket); if at_select_statement(p) { - parse_select_statement(p); + parse_nested_select_statement(p); } else if at_explain_statement(p) { parse_explain_statement(p); } else {