use super::*; use super::super::utils::*; use crate::box_trait::NyashBox; impl MirInterpreter { pub(super) fn handle_new_box( &mut self, dst: ValueId, box_type: &str, args: &[ValueId], ) -> Result<(), VMError> { // Provider Lock guard (受け口・既定は挙動不変) if let Err(e) = crate::runtime::provider_lock::guard_before_new_box(box_type) { return Err(VMError::InvalidInstruction(e)); } let mut converted: Vec> = Vec::with_capacity(args.len()); for vid in args { converted.push(self.reg_load(*vid)?.to_nyash_box()); } let reg = crate::runtime::unified_registry::get_global_unified_registry(); let created = reg .lock() .unwrap() .create_box(box_type, &converted) .map_err(|e| { VMError::InvalidInstruction(format!("NewBox {} failed: {}", box_type, e)) })?; // Store created instance first so 'me' can be passed to birth let created_vm = VMValue::from_nyash_box(created); self.regs.insert(dst, created_vm.clone()); // Trace: new box event (dev-only) if Self::box_trace_enabled() { self.box_trace_emit_new(box_type, args.len()); } // Note: birth の自動呼び出しは削除。 // 正しい設計は Builder が NewBox 後に明示的に birth 呼び出しを生成すること。 Ok(()) } pub(super) fn handle_plugin_invoke( &mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId], ) -> Result<(), VMError> { let recv = self.reg_load(box_val)?; let recv_box: Box = match recv.clone() { VMValue::BoxRef(b) => b.share_box(), other => other.to_nyash_box(), }; if let Some(p) = recv_box .as_any() .downcast_ref::() { let host = crate::runtime::plugin_loader_unified::get_global_plugin_host(); let host = host.read().unwrap(); let mut argv: Vec> = Vec::with_capacity(args.len()); for a in args { argv.push(self.reg_load(*a)?.to_nyash_box()); } match host.invoke_instance_method(&p.box_type, method, p.inner.instance_id, &argv) { Ok(Some(ret)) => { if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); } } Ok(None) => { if let Some(d) = dst { self.regs.insert(d, VMValue::Void); } } Err(e) => { return Err(VMError::InvalidInstruction(format!( "PluginInvoke {}.{} failed: {:?}", p.box_type, method, e ))) } } Ok(()) } else if method == "toString" { if let Some(d) = dst { self.regs .insert(d, VMValue::String(recv_box.to_string_box().value)); } Ok(()) } else { Err(VMError::InvalidInstruction(format!( "PluginInvoke unsupported on {} for method {}", recv_box.type_name(), method ))) } } pub(super) fn handle_box_call( &mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId], ) -> Result<(), VMError> { // Dev-safe: stringify(Void) → "null" (最小安全弁) if method == "stringify" { if let VMValue::Void = self.reg_load(box_val)? { if let Some(d) = dst { self.regs.insert(d, VMValue::String("null".to_string())); } return Ok(()); } if let VMValue::BoxRef(b) = self.reg_load(box_val)? { if b.as_any().downcast_ref::().is_some() { if let Some(d) = dst { self.regs.insert(d, VMValue::String("null".to_string())); } return Ok(()); } } } // Trace: method call (class inferred from receiver) if Self::box_trace_enabled() { let cls = match self.reg_load(box_val).unwrap_or(VMValue::Void) { VMValue::BoxRef(b) => { if let Some(inst) = b.as_any().downcast_ref::() { inst.class_name.clone() } else { b.type_name().to_string() } } VMValue::String(_) => "StringBox".to_string(), VMValue::Integer(_) => "IntegerBox".to_string(), VMValue::Float(_) => "FloatBox".to_string(), VMValue::Bool(_) => "BoolBox".to_string(), VMValue::Void => "".to_string(), VMValue::Future(_) => "".to_string(), }; self.box_trace_emit_call(&cls, method, args.len()); } if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" { eprintln!("[vm-trace] handle_box_call: method=trim (pre-dispatch)"); } // Debug: trace length dispatch receiver type before any handler resolution if method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") { let recv = self.reg_load(box_val).unwrap_or(VMValue::Void); let type_name = match recv.clone() { VMValue::BoxRef(b) => b.type_name().to_string(), VMValue::Integer(_) => "Integer".to_string(), VMValue::Float(_) => "Float".to_string(), VMValue::Bool(_) => "Bool".to_string(), VMValue::String(_) => "String".to_string(), VMValue::Void => "Void".to_string(), VMValue::Future(_) => "Future".to_string(), }; eprintln!("[vm-trace] length dispatch recv_type={}", type_name); } // Graceful void guard for common short-circuit patterns in user code // e.g., `A or not last.is_eof()` should not crash when last is absent. match self.reg_load(box_val)? { VMValue::Void => { if let Some(val) = super::boxes_void_guards::handle_void_method(method) { if let Some(d) = dst { self.regs.insert(d, val); } return Ok(()); } } VMValue::BoxRef(ref b) => { if b.as_any().downcast_ref::().is_some() { if let Some(val) = super::boxes_void_guards::handle_void_method(method) { if let Some(d) = dst { self.regs.insert(d, val); } return Ok(()); } } } _ => {} } if super::boxes_object_fields::try_handle_object_fields(self, dst, box_val, method, args)? { trace_dispatch!(method, "object_fields"); return Ok(()); } // Policy gate: user InstanceBox BoxCall runtime fallback // - Prod: disallowed (builder must have rewritten obj.m(...) to a // function call). Error here indicates a builder/using materialize // miss. // - Dev/CI: allowed with WARN to aid diagnosis. let mut user_instance_class: Option = None; if let VMValue::BoxRef(ref b) = self.reg_load(box_val)? { if let Some(inst) = b.as_any().downcast_ref::() { user_instance_class = Some(inst.class_name.clone()); } } if user_instance_class.is_some() && !crate::config::env::vm_allow_user_instance_boxcall() { let cls = user_instance_class.unwrap(); return Err(VMError::InvalidInstruction(format!( "User Instance BoxCall disallowed in prod: {}.{} (enable builder rewrite)", cls, method ))); } if user_instance_class.is_some() && crate::config::env::vm_allow_user_instance_boxcall() { if crate::config::env::cli_verbose() { eprintln!( "[warn] dev fallback: user instance BoxCall {}.{} routed via VM instance-dispatch", user_instance_class.as_ref().unwrap(), method ); } } if super::boxes_instance::try_handle_instance_box(self, dst, box_val, method, args)? { trace_dispatch!(method, "instance_box"); return Ok(()); } if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" { eprintln!("[vm-trace] dispatch trying boxes_string"); } if super::boxes_string::try_handle_string_box(self, dst, box_val, method, args)? { trace_dispatch!(method, "string_box"); if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" { eprintln!("[vm-trace] dispatch handled by boxes_string"); } return Ok(()); } if super::boxes_array::try_handle_array_box(self, dst, box_val, method, args)? { trace_dispatch!(method, "array_box"); return Ok(()); } if super::boxes_map::try_handle_map_box(self, dst, box_val, method, args)? { trace_dispatch!(method, "map_box"); return Ok(()); } // Narrow safety valve: if 'length' wasn't handled by any box-specific path, // treat it as 0 (avoids Lt on Void in common loops). This is a dev-time // robustness measure; precise behavior should be provided by concrete boxes. if method == "length" { trace_dispatch!(method, "fallback(length=0)"); self.write_result(dst, VMValue::Integer(0)); return Ok(()); } // Fallback: unique-tail dynamic resolution for user-defined methods // Narrowing: restrict to receiver's class when available to avoid // accidentally binding methods from unrelated boxes that happen to // share the same method name/arity (e.g., JsonScanner.is_eof vs JsonToken.is_eof). if let Some(func) = { let tail = format!(".{}{}", method, format!("/{}", args.len())); let mut cands: Vec = self .functions .keys() .filter(|k| k.ends_with(&tail)) .cloned() .collect(); // Determine receiver class name when possible let recv_cls: Option = match self.reg_load(box_val).ok() { Some(VMValue::BoxRef(b)) => { if let Some(inst) = b.as_any().downcast_ref::() { Some(inst.class_name.clone()) } else { None } } _ => None, }; if let Some(ref want) = recv_cls { let prefix = format!("{}.", want); cands.retain(|k| k.starts_with(&prefix)); } if cands.len() == 1 { self.functions.get(&cands[0]).cloned() } else { None } } { // Build argv: pass receiver as first arg (me) let recv_vm = self.reg_load(box_val)?; let mut argv: Vec = Vec::with_capacity(1 + args.len()); argv.push(recv_vm); for a in args { argv.push(self.reg_load(*a)?); } let ret = self.exec_function_inner(&func, Some(&argv))?; if let Some(d) = dst { self.regs.insert(d, ret); } return Ok(()); } super::boxes_plugin::invoke_plugin_box(self, dst, box_val, method, args) } // moved: try_handle_map_box → handlers/boxes_map.rs fn try_handle_map_box( &mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId], ) -> Result { super::boxes_map::try_handle_map_box(self, dst, box_val, method, args) } // moved: try_handle_string_box → handlers/boxes_string.rs fn try_handle_string_box( &mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId], ) -> Result { super::boxes_string::try_handle_string_box(self, dst, box_val, method, args) } // moved: try_handle_array_box → handlers/boxes_array.rs fn try_handle_array_box( &mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId], ) -> Result { super::boxes_array::try_handle_array_box(self, dst, box_val, method, args) } }