@@ -31,11 +31,22 @@ use std::iter::FromIterator;
3131use std:: ops:: ControlFlow ;
3232use std:: sync:: Arc ;
3333use std:: thread;
34+ use std:: time:: Instant ;
3435use vir_macros:: ToDebugSNode ;
3536
3637// An approximation of how many interpreter invocations we can do in 1 second (in release mode)
3738const RLIMIT_MULTIPLIER : u64 = 400_000 ;
3839
40+ // Depth limit multiplier: max recursion depth as a multiplier of rlimit.
41+ //
42+ // This is set to be generous enough for legitimate use cases (like complex mathematical
43+ // computations) while still preventing stack overflow. Stack overflow typically occurs
44+ // around 50,000+ recursion levels, so this gives us good protection.
45+ const DEPTH_LIMIT_MULTIPLIER : u64 = 500 ;
46+
47+ // Time-based warning interval in seconds
48+ const WARNING_INTERVAL_SECS : u64 = 2 ;
49+
3950type Env = ScopeMap < UniqueIdent , Exp > ;
4051type TypeEnv = ScopeMap < Ident , Typ > ;
4152
@@ -98,7 +109,7 @@ impl<T> PtrSet<T> {
98109
99110/// Mutable interpreter state
100111struct State {
101- /// Depth of our current recursion; used for formatting log output
112+ /// Depth of our current recursion; used for formatting log output and recursion control
102113 depth : usize ,
103114 /// Symbol table mapping bound variables to their values
104115 env : Env ,
@@ -127,6 +138,10 @@ struct State {
127138 ptr_misses : u64 ,
128139 /// Number of calls for each function
129140 fun_calls : HashMap < Fun , u64 > ,
141+
142+ /// Time tracking for warnings
143+ start_time : Instant ,
144+ last_warning_time : Instant ,
130145}
131146
132147// Define the function-call cache's API
@@ -137,6 +152,22 @@ impl State {
137152 }
138153 }
139154
155+ /// Check time-based warnings and emit warning if enough time has passed. Do so only in debug mode.
156+ fn check_time_warning ( & mut self ) {
157+ if !cfg ! ( debug_assertions) {
158+ return ;
159+ }
160+ let now = Instant :: now ( ) ;
161+ if now. duration_since ( self . last_warning_time ) . as_secs ( ) >= WARNING_INTERVAL_SECS {
162+ let total_time = now. duration_since ( self . start_time ) . as_secs ( ) ;
163+ eprintln ! (
164+ "note: assert_by_compute has been running for {} seconds (depth: {}, iterations: {})" ,
165+ total_time, self . depth, self . iterations
166+ ) ;
167+ self . last_warning_time = now;
168+ }
169+ }
170+
140171 fn lookup_call ( & mut self , f : & Fun , args : & Exps , memoize : bool ) -> Option < Exp > {
141172 if self . enable_cache && memoize {
142173 if self . perf {
@@ -163,6 +194,8 @@ struct Ctx<'a> {
163194 fun_ssts : & ' a HashMap < Fun , FunctionSst > ,
164195 /// We avoid infinite loops by running for a fixed number of intervals
165196 max_iterations : u64 ,
197+ /// Maximum recursion depth to prevent stack overflow
198+ max_depth : usize ,
166199 arch : ArchWordBits ,
167200 global : & ' a GlobalCtx ,
168201}
@@ -1057,6 +1090,11 @@ fn eval_expr_internal(ctx: &Ctx, state: &mut State, exp: &Exp) -> Result<Exp, Vi
10571090 if state. iterations > ctx. max_iterations {
10581091 return Err ( error ( & exp. span , "assert_by_compute timed out" ) ) ;
10591092 }
1093+ if state. depth > ctx. max_depth {
1094+ return Err ( error ( & exp. span , "assert_by_compute exceeded maximum recursion depth" ) ) ;
1095+ }
1096+ state. check_time_warning ( ) ;
1097+
10601098 state. log ( format ! (
10611099 "{}Evaluating {:}" ,
10621100 "\t " . repeat( state. depth) ,
@@ -1860,6 +1898,7 @@ fn eval_expr_launch(
18601898 let cache = HashMap :: new ( ) ;
18611899 let logging = log. is_some ( ) ;
18621900 let msgs = Vec :: new ( ) ;
1901+ let now = Instant :: now ( ) ;
18631902 let mut state = State {
18641903 depth : 0 ,
18651904 env,
@@ -1877,10 +1916,14 @@ fn eval_expr_launch(
18771916 ptr_hits : 0 ,
18781917 ptr_misses : 0 ,
18791918 fun_calls : HashMap :: new ( ) ,
1919+ start_time : now,
1920+ last_warning_time : now,
18801921 } ;
18811922 // Don't run for too long
18821923 let max_iterations = ( rlimit as f64 * RLIMIT_MULTIPLIER as f64 ) as u64 ;
1883- let ctx = Ctx { fun_ssts : & fun_ssts, max_iterations, arch, global } ;
1924+ // Calculate max recursion depth as a fraction of rlimit
1925+ let max_depth = ( rlimit as f64 * DEPTH_LIMIT_MULTIPLIER as f64 ) as usize ;
1926+ let ctx = Ctx { fun_ssts : & fun_ssts, max_iterations, max_depth, arch, global } ;
18841927 let result = eval_expr_top ( & ctx, & mut state, & exp) ?;
18851928 display_perf_stats ( & state) ;
18861929 if state. log . is_some ( ) {
0 commit comments