fix: guard unified BoxCall recursion and document Stage-B stack overflow status
This commit is contained in:
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user