1use std::time::Duration;
26
27#[cfg(feature = "failpoints")]
28use fail::fail_point;
29
30#[derive(Debug, Clone)]
32pub struct ExecutionLimits {
33 pub max_commands: usize,
36
37 pub max_loop_iterations: usize,
40
41 pub max_total_loop_iterations: usize,
47
48 pub max_function_depth: usize,
51
52 pub timeout: Duration,
55
56 pub parser_timeout: Duration,
61
62 pub max_input_bytes: usize,
66
67 pub max_ast_depth: usize,
71
72 pub max_parser_operations: usize,
76}
77
78impl Default for ExecutionLimits {
79 fn default() -> Self {
80 Self {
81 max_commands: 10_000,
82 max_loop_iterations: 10_000,
83 max_total_loop_iterations: 1_000_000,
84 max_function_depth: 100,
85 timeout: Duration::from_secs(30),
86 parser_timeout: Duration::from_secs(5),
87 max_input_bytes: 10_000_000, max_ast_depth: 100,
89 max_parser_operations: 100_000,
90 }
91 }
92}
93
94impl ExecutionLimits {
95 pub fn new() -> Self {
97 Self::default()
98 }
99
100 pub fn max_commands(mut self, count: usize) -> Self {
102 self.max_commands = count;
103 self
104 }
105
106 pub fn max_loop_iterations(mut self, count: usize) -> Self {
108 self.max_loop_iterations = count;
109 self
110 }
111
112 pub fn max_total_loop_iterations(mut self, count: usize) -> Self {
115 self.max_total_loop_iterations = count;
116 self
117 }
118
119 pub fn max_function_depth(mut self, depth: usize) -> Self {
121 self.max_function_depth = depth;
122 self
123 }
124
125 pub fn timeout(mut self, timeout: Duration) -> Self {
127 self.timeout = timeout;
128 self
129 }
130
131 pub fn parser_timeout(mut self, timeout: Duration) -> Self {
133 self.parser_timeout = timeout;
134 self
135 }
136
137 pub fn max_input_bytes(mut self, bytes: usize) -> Self {
139 self.max_input_bytes = bytes;
140 self
141 }
142
143 pub fn max_ast_depth(mut self, depth: usize) -> Self {
145 self.max_ast_depth = depth;
146 self
147 }
148
149 pub fn max_parser_operations(mut self, ops: usize) -> Self {
151 self.max_parser_operations = ops;
152 self
153 }
154}
155
156#[derive(Debug, Clone, Default)]
158pub struct ExecutionCounters {
159 pub commands: usize,
161
162 pub function_depth: usize,
164
165 pub loop_iterations: usize,
167
168 pub total_loop_iterations: usize,
172}
173
174impl ExecutionCounters {
175 pub fn new() -> Self {
177 Self::default()
178 }
179
180 pub fn reset_for_execution(&mut self) {
184 self.commands = 0;
185 self.loop_iterations = 0;
186 self.total_loop_iterations = 0;
187 self.function_depth = 0;
190 }
191
192 pub fn tick_command(&mut self, limits: &ExecutionLimits) -> Result<(), LimitExceeded> {
194 #[cfg(feature = "failpoints")]
196 fail_point!("limits::tick_command", |action| {
197 match action.as_deref() {
198 Some("skip_increment") => {
199 return Ok(());
201 }
202 Some("force_overflow") => {
203 self.commands = usize::MAX;
205 return Err(LimitExceeded::MaxCommands(limits.max_commands));
206 }
207 Some("corrupt_high") => {
208 self.commands = limits.max_commands + 1;
210 }
211 _ => {}
212 }
213 Ok(())
214 });
215
216 self.commands += 1;
217 if self.commands > limits.max_commands {
218 return Err(LimitExceeded::MaxCommands(limits.max_commands));
219 }
220 Ok(())
221 }
222
223 pub fn tick_loop(&mut self, limits: &ExecutionLimits) -> Result<(), LimitExceeded> {
225 #[cfg(feature = "failpoints")]
227 fail_point!("limits::tick_loop", |action| {
228 match action.as_deref() {
229 Some("skip_check") => {
230 self.loop_iterations += 1;
232 return Ok(());
233 }
234 Some("reset_counter") => {
235 self.loop_iterations = 0;
237 return Ok(());
238 }
239 _ => {}
240 }
241 Ok(())
242 });
243
244 self.loop_iterations += 1;
245 self.total_loop_iterations += 1;
246 if self.loop_iterations > limits.max_loop_iterations {
247 return Err(LimitExceeded::MaxLoopIterations(limits.max_loop_iterations));
248 }
249 if self.total_loop_iterations > limits.max_total_loop_iterations {
251 return Err(LimitExceeded::MaxTotalLoopIterations(
252 limits.max_total_loop_iterations,
253 ));
254 }
255 Ok(())
256 }
257
258 pub fn reset_loop(&mut self) {
260 self.loop_iterations = 0;
261 }
262
263 pub fn push_function(&mut self, limits: &ExecutionLimits) -> Result<(), LimitExceeded> {
265 #[cfg(feature = "failpoints")]
267 fail_point!("limits::push_function", |action| {
268 match action.as_deref() {
269 Some("skip_check") => {
270 self.function_depth += 1;
272 return Ok(());
273 }
274 Some("corrupt_depth") => {
275 self.function_depth = 0;
277 return Ok(());
278 }
279 _ => {}
280 }
281 Ok(())
282 });
283
284 if self.function_depth >= limits.max_function_depth {
286 return Err(LimitExceeded::MaxFunctionDepth(limits.max_function_depth));
287 }
288 self.function_depth += 1;
289 Ok(())
290 }
291
292 pub fn pop_function(&mut self) {
294 if self.function_depth > 0 {
295 self.function_depth -= 1;
296 }
297 }
298}
299
300#[derive(Debug, Clone, thiserror::Error)]
302pub enum LimitExceeded {
303 #[error("maximum command count exceeded ({0})")]
304 MaxCommands(usize),
305
306 #[error("maximum loop iterations exceeded ({0})")]
307 MaxLoopIterations(usize),
308
309 #[error("maximum total loop iterations exceeded ({0})")]
310 MaxTotalLoopIterations(usize),
311
312 #[error("maximum function depth exceeded ({0})")]
313 MaxFunctionDepth(usize),
314
315 #[error("execution timeout ({0:?})")]
316 Timeout(Duration),
317
318 #[error("parser timeout ({0:?})")]
319 ParserTimeout(Duration),
320
321 #[error("input too large ({0} bytes, max {1} bytes)")]
322 InputTooLarge(usize, usize),
323
324 #[error("AST nesting too deep ({0} levels, max {1})")]
325 AstTooDeep(usize, usize),
326
327 #[error("parser fuel exhausted ({0} operations, max {1})")]
328 ParserExhausted(usize, usize),
329}
330
331#[cfg(test)]
332#[allow(clippy::unwrap_used)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn test_default_limits() {
338 let limits = ExecutionLimits::default();
339 assert_eq!(limits.max_commands, 10_000);
340 assert_eq!(limits.max_loop_iterations, 10_000);
341 assert_eq!(limits.max_total_loop_iterations, 1_000_000);
342 assert_eq!(limits.max_function_depth, 100);
343 assert_eq!(limits.timeout, Duration::from_secs(30));
344 assert_eq!(limits.parser_timeout, Duration::from_secs(5));
345 assert_eq!(limits.max_input_bytes, 10_000_000);
346 assert_eq!(limits.max_ast_depth, 100);
347 assert_eq!(limits.max_parser_operations, 100_000);
348 }
349
350 #[test]
351 fn test_builder_pattern() {
352 let limits = ExecutionLimits::new()
353 .max_commands(100)
354 .max_loop_iterations(50)
355 .max_function_depth(10)
356 .timeout(Duration::from_secs(5));
357
358 assert_eq!(limits.max_commands, 100);
359 assert_eq!(limits.max_loop_iterations, 50);
360 assert_eq!(limits.max_function_depth, 10);
361 assert_eq!(limits.timeout, Duration::from_secs(5));
362 }
363
364 #[test]
365 fn test_command_counter() {
366 let limits = ExecutionLimits::new().max_commands(5);
367 let mut counters = ExecutionCounters::new();
368
369 for _ in 0..5 {
370 assert!(counters.tick_command(&limits).is_ok());
371 }
372
373 assert!(matches!(
375 counters.tick_command(&limits),
376 Err(LimitExceeded::MaxCommands(5))
377 ));
378 }
379
380 #[test]
381 fn test_loop_counter() {
382 let limits = ExecutionLimits::new().max_loop_iterations(3);
383 let mut counters = ExecutionCounters::new();
384
385 for _ in 0..3 {
386 assert!(counters.tick_loop(&limits).is_ok());
387 }
388
389 assert!(matches!(
391 counters.tick_loop(&limits),
392 Err(LimitExceeded::MaxLoopIterations(3))
393 ));
394
395 counters.reset_loop();
397 assert!(counters.tick_loop(&limits).is_ok());
398 }
399
400 #[test]
401 fn test_total_loop_counter_accumulates() {
402 let limits = ExecutionLimits::new()
403 .max_loop_iterations(5)
404 .max_total_loop_iterations(8);
405 let mut counters = ExecutionCounters::new();
406
407 for _ in 0..5 {
409 assert!(counters.tick_loop(&limits).is_ok());
410 }
411 assert_eq!(counters.total_loop_iterations, 5);
412
413 counters.reset_loop();
415 assert_eq!(counters.loop_iterations, 0);
416 assert_eq!(counters.total_loop_iterations, 5);
418
419 assert!(counters.tick_loop(&limits).is_ok()); assert!(counters.tick_loop(&limits).is_ok()); assert!(counters.tick_loop(&limits).is_ok()); assert!(matches!(
426 counters.tick_loop(&limits),
427 Err(LimitExceeded::MaxTotalLoopIterations(8))
428 ));
429 }
430
431 #[test]
432 fn test_function_depth() {
433 let limits = ExecutionLimits::new().max_function_depth(2);
434 let mut counters = ExecutionCounters::new();
435
436 assert!(counters.push_function(&limits).is_ok());
437 assert!(counters.push_function(&limits).is_ok());
438
439 assert!(matches!(
441 counters.push_function(&limits),
442 Err(LimitExceeded::MaxFunctionDepth(2))
443 ));
444
445 counters.pop_function();
447 assert!(counters.push_function(&limits).is_ok());
448 }
449
450 #[test]
451 fn test_reset_for_execution() {
452 let limits = ExecutionLimits::new().max_commands(5);
453 let mut counters = ExecutionCounters::new();
454
455 for _ in 0..5 {
457 counters.tick_command(&limits).unwrap();
458 }
459 assert!(counters.tick_command(&limits).is_err());
460
461 counters.loop_iterations = 42;
463 counters.total_loop_iterations = 999;
464 counters.function_depth = 3;
465
466 counters.reset_for_execution();
468 assert_eq!(counters.commands, 0);
469 assert_eq!(counters.loop_iterations, 0);
470 assert_eq!(counters.total_loop_iterations, 0);
471 assert_eq!(counters.function_depth, 0);
472
473 assert!(counters.tick_command(&limits).is_ok());
475 }
476}