fix: guard unified BoxCall recursion and document Stage-B stack overflow status

This commit is contained in:
nyash-codex
2025-11-17 17:53:40 +09:00
parent 4f3831c07b
commit e5b9b84aca
5 changed files with 78 additions and 9 deletions

View File

@ -188,6 +188,10 @@ pub struct MirBuilder {
/// infinite recursion (emit_unified_call → emit_box_or_plugin_call →
/// emit_unified_call …) can occur when routing decisions disagree.
pub(super) in_unified_boxcall_fallback: bool,
/// Recursion depth counter for debugging stack overflow
/// Tracks the depth of build_expression calls to detect infinite loops
pub(super) recursion_depth: usize,
}
impl MirBuilder {
@ -242,6 +246,7 @@ impl MirBuilder {
schedule_mat_map: HashMap::new(),
in_unified_boxcall_fallback: false,
recursion_depth: 0,
}
}
@ -371,7 +376,21 @@ impl MirBuilder {
/// Build an expression and return its value ID
pub(super) fn build_expression(&mut self, ast: ASTNode) -> Result<ValueId, String> {
// Delegated to exprs.rs to keep this file lean
self.build_expression_impl(ast)
// Debug: Track recursion depth to detect infinite loops
const MAX_RECURSION_DEPTH: usize = 200;
self.recursion_depth += 1;
if self.recursion_depth > MAX_RECURSION_DEPTH {
eprintln!("\n[FATAL] ============================================");
eprintln!("[FATAL] Recursion depth exceeded {} in build_expression", MAX_RECURSION_DEPTH);
eprintln!("[FATAL] Current depth: {}", self.recursion_depth);
eprintln!("[FATAL] AST node type: {:?}", std::mem::discriminant(&ast));
eprintln!("[FATAL] ============================================\n");
return Err(format!("Recursion depth exceeded: {} (possible infinite loop)", self.recursion_depth));
}
let result = self.build_expression_impl(ast);
self.recursion_depth -= 1;
result
}

View File

@ -66,6 +66,27 @@ impl MirBuilder {
object: ASTNode,
method: String,
arguments: Vec<ASTNode>,
) -> Result<ValueId, String> {
// Debug: Check recursion depth
const MAX_METHOD_DEPTH: usize = 100;
self.recursion_depth += 1;
if self.recursion_depth > MAX_METHOD_DEPTH {
eprintln!("[FATAL] build_method_call recursion depth exceeded {}", MAX_METHOD_DEPTH);
eprintln!("[FATAL] Current depth: {}", self.recursion_depth);
eprintln!("[FATAL] Method: {}", method);
return Err(format!("build_method_call recursion depth exceeded: {}", self.recursion_depth));
}
let result = self.build_method_call_impl(object, method, arguments);
self.recursion_depth -= 1;
result
}
fn build_method_call_impl(
&mut self,
object: ASTNode,
method: String,
arguments: Vec<ASTNode>,
) -> Result<ValueId, String> {
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
let kind = match &object {

View File

@ -18,12 +18,34 @@ impl MirBuilder {
target: CallTarget,
args: Vec<ValueId>,
) -> Result<(), String> {
// Check environment variable for unified call usage
if !call_unified::is_unified_call_enabled() {
// Fall back to legacy implementation
return self.emit_legacy_call(dst, target, args);
// Debug: Check recursion depth
const MAX_EMIT_DEPTH: usize = 100;
self.recursion_depth += 1;
if self.recursion_depth > MAX_EMIT_DEPTH {
eprintln!("[FATAL] emit_unified_call recursion depth exceeded {}", MAX_EMIT_DEPTH);
eprintln!("[FATAL] Current depth: {}", self.recursion_depth);
eprintln!("[FATAL] Target: {:?}", target);
return Err(format!("emit_unified_call recursion depth exceeded: {}", self.recursion_depth));
}
// Check environment variable for unified call usage
let result = if !call_unified::is_unified_call_enabled() {
// Fall back to legacy implementation
self.emit_legacy_call(dst, target, args)
} else {
self.emit_unified_call_impl(dst, target, args)
};
self.recursion_depth -= 1;
result
}
fn emit_unified_call_impl(
&mut self,
dst: Option<ValueId>,
target: CallTarget,
args: Vec<ValueId>,
) -> Result<(), String> {
// Emit resolve.try for method targets (dev-only; default OFF)
let arity_for_try = args.len();
if let CallTarget::Method { ref box_type, ref method, receiver } = target {
@ -166,7 +188,13 @@ impl MirBuilder {
// LEGACY PATH (after unified migration):
// Instance→Function rewrite is centralized in unified call path.
// Legacy path no longer functionizes; always use Box/Plugin call here.
self.emit_box_or_plugin_call(dst, receiver, method, None, args, EffectMask::IO)
// CRITICAL FIX: Prevent bouncing back to emit_unified_call
// Set flag to prevent emit_box_or_plugin_call from calling emit_unified_call
let prev_flag = self.in_unified_boxcall_fallback;
self.in_unified_boxcall_fallback = true;
let result = self.emit_box_or_plugin_call(dst, receiver, method, None, args, EffectMask::IO);
self.in_unified_boxcall_fallback = prev_flag;
result
},
CallTarget::Constructor(box_type) => {
// Use existing NewBox