1use crate::error::{Error, Result};
4use crate::expressions::Expression;
5use crate::tokens::{ParserToken, Span, Token, TokenType};
6use serde::{Deserialize, Serialize};
7
8const DEFAULT_MAX_INPUT_BYTES: usize = 16 * 1024 * 1024;
9const DEFAULT_MAX_TOKENS: usize = 1_000_000;
10const DEFAULT_MAX_AST_NODES: usize = 1_000_000;
11const DEFAULT_MAX_AST_DEPTH: usize = 512;
12const DEFAULT_MAX_PARENTHESES_DEPTH: usize = 512;
13const DEFAULT_MAX_FUNCTION_CALL_DEPTH: usize = 64;
14#[cfg(target_arch = "wasm32")]
17const DEFAULT_MAX_PARSER_DEPTH: usize = 32;
18#[cfg(not(target_arch = "wasm32"))]
19const DEFAULT_MAX_PARSER_DEPTH: usize = 1_024;
20
21fn default_max_parser_depth() -> Option<usize> {
22 Some(DEFAULT_MAX_PARSER_DEPTH)
23}
24
25pub(crate) fn is_guard_error(error: &Error) -> bool {
27 matches!(error, Error::Parse { message, .. } if message.starts_with("E_GUARD_"))
28}
29
30fn default_max_input_bytes() -> Option<usize> {
31 Some(DEFAULT_MAX_INPUT_BYTES)
32}
33
34fn default_max_tokens() -> Option<usize> {
35 Some(DEFAULT_MAX_TOKENS)
36}
37
38fn default_max_ast_nodes() -> Option<usize> {
39 Some(DEFAULT_MAX_AST_NODES)
40}
41
42fn default_max_ast_depth() -> Option<usize> {
43 Some(DEFAULT_MAX_AST_DEPTH)
44}
45
46fn default_max_parenthesis_depth() -> Option<usize> {
47 Some(DEFAULT_MAX_PARENTHESES_DEPTH)
48}
49
50fn default_max_function_call_depth() -> Option<usize> {
51 Some(DEFAULT_MAX_FUNCTION_CALL_DEPTH)
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct ComplexityGuardOptions {
61 #[serde(default = "default_max_parser_depth")]
65 pub max_parser_depth: Option<usize>,
66 #[serde(default = "default_max_input_bytes")]
69 pub max_input_bytes: Option<usize>,
70 #[serde(default = "default_max_tokens")]
73 pub max_tokens: Option<usize>,
74 #[serde(default = "default_max_ast_nodes")]
77 pub max_ast_nodes: Option<usize>,
78 #[serde(default = "default_max_ast_depth")]
81 pub max_ast_depth: Option<usize>,
82 #[serde(default = "default_max_parenthesis_depth")]
85 pub max_parenthesis_depth: Option<usize>,
86 #[serde(default = "default_max_function_call_depth")]
89 pub max_function_call_depth: Option<usize>,
90}
91
92#[derive(Debug, Default)]
93pub(crate) struct TokenGuardStats {
94 pub token_count: usize,
95 parenthesis_depth_spans: Vec<Span>,
96 function_depth_spans: Vec<Span>,
97 parenthesis_stack: Vec<bool>,
98 parenthesis_depth: usize,
99 function_depth: usize,
100 previous_significant: Option<TokenType>,
101}
102
103impl TokenGuardStats {
104 pub(crate) fn observe(&mut self, token_type: TokenType, span: Span) {
105 self.token_count += 1;
106 match token_type {
107 TokenType::LParen => {
108 self.parenthesis_depth += 1;
109 if self.parenthesis_depth_spans.len() < self.parenthesis_depth {
110 self.parenthesis_depth_spans.push(span);
111 }
112
113 let is_function_call = self
114 .previous_significant
115 .map(is_function_call_name_token)
116 .unwrap_or(false);
117 self.parenthesis_stack.push(is_function_call);
118 if is_function_call {
119 self.function_depth += 1;
120 if self.function_depth_spans.len() < self.function_depth {
121 self.function_depth_spans.push(span);
122 }
123 }
124 }
125 TokenType::RParen => {
126 self.parenthesis_depth = self.parenthesis_depth.saturating_sub(1);
127 if self.parenthesis_stack.pop().unwrap_or(false) {
128 self.function_depth = self.function_depth.saturating_sub(1);
129 }
130 }
131 _ => {}
132 }
133
134 if !is_trivia_token(token_type) {
135 self.previous_significant = Some(token_type);
136 }
137 }
138}
139
140impl Default for ComplexityGuardOptions {
141 fn default() -> Self {
142 Self {
143 max_parser_depth: default_max_parser_depth(),
144 max_input_bytes: default_max_input_bytes(),
145 max_tokens: default_max_tokens(),
146 max_ast_nodes: default_max_ast_nodes(),
147 max_ast_depth: default_max_ast_depth(),
148 max_parenthesis_depth: default_max_parenthesis_depth(),
149 max_function_call_depth: default_max_function_call_depth(),
150 }
151 }
152}
153
154fn parse_guard_error(code: &str, actual: usize, limit: usize, span: Option<Span>) -> Error {
155 let message = format!("{code}: value {actual} exceeds configured limit {limit}");
156 if let Some(span) = span {
157 Error::parse(message, span.line, span.column, span.start, span.end)
158 } else {
159 Error::parse(message, 0, 0, 0, 0)
160 }
161}
162
163fn generate_guard_error(code: &str, actual: usize, limit: usize) -> Error {
164 Error::generate(format!(
165 "{code}: value {actual} exceeds configured limit {limit}"
166 ))
167}
168
169pub fn enforce_input(sql: &str, options: &ComplexityGuardOptions) -> Result<()> {
171 if let Some(max) = options.max_input_bytes {
172 let input_bytes = sql.len();
173 if input_bytes > max {
174 return Err(parse_guard_error(
175 "E_GUARD_INPUT_TOO_LARGE",
176 input_bytes,
177 max,
178 None,
179 ));
180 }
181 }
182
183 Ok(())
184}
185
186trait GuardToken {
188 fn token_type(&self) -> TokenType;
189 fn span(&self) -> Span;
190}
191
192impl GuardToken for Token {
193 fn token_type(&self) -> TokenType {
194 self.token_type
195 }
196
197 fn span(&self) -> Span {
198 self.span
199 }
200}
201
202impl GuardToken for ParserToken {
203 fn token_type(&self) -> TokenType {
204 self.token_type
205 }
206
207 fn span(&self) -> Span {
208 self.span
209 }
210}
211
212fn enforce_token_slice<T: GuardToken>(
213 tokens: &[T],
214 options: &ComplexityGuardOptions,
215) -> Result<()> {
216 if let Some(max) = options.max_tokens {
217 let token_count = tokens.len();
218 if token_count > max {
219 let span = tokens
220 .get(max)
221 .or_else(|| tokens.last())
222 .map(GuardToken::span);
223 return Err(parse_guard_error(
224 "E_GUARD_TOKEN_BUDGET_EXCEEDED",
225 token_count,
226 max,
227 span,
228 ));
229 }
230 }
231
232 if options.max_parenthesis_depth.is_some() || options.max_function_call_depth.is_some() {
233 let mut paren_depth = 0usize;
234 let mut function_depth = 0usize;
235 let mut paren_stack = Vec::new();
236 let mut previous_significant: Option<TokenType> = None;
237
238 for token in tokens {
239 match token.token_type() {
240 TokenType::LParen => {
241 paren_depth += 1;
242 if let Some(max) = options.max_parenthesis_depth {
243 if paren_depth > max {
244 return Err(parse_guard_error(
245 "E_GUARD_NESTING_DEPTH_EXCEEDED",
246 paren_depth,
247 max,
248 Some(token.span()),
249 ));
250 }
251 }
252
253 let is_function_call = previous_significant
254 .map(is_function_call_name_token)
255 .unwrap_or(false);
256 paren_stack.push(is_function_call);
257 if is_function_call {
258 function_depth += 1;
259 if let Some(max) = options.max_function_call_depth {
260 if function_depth > max {
261 return Err(parse_guard_error(
262 "E_GUARD_FUNCTION_NESTING_DEPTH_EXCEEDED",
263 function_depth,
264 max,
265 Some(token.span()),
266 ));
267 }
268 }
269 }
270 }
271 TokenType::RParen => {
272 paren_depth = paren_depth.saturating_sub(1);
273 if paren_stack.pop().unwrap_or(false) {
274 function_depth = function_depth.saturating_sub(1);
275 }
276 }
277 _ => {}
278 }
279
280 if !is_trivia_token(token.token_type()) {
281 previous_significant = Some(token.token_type());
282 }
283 }
284 }
285
286 Ok(())
287}
288
289pub fn enforce_tokens(tokens: &[Token], options: &ComplexityGuardOptions) -> Result<()> {
290 enforce_token_slice(tokens, options)
291}
292
293pub(crate) fn enforce_parser_tokens(
294 tokens: &[ParserToken],
295 options: &ComplexityGuardOptions,
296) -> Result<()> {
297 enforce_token_slice(tokens, options)
298}
299
300pub(crate) fn enforce_parser_token_stats(
301 tokens: &[ParserToken],
302 stats: &TokenGuardStats,
303 options: &ComplexityGuardOptions,
304) -> Result<()> {
305 if let Some(max) = options.max_tokens {
306 if stats.token_count > max {
307 let span = tokens
308 .get(max)
309 .or_else(|| tokens.last())
310 .map(|token| token.span);
311 return Err(parse_guard_error(
312 "E_GUARD_TOKEN_BUDGET_EXCEEDED",
313 stats.token_count,
314 max,
315 span,
316 ));
317 }
318 }
319
320 let parenthesis_error = options.max_parenthesis_depth.and_then(|max| {
321 stats
322 .parenthesis_depth_spans
323 .get(max)
324 .copied()
325 .map(|span| (span, max))
326 });
327 let function_error = options.max_function_call_depth.and_then(|max| {
328 stats
329 .function_depth_spans
330 .get(max)
331 .copied()
332 .map(|span| (span, max))
333 });
334
335 match (parenthesis_error, function_error) {
336 (Some((paren_span, _paren_max)), Some((function_span, function_max)))
337 if function_span.start < paren_span.start =>
338 {
339 Err(parse_guard_error(
340 "E_GUARD_FUNCTION_NESTING_DEPTH_EXCEEDED",
341 function_max + 1,
342 function_max,
343 Some(function_span),
344 ))
345 }
346 (Some((span, max)), _) => Err(parse_guard_error(
347 "E_GUARD_NESTING_DEPTH_EXCEEDED",
348 max + 1,
349 max,
350 Some(span),
351 )),
352 (None, Some((span, max))) => Err(parse_guard_error(
353 "E_GUARD_FUNCTION_NESTING_DEPTH_EXCEEDED",
354 max + 1,
355 max,
356 Some(span),
357 )),
358 (None, None) => Ok(()),
359 }
360}
361
362fn is_trivia_token(token_type: TokenType) -> bool {
363 matches!(
364 token_type,
365 TokenType::Space | TokenType::Break | TokenType::LineComment | TokenType::BlockComment
366 )
367}
368
369fn is_function_call_name_token(token_type: TokenType) -> bool {
370 matches!(
371 token_type,
372 TokenType::Identifier
373 | TokenType::Var
374 | TokenType::QuotedIdentifier
375 | TokenType::CurrentDate
376 | TokenType::CurrentDateTime
377 | TokenType::CurrentTime
378 | TokenType::CurrentTimestamp
379 | TokenType::CurrentUser
380 | TokenType::If
381 | TokenType::Index
382 | TokenType::Insert
383 | TokenType::Left
384 | TokenType::Replace
385 | TokenType::Right
386 | TokenType::Row
387 )
388}
389
390#[cfg(test)]
391mod token_guard_tests {
392 use super::*;
393 use crate::tokens::Tokenizer;
394 use std::sync::Arc;
395
396 #[test]
397 fn parser_depth_json_contract() {
398 let omitted: ComplexityGuardOptions = serde_json::from_str("{}").unwrap();
399 assert_eq!(omitted.max_parser_depth, Some(DEFAULT_MAX_PARSER_DEPTH));
400 assert_eq!(
401 omitted.max_parser_depth,
402 ComplexityGuardOptions::default().max_parser_depth
403 );
404 for limit in [Some(0), Some(1), Some(2048), None] {
405 let guard = ComplexityGuardOptions {
406 max_parser_depth: limit,
407 ..Default::default()
408 };
409 let json = serde_json::to_string(&guard).unwrap();
410 assert_eq!(
411 serde_json::from_str::<ComplexityGuardOptions>(&json).unwrap(),
412 guard
413 );
414 }
415 for value in ["-1", "1.5", "true", "\"10\""] {
416 assert!(serde_json::from_str::<ComplexityGuardOptions>(&format!(
417 "{{\"maxParserDepth\":{value}}}"
418 ))
419 .is_err());
420 }
421 }
422
423 #[test]
424 fn collected_parser_stats_match_full_token_guard_pass() {
425 let sql: Arc<str> = Arc::from("SELECT outer(inner((value))), other(1, 2) FROM t");
426 let tokenizer = Tokenizer::default();
427 let public_tokens = tokenizer.tokenize(&sql).unwrap();
428 let (parser_tokens, stats) = tokenizer.tokenize_for_parser(&sql).unwrap();
429 let options = [
430 ComplexityGuardOptions::default(),
431 ComplexityGuardOptions {
432 max_tokens: Some(5),
433 ..Default::default()
434 },
435 ComplexityGuardOptions {
436 max_parenthesis_depth: Some(1),
437 ..Default::default()
438 },
439 ComplexityGuardOptions {
440 max_function_call_depth: Some(1),
441 ..Default::default()
442 },
443 ComplexityGuardOptions {
444 max_parenthesis_depth: Some(2),
445 max_function_call_depth: Some(1),
446 ..Default::default()
447 },
448 ];
449
450 for option in options {
451 let full_pass =
452 enforce_tokens(&public_tokens, &option).map_err(|error| error.to_string());
453 let collected = enforce_parser_token_stats(&parser_tokens, &stats, &option)
454 .map_err(|error| error.to_string());
455 assert_eq!(collected, full_pass);
456 }
457 }
458}
459
460pub fn enforce_ast(expressions: &[Expression], options: &ComplexityGuardOptions) -> Result<()> {
462 let Some(max_nodes) = options.max_ast_nodes else {
463 return enforce_ast_depth_only(expressions, options);
464 };
465
466 let stats = ast_stats(expressions, Some(max_nodes), options.max_ast_depth)?;
467 if stats.node_count > max_nodes {
468 return Err(parse_guard_error(
469 "E_GUARD_AST_BUDGET_EXCEEDED",
470 stats.node_count,
471 max_nodes,
472 None,
473 ));
474 }
475
476 if let Some(max_depth) = options.max_ast_depth {
477 if stats.max_depth > max_depth {
478 return Err(parse_guard_error(
479 "E_GUARD_AST_DEPTH_EXCEEDED",
480 stats.max_depth,
481 max_depth,
482 None,
483 ));
484 }
485 }
486
487 Ok(())
488}
489
490pub fn enforce_generate_ast(
492 expression: &Expression,
493 options: &ComplexityGuardOptions,
494) -> Result<()> {
495 let stats = ast_stats(
496 std::slice::from_ref(expression),
497 options.max_ast_nodes,
498 options.max_ast_depth,
499 )?;
500
501 if let Some(max_nodes) = options.max_ast_nodes {
502 if stats.node_count > max_nodes {
503 return Err(generate_guard_error(
504 "E_GUARD_AST_BUDGET_EXCEEDED",
505 stats.node_count,
506 max_nodes,
507 ));
508 }
509 }
510
511 if let Some(max_depth) = options.max_ast_depth {
512 if stats.max_depth > max_depth {
513 return Err(generate_guard_error(
514 "E_GUARD_AST_DEPTH_EXCEEDED",
515 stats.max_depth,
516 max_depth,
517 ));
518 }
519 }
520
521 Ok(())
522}
523
524fn enforce_ast_depth_only(
525 expressions: &[Expression],
526 options: &ComplexityGuardOptions,
527) -> Result<()> {
528 let Some(max_depth) = options.max_ast_depth else {
529 return Ok(());
530 };
531
532 let stats = ast_stats(expressions, None, Some(max_depth))?;
533 if stats.max_depth > max_depth {
534 return Err(parse_guard_error(
535 "E_GUARD_AST_DEPTH_EXCEEDED",
536 stats.max_depth,
537 max_depth,
538 None,
539 ));
540 }
541
542 Ok(())
543}
544
545#[derive(Debug, Clone, Copy, Default)]
546struct AstStats {
547 node_count: usize,
548 max_depth: usize,
549}
550
551fn ast_stats(
552 expressions: &[Expression],
553 max_nodes: Option<usize>,
554 max_depth: Option<usize>,
555) -> Result<AstStats> {
556 let mut stats = AstStats::default();
557 let mut stack: Vec<(&Expression, usize)> = expressions.iter().rev().map(|e| (e, 0)).collect();
558
559 while let Some((expr, depth)) = stack.pop() {
560 stats.node_count += 1;
561 stats.max_depth = stats.max_depth.max(depth);
562
563 if let Some(max) = max_nodes {
564 if stats.node_count > max {
565 return Ok(stats);
566 }
567 }
568
569 if let Some(max) = max_depth {
570 if stats.max_depth > max {
571 return Ok(stats);
572 }
573 }
574
575 push_ast_children(&mut stack, expr, depth);
576 }
577
578 Ok(stats)
579}
580
581fn push_ast_children<'a>(
582 stack: &mut Vec<(&'a Expression, usize)>,
583 expr: &'a Expression,
584 depth: usize,
585) {
586 match expr {
587 Expression::And(op) if is_commentless_binary_op(op) => {
588 push_connector_child(stack, &op.right, depth, ConnectorKind::And);
589 push_connector_child(stack, &op.left, depth, ConnectorKind::And);
590 }
591 Expression::Or(op) if is_commentless_binary_op(op) => {
592 push_connector_child(stack, &op.right, depth, ConnectorKind::Or);
593 push_connector_child(stack, &op.left, depth, ConnectorKind::Or);
594 }
595 _ => {
596 let child_start = stack.len();
597 crate::ast_children::for_each_child(expr, |_, child| {
598 stack.push((child, depth + 1));
599 });
600 stack[child_start..].reverse();
601 }
602 }
603}
604
605fn push_connector_child<'a>(
606 stack: &mut Vec<(&'a Expression, usize)>,
607 child: &'a Expression,
608 depth: usize,
609 kind: ConnectorKind,
610) {
611 let child_depth = if is_commentless_connector(child, kind) {
612 depth
613 } else {
614 depth + 1
615 };
616 stack.push((child, child_depth));
617}
618
619fn is_commentless_connector(expr: &Expression, kind: ConnectorKind) -> bool {
620 match (kind, expr) {
621 (ConnectorKind::And, Expression::And(op)) | (ConnectorKind::Or, Expression::Or(op)) => {
622 is_commentless_binary_op(op)
623 }
624 _ => false,
625 }
626}
627
628fn is_commentless_binary_op(op: &crate::expressions::BinaryOp) -> bool {
629 op.left_comments.is_empty()
630 && op.operator_comments.is_empty()
631 && op.trailing_comments.is_empty()
632}
633
634#[derive(Debug, Clone, Copy)]
635enum ConnectorKind {
636 And,
637 Or,
638}