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}