exp_rs/eval/recursion.rs
1extern crate alloc;
2use crate::error::ExprError;
3#[cfg(not(test))]
4use alloc::format;
5
6// Add recursion depth tracking for evaluating expression functions
7#[cfg(not(test))]
8use core::sync::atomic::{AtomicUsize, Ordering};
9#[cfg(test)]
10use std::sync::atomic::{AtomicUsize, Ordering};
11
12// Define a static atomic counter for recursion depth
13// Using atomics avoids need for RefCell and thread_local
14pub static RECURSION_DEPTH: AtomicUsize = AtomicUsize::new(0);
15
16// Add a constant for maximum recursion depth
17// We're tracking function calls, so we can use a reasonable limit for call depth
18// This should allow for legitimate recursive functions but catch infinite recursion
19// Note: On embedded systems or with limited stack space, this may need to be lower
20#[cfg(test)]
21const MAX_RECURSION_DEPTH: usize = 10; // Very low limit for tests to avoid stack overflow
22#[cfg(not(test))]
23const MAX_RECURSION_DEPTH: usize = 100; // Conservative limit for production
24
25// Add a helper function to check and increment recursion depth
26pub fn check_and_increment_recursion_depth() -> Result<(), ExprError> {
27 let current = RECURSION_DEPTH.load(Ordering::Relaxed);
28
29 // Safety check: If the counter is abnormally high but not at the limit,
30 // it might indicate a leak from a previous test or evaluation
31 if current > MAX_RECURSION_DEPTH - 10 && current < MAX_RECURSION_DEPTH {
32 // Log a warning in debug builds
33 #[cfg(test)]
34 eprintln!(
35 "WARNING: Unusually high recursion depth detected: {}",
36 current
37 );
38 }
39
40 if current >= MAX_RECURSION_DEPTH {
41 // Immediately reset the counter to prevent potential state inconsistency
42 // This ensures future evaluations don't start with a maxed-out counter
43 RECURSION_DEPTH.store(0, Ordering::Relaxed);
44
45 Err(ExprError::RecursionLimit(format!(
46 "Maximum recursion depth of {} exceeded during expression evaluation",
47 MAX_RECURSION_DEPTH
48 )))
49 } else {
50 RECURSION_DEPTH.store(current + 1, Ordering::Relaxed);
51 Ok(())
52 }
53}
54
55// Add a helper function to decrement recursion depth
56pub fn decrement_recursion_depth() {
57 let current = RECURSION_DEPTH.load(Ordering::Relaxed);
58 if current > 0 {
59 RECURSION_DEPTH.store(current - 1, Ordering::Relaxed);
60 }
61}
62
63// Get the current recursion depth - exposed for testing
64pub fn get_recursion_depth() -> usize {
65 RECURSION_DEPTH.load(Ordering::Relaxed)
66}
67
68// Reset the recursion depth counter to zero - exposed for testing
69pub fn reset_recursion_depth() {
70 RECURSION_DEPTH.store(0, Ordering::Relaxed)
71}
72
73// Set the recursion depth to a specific value - exposed for testing
74pub fn set_max_recursion_depth(depth: usize) -> usize {
75 // We can't actually modify the const, but we can expose this for documentation
76 // of test expectations
77 depth
78}