diff --git a/src/backend/mir_interpreter/exec.rs b/src/backend/mir_interpreter/exec.rs index 5589a8c7..742a5dd3 100644 --- a/src/backend/mir_interpreter/exec.rs +++ b/src/backend/mir_interpreter/exec.rs @@ -13,6 +13,23 @@ impl MirInterpreter { func: &MirFunction, arg_vals: Option<&[VMValue]>, ) -> Result { + // Safety valve: cap nested exec_function_inner depth to avoid Rust stack overflow + // on accidental infinite recursion in MIR (e.g., self-recursive call chains). + const MAX_CALL_DEPTH: usize = 1024; + self.call_depth = self.call_depth.saturating_add(1); + if self.call_depth > MAX_CALL_DEPTH { + eprintln!( + "[vm-call-depth] exceeded {} in fn={} (depth={})", + MAX_CALL_DEPTH, + func.signature.name, + self.call_depth + ); + self.call_depth = self.call_depth.saturating_sub(1); + return Err(VMError::InvalidInstruction(format!( + "vm call stack depth exceeded (max_depth={}, fn={})", + MAX_CALL_DEPTH, func.signature.name + ))); + } // Phase 1: delegate cross-class reroute / narrow fallbacks to method_router if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) { return r; } let saved_regs = mem::take(&mut self.regs); @@ -96,6 +113,7 @@ impl MirInterpreter { BlockOutcome::Return(result) => { self.cur_fn = saved_fn; self.regs = saved_regs; + self.call_depth = self.call_depth.saturating_sub(1); return Ok(result); } BlockOutcome::Next { diff --git a/src/backend/mir_interpreter/mod.rs b/src/backend/mir_interpreter/mod.rs index ede236fb..32309b21 100644 --- a/src/backend/mir_interpreter/mod.rs +++ b/src/backend/mir_interpreter/mod.rs @@ -41,6 +41,9 @@ pub struct MirInterpreter { pub(super) inst_count: u64, pub(super) branch_count: u64, pub(super) compare_count: u64, + /// Call stack depth (exec_function_inner nesting). Used as a safety valve + /// to prevent Rust stack overflow on accidental infinite recursion in MIR. + pub(super) call_depth: usize, } impl MirInterpreter { @@ -58,6 +61,7 @@ impl MirInterpreter { inst_count: 0, branch_count: 0, compare_count: 0, + call_depth: 0, } }