Skip to main content

miden_processor/
errors.rs

1// Allow unused assignments - required by miette::Diagnostic derive macro
2#![allow(unused_assignments)]
3
4use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
5
6use miden_core::{deferred::PrecompileError, program::MIN_STACK_DEPTH};
7use miden_debug_types::{Location, SourceFile, SourceSpan};
8use miden_mast_package::{
9    PackageDebugInfoError,
10    debug_info::{DebugSourceNodeId, PackageDebugInfo},
11};
12use miden_utils_diagnostics::{Diagnostic, miette};
13
14use crate::{
15    BaseHost, ContextId, Felt, Word,
16    advice::AdviceError,
17    event::{EventError, EventId, EventName},
18    fast::SystemEventError,
19    utils::to_hex,
20};
21
22// EXECUTION ERROR
23// ================================================================================================
24
25#[non_exhaustive]
26#[derive(Debug, thiserror::Error, Diagnostic)]
27pub enum ExecutionError {
28    #[error("failed to execute arithmetic circuit evaluation operation: {error}")]
29    #[diagnostic()]
30    AceChipError {
31        #[label("this call failed")]
32        label: SourceSpan,
33        #[source_code]
34        source_file: Option<Arc<SourceFile>>,
35        error: AceError,
36    },
37    #[error("{err}")]
38    #[diagnostic(forward(err))]
39    AdviceError {
40        #[label]
41        label: SourceSpan,
42        #[source_code]
43        source_file: Option<Arc<SourceFile>>,
44        err: AdviceError,
45    },
46    #[error("exceeded the allowed number of max cycles {0}")]
47    CycleLimitExceeded(u32),
48    #[error("error during processing of event {}", match event_name {
49        Some(name) => format!("'{name}' (ID: {event_id})"),
50        None => format!("with ID: {event_id}"),
51    })]
52    #[diagnostic()]
53    EventError {
54        #[label]
55        label: SourceSpan,
56        #[source_code]
57        source_file: Option<Arc<SourceFile>>,
58        event_id: EventId,
59        event_name: Option<EventName>,
60        #[source]
61        error: EventError,
62    },
63    /// Deferred system event failed while validating or evaluating a committed node.
64    #[error("{err}")]
65    #[diagnostic()]
66    DeferredError {
67        #[label]
68        label: SourceSpan,
69        #[source_code]
70        source_file: Option<Arc<SourceFile>>,
71        err: PrecompileError,
72    },
73    #[error("failed to execute the program for internal reason: {0}")]
74    Internal(&'static str),
75    #[error("operand stack depth {depth} exceeds the maximum of {max}")]
76    StackDepthLimitExceeded { depth: usize, max: usize },
77    /// This means trace generation would go over the configured row limit.
78    ///
79    /// In parallel trace building, this is used for core-row prechecks and chiplet overflow.
80    #[error("trace length exceeded the maximum of {0} rows")]
81    TraceLenExceeded(usize),
82    /// Memory error with source context for diagnostics.
83    ///
84    /// Use `MemoryResultExt::map_mem_err` to convert `Result<T, MemoryError>` with context.
85    #[error("{err}")]
86    #[diagnostic(forward(err))]
87    MemoryError {
88        #[label]
89        label: SourceSpan,
90        #[source_code]
91        source_file: Option<Arc<SourceFile>>,
92        err: MemoryError,
93    },
94    /// Memory error without source context (for internal operations like FMP initialization).
95    ///
96    /// Use `ExecutionError::MemoryErrorNoCtx` for memory errors that don't have error context
97    /// available (e.g., during call/syscall context initialization).
98    #[error(transparent)]
99    #[diagnostic(transparent)]
100    MemoryErrorNoCtx(MemoryError),
101    #[error("{err}")]
102    #[diagnostic(forward(err))]
103    OperationError {
104        #[label]
105        label: SourceSpan,
106        #[source_code]
107        source_file: Option<Arc<SourceFile>>,
108        err: OperationError,
109    },
110    #[error("stack should have at most {MIN_STACK_DEPTH} elements at the end of program execution, but had {} elements", MIN_STACK_DEPTH + .0)]
111    OutputStackOverflow(usize),
112    #[error("procedure with root digest {root_digest} could not be found")]
113    #[diagnostic()]
114    ProcedureNotFound {
115        #[label]
116        label: SourceSpan,
117        #[source_code]
118        source_file: Option<Arc<SourceFile>>,
119        root_digest: Word,
120    },
121    #[error("failed to generate STARK proof: {0}")]
122    ProvingError(String),
123    #[error(transparent)]
124    HostError(#[from] HostError),
125    #[error(transparent)]
126    PackageDebugInfoError(#[from] PackageDebugInfoError),
127}
128
129impl ExecutionError {
130    /// Wraps an advice error without source-location context.
131    pub fn advice_error_no_context(err: AdviceError) -> Self {
132        Self::AdviceError {
133            label: SourceSpan::UNKNOWN,
134            source_file: None,
135            err,
136        }
137    }
138}
139
140impl AsRef<dyn Diagnostic> for ExecutionError {
141    fn as_ref(&self) -> &(dyn Diagnostic + 'static) {
142        self
143    }
144}
145
146// ACE ERROR
147// ================================================================================================
148
149#[derive(Debug, thiserror::Error)]
150#[error("ace circuit evaluation failed: {0}")]
151pub struct AceError(pub String);
152
153// ACE EVAL ERROR
154// ================================================================================================
155
156/// Context-free error type for ACE circuit evaluation operations.
157///
158/// This enum wraps errors from ACE evaluation and memory subsystems without
159/// carrying source location context. Context is added at the call site via
160/// `AceEvalResultExt::map_ace_eval_err`.
161#[derive(Debug, thiserror::Error)]
162pub enum AceEvalError {
163    #[error(transparent)]
164    Ace(#[from] AceError),
165    #[error(transparent)]
166    Memory(#[from] MemoryError),
167}
168
169// HOST ERROR
170// ================================================================================================
171
172/// Error type for host-related operations.
173#[derive(Debug, thiserror::Error)]
174pub enum HostError {
175    #[error("attempted to add event handler for '{event}' (already registered)")]
176    DuplicateEventHandler { event: EventName },
177    #[error("attempted to add event handler for '{event}' (reserved system event)")]
178    ReservedEventNamespace { event: EventName },
179}
180
181// IO ERROR
182// ================================================================================================
183
184/// Context-free error type for IO operations.
185///
186/// This enum wraps errors from the advice provider and memory subsystems without
187/// carrying source location context. Context is added at the call site via
188/// `IoResultExt::map_io_err`.
189#[derive(Debug, thiserror::Error, Diagnostic)]
190pub enum IoError {
191    #[error(transparent)]
192    Advice(#[from] AdviceError),
193    #[error(transparent)]
194    Memory(#[from] MemoryError),
195    #[error(transparent)]
196    #[diagnostic(transparent)]
197    Operation(#[from] OperationError),
198    /// Stack operation error (increment/decrement size failures).
199    ///
200    /// These are internal execution errors that don't need additional context
201    /// since they already carry their own error information.
202    #[error(transparent)]
203    #[diagnostic(transparent)]
204    Execution(Box<ExecutionError>),
205}
206
207impl From<ExecutionError> for IoError {
208    fn from(err: ExecutionError) -> Self {
209        IoError::Execution(Box::new(err))
210    }
211}
212
213// MEMORY ERROR
214// ================================================================================================
215
216/// Lightweight error type for memory operations.
217///
218/// This enum captures error conditions without expensive context information (no source location,
219/// no file references). When a `MemoryError` propagates up to become an `ExecutionError`, the
220/// context is resolved lazily via `MapExecErr::map_exec_err`.
221#[derive(Debug, thiserror::Error, Diagnostic)]
222pub enum MemoryError {
223    #[error("memory address cannot exceed 2^32 but was {addr}")]
224    AddressOutOfBounds { addr: u64 },
225    #[error(
226        "memory address {addr} in context {ctx} was read and written, or written twice, in the same clock cycle {clk}"
227    )]
228    IllegalMemoryAccess { ctx: ContextId, addr: u32, clk: Felt },
229    #[error(
230        "memory range start address cannot exceed end address, but was ({start_addr}, {end_addr})"
231    )]
232    InvalidMemoryRange { start_addr: u64, end_addr: u64 },
233    #[error(
234        "word access at memory address {addr} in context {ctx} is unaligned: word accesses require addresses that are multiples of 4"
235    )]
236    UnalignedWordAccess { addr: u32, ctx: ContextId },
237    #[error("failed to read from memory: {0}")]
238    MemoryReadFailed(String),
239    #[error(
240        "writing to memory address {addr} in context {ctx} would exceed the maximum number of memory elements {max}"
241    )]
242    #[diagnostic(help(
243        "increase the limit via `ExecutionOptions::with_max_memory_elements`, or reduce the number of distinct memory addresses the program writes to"
244    ))]
245    MemoryElementLimitExceeded { ctx: ContextId, addr: u32, max: usize },
246}
247
248// CRYPTO ERROR
249// ================================================================================================
250
251/// Context-free error type for cryptographic operations (Merkle path verification, updates).
252///
253/// This enum wraps errors from the advice provider and operation subsystems without carrying
254/// source location context. Context is added at the call site via
255/// `CryptoResultExt::map_crypto_err`.
256#[derive(Debug, thiserror::Error, Diagnostic)]
257pub enum CryptoError {
258    #[error(transparent)]
259    Advice(#[from] AdviceError),
260    #[error(transparent)]
261    #[diagnostic(transparent)]
262    Operation(#[from] OperationError),
263}
264
265// OPERATION ERROR
266// ================================================================================================
267
268/// Lightweight error type for operations that can fail.
269///
270/// This enum captures error conditions without expensive context information (no source location,
271/// no file references). When an `OperationError` propagates up to become an `ExecutionError`, the
272/// context is resolved lazily via extension traits like `OperationResultExt::map_exec_err`.
273///
274/// # Adding new errors (for contributors)
275///
276/// **Use `OperationError` when:**
277/// - The error occurs during operation execution (e.g., assertion failures, type mismatches)
278/// - Context can be resolved at the call site via the extension traits
279/// - The error needs both a human-readable message and optional diagnostic help
280///
281/// **Avoid duplicating error context.** Context is added by the extension traits,
282/// so do NOT add `label` or `source_file` fields to the variant.
283///
284/// **Pattern at call sites:**
285/// ```ignore
286/// // Return OperationError and let the caller wrap it:
287/// fn some_op() -> Result<(), OperationError> {
288///     Err(OperationError::DivideByZero)
289/// }
290///
291/// // Caller wraps with context lazily:
292/// some_op().map_exec_err()?;
293/// ```
294///
295/// For wrapper errors (`AdviceError`, `EventError`, `AceError`), use the corresponding extension
296/// traits (`AdviceResultExt`, `AceResultExt`) or helper functions (`advice_error_with_context`,
297/// `event_error_with_context`).
298#[derive(Debug, Clone, thiserror::Error, Diagnostic)]
299pub enum OperationError {
300    #[error("external node with mast root {0} resolved to an external node")]
301    CircularExternalNode(Word),
302    #[error("division by zero: divisor must be non-zero for division or modulo operations")]
303    DivideByZero,
304    #[error(transparent)]
305    Deferred(#[from] PrecompileError),
306    #[error(
307        "assertion failed with error {}",
308        match err_msg {
309            Some(msg) => format!("message: {msg}"),
310            None => format!("code: {err_code}"),
311        }
312    )]
313    FailedAssertion {
314        err_code: Felt,
315        err_msg: Option<Arc<str>>,
316    },
317    #[error(
318        "u32 assertion failed: u32assert2 requires both stack values to be valid 32-bit unsigned integers; error {}; invalid values: {invalid_values:?}",
319        match err_msg {
320            Some(msg) => format!("message: {msg}"),
321            None => format!("code: {err_code}"),
322        }
323    )]
324    U32AssertionFailed {
325        err_code: Felt,
326        err_msg: Option<Arc<str>>,
327        invalid_values: Vec<Felt>,
328    },
329    #[error("FRI operation failed: {0}")]
330    FriError(String),
331    #[error(
332        "invalid crypto operation: Merkle path length {path_len} does not match expected depth {depth}"
333    )]
334    InvalidMerklePathLength { path_len: usize, depth: Felt },
335    #[error("when returning from a call, stack depth must be {MIN_STACK_DEPTH}, but was {depth}")]
336    InvalidStackDepthOnReturn { depth: usize },
337    #[error("ilog2 requires a non-zero argument")]
338    LogArgumentZero,
339    #[error(
340        "MAST forest in host indexed by procedure root {root_digest} doesn't contain that root"
341    )]
342    MalformedMastForestInHost { root_digest: Word },
343    #[error("merkle path verification failed for value {value} at index {index} in the Merkle tree with root {root} (error {err})",
344      value = to_hex(inner.value.as_bytes()),
345      root = to_hex(inner.root.as_bytes()),
346      index = inner.index,
347      err = match &inner.err_msg {
348        Some(msg) => format!("message: {msg}"),
349        None => format!("code: {}", inner.err_code),
350      }
351    )]
352    MerklePathVerificationFailed {
353        inner: Box<MerklePathVerificationFailedInner>,
354    },
355    #[error("{message}, but got {value}", message = context.message())]
356    NotBinaryValue {
357        context: BinaryValueErrorContext,
358        value: Felt,
359    },
360    #[error("operation expected u32 values, but got values: {values:?}")]
361    NotU32Values { values: Vec<Felt> },
362    #[error("syscall failed: procedure with root {proc_root} was not found in the kernel")]
363    SyscallTargetNotInKernel { proc_root: Word },
364    #[error("failed to execute the operation for internal reason: {0}")]
365    Internal(&'static str),
366}
367
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum BinaryValueErrorContext {
370    Operation,
371    If,
372    Loop,
373}
374
375impl BinaryValueErrorContext {
376    const fn message(self) -> &'static str {
377        match self {
378            Self::Operation => "operation expected a binary value",
379            Self::If => "if statement expected a binary value on top of the stack",
380            Self::Loop => {
381                "loop condition must be a binary value on entry and each subsequent iteration"
382            },
383        }
384    }
385}
386
387impl OperationError {
388    /// Wraps this error with execution context to produce an `ExecutionError`.
389    ///
390    /// This is useful when working with `ControlFlow` or other non-`Result` return types
391    /// where the `OperationResultExt::map_exec_err` extension trait cannot be used directly.
392    pub fn with_context(self) -> ExecutionError {
393        let (label, source_file) = get_label_and_source_file();
394        ExecutionError::OperationError { label, source_file, err: self }
395    }
396
397    /// Wraps this error with package-owned source-occurrence execution context.
398    ///
399    /// Unlike [`Self::with_context`], this resolves source metadata from package debug sections
400    /// keyed by a source/debug MAST occurrence rather than by the reduced execution MAST node.
401    pub fn with_package_source_context(
402        self,
403        context: PackageSourceDebugContext<'_>,
404        host: &(dyn BaseHost + '_),
405        op_idx: Option<usize>,
406    ) -> ExecutionError {
407        let (label, source_file) =
408            label_and_source_file_from_location(context.assembly_location(op_idx).as_ref(), host);
409        ExecutionError::OperationError {
410            label,
411            source_file,
412            err: self.with_package_debug_info(context.debug_info()),
413        }
414    }
415
416    fn with_package_debug_info(self, debug_info: &PackageDebugInfo) -> Self {
417        match self {
418            Self::FailedAssertion { err_code, err_msg: None } => Self::FailedAssertion {
419                err_msg: debug_info.error_message(err_code.as_canonical_u64()),
420                err_code,
421            },
422            Self::U32AssertionFailed { err_code, err_msg: None, invalid_values } => {
423                Self::U32AssertionFailed {
424                    err_msg: debug_info.error_message(err_code.as_canonical_u64()),
425                    err_code,
426                    invalid_values,
427                }
428            },
429            Self::MerklePathVerificationFailed { mut inner } if inner.err_msg.is_none() => {
430                inner.err_msg = debug_info.error_message(inner.err_code.as_canonical_u64());
431                Self::MerklePathVerificationFailed { inner }
432            },
433            err => err,
434        }
435    }
436}
437
438/// Inner data for `OperationError::MerklePathVerificationFailed`.
439///
440/// Boxed to reduce the size of `OperationError`.
441#[derive(Debug, Clone)]
442pub struct MerklePathVerificationFailedInner {
443    pub value: Word,
444    pub index: Felt,
445    pub root: Word,
446    pub err_code: Felt,
447    pub err_msg: Option<Arc<str>>,
448}
449
450// EXTENSION TRAITS
451// ================================================================================================
452
453/// Source-occurrence debug context decoded from package debug sections.
454///
455/// This keeps diagnostic lookup keyed by [`DebugSourceNodeId`] so two source occurrences that
456/// reduce to the same executable MAST node can still report distinct source locations.
457#[derive(Clone, Copy, Debug)]
458pub struct PackageSourceDebugContext<'a> {
459    debug_info: &'a PackageDebugInfo,
460    source_node_id: Option<DebugSourceNodeId>,
461}
462
463impl<'a> PackageSourceDebugContext<'a> {
464    /// Creates a source debug context for one package-owned source/debug MAST occurrence.
465    pub fn new(debug_info: &'a PackageDebugInfo, source_node_id: DebugSourceNodeId) -> Self {
466        Self {
467            debug_info,
468            source_node_id: Some(source_node_id),
469        }
470    }
471
472    /// Creates a package debug context when source location metadata may be unavailable.
473    pub(crate) fn new_optional(
474        debug_info: &'a PackageDebugInfo,
475        source_node_id: Option<DebugSourceNodeId>,
476    ) -> Self {
477        Self { debug_info, source_node_id }
478    }
479
480    /// Returns the source/debug MAST occurrence associated with this context, if known.
481    pub fn source_node_id(&self) -> Option<DebugSourceNodeId> {
482        self.source_node_id
483    }
484
485    /// Returns the package debug info backing this context.
486    pub fn debug_info(&self) -> &'a PackageDebugInfo {
487        self.debug_info
488    }
489
490    /// Returns source location metadata for `op_idx`, if present.
491    ///
492    /// If `op_idx` is absent, this falls back to the first operation row for the source occurrence.
493    pub fn assembly_location(&self, op_idx: Option<usize>) -> Option<Location> {
494        let source_node_id = self.source_node_id?;
495        let assembly_op = match op_idx {
496            Some(op_idx) => u32::try_from(op_idx)
497                .ok()
498                .and_then(|op_idx| self.debug_info.asm_op_for_operation(source_node_id, op_idx)),
499            None => self.debug_info.first_asm_op_for_source_node(source_node_id),
500        }?;
501
502        assembly_op
503            .location_idx
504            .into_option()
505            .and_then(|idx| self.debug_info.get_location(idx))
506    }
507}
508
509fn label_and_source_file_from_location(
510    location: Option<&Location>,
511    host: &(dyn BaseHost + '_),
512) -> (SourceSpan, Option<Arc<SourceFile>>) {
513    location.map_or_else(
514        || (SourceSpan::UNKNOWN, None),
515        |location| host.get_label_and_source_file(location),
516    )
517}
518
519/// Computes the label and source file for error context.
520///
521/// This function is called by the extension traits to compute source location
522/// only when an error occurs. Since errors are rare, the cost of source metadata lookup is
523/// acceptable.
524fn get_label_and_source_file() -> (SourceSpan, Option<Arc<SourceFile>>) {
525    (SourceSpan::UNKNOWN, None)
526}
527
528/// Wraps an `AdviceError` with execution context to produce an `ExecutionError`.
529///
530/// This is useful when working with `ControlFlow` or other non-`Result` return types
531/// where the extension traits cannot be used directly.
532pub fn advice_error_with_context(err: AdviceError) -> ExecutionError {
533    let (label, source_file) = get_label_and_source_file();
534    ExecutionError::AdviceError { label, source_file, err }
535}
536
537/// Wraps an `AdviceError` with package-owned source-occurrence execution context.
538pub fn advice_error_with_package_source_context(
539    err: AdviceError,
540    context: PackageSourceDebugContext<'_>,
541    host: &(dyn BaseHost + '_),
542    op_idx: Option<usize>,
543) -> ExecutionError {
544    let (label, source_file) =
545        label_and_source_file_from_location(context.assembly_location(op_idx).as_ref(), host);
546    ExecutionError::AdviceError { label, source_file, err }
547}
548
549/// Wraps an `EventError` with execution context to produce an `ExecutionError`.
550///
551/// This is useful when working with `ControlFlow` or other non-`Result` return types
552/// where an extension trait on `Result` cannot be used directly.
553pub fn event_error_with_context(
554    error: EventError,
555    event_id: EventId,
556    event_name: Option<EventName>,
557) -> ExecutionError {
558    let (label, source_file) = get_label_and_source_file();
559    ExecutionError::EventError {
560        label,
561        source_file,
562        event_id,
563        event_name,
564        error,
565    }
566}
567
568/// Wraps an `EventError` with package-owned source-occurrence execution context.
569pub fn event_error_with_package_source_context(
570    error: EventError,
571    context: PackageSourceDebugContext<'_>,
572    host: &(dyn BaseHost + '_),
573    op_idx: Option<usize>,
574    event_id: EventId,
575    event_name: Option<EventName>,
576) -> ExecutionError {
577    let (label, source_file) =
578        label_and_source_file_from_location(context.assembly_location(op_idx).as_ref(), host);
579    ExecutionError::EventError {
580        label,
581        source_file,
582        event_id,
583        event_name,
584        error,
585    }
586}
587
588/// Creates a `ProcedureNotFound` error with execution context.
589pub fn procedure_not_found_with_context(root_digest: Word) -> ExecutionError {
590    let (label, source_file) = get_label_and_source_file();
591    ExecutionError::ProcedureNotFound { label, source_file, root_digest }
592}
593
594/// Creates a `ProcedureNotFound` error with package-owned source-occurrence execution context.
595pub fn procedure_not_found_with_package_source_context(
596    root_digest: Word,
597    context: PackageSourceDebugContext<'_>,
598    host: &(dyn BaseHost + '_),
599) -> ExecutionError {
600    let (label, source_file) =
601        label_and_source_file_from_location(context.assembly_location(None).as_ref(), host);
602    ExecutionError::ProcedureNotFound { label, source_file, root_digest }
603}
604
605/// Creates a `MalformedMastForestInHost` operation error with execution context.
606pub fn malformed_mast_forest_with_context(
607    root_digest: Word,
608    context: Option<PackageSourceDebugContext<'_>>,
609    host: &(dyn BaseHost + '_),
610) -> ExecutionError {
611    let err = OperationError::MalformedMastForestInHost { root_digest };
612    match context {
613        Some(context) => err.with_package_source_context(context, host, None),
614        None => err.with_context(),
615    }
616}
617
618// CONSOLIDATED EXTENSION TRAITS (plafer's approach)
619// ================================================================================================
620//
621// Three traits organized by method signature rather than by error type:
622// 1. MapExecErr - for errors with basic context
623// 2. MapExecErrWithOpIdx - for errors in basic blocks that may need op_idx
624// 3. MapExecErrNoCtx - for errors without any context
625
626/// Extension trait for mapping errors to `ExecutionError`.
627///
628/// Legacy MAST-local debug metadata no longer provides source locations here; callers that have
629/// package-owned source context should use `map_exec_err_with_package_source_op_idx`.
630pub trait MapExecErr<T> {
631    fn map_exec_err(self) -> Result<T, ExecutionError>;
632}
633
634/// Extension trait for mapping errors to `ExecutionError` with op index context.
635///
636/// Implement this for error types that occur within basic blocks where the
637/// operation index is available for more precise source location.
638pub trait MapExecErrWithOpIdx<T> {
639    fn map_exec_err_with_op_idx(self) -> Result<T, ExecutionError>;
640
641    fn map_exec_err_with_package_source_op_idx(
642        self,
643        context: Option<PackageSourceDebugContext<'_>>,
644        _host: &(dyn BaseHost + '_),
645        _op_idx: usize,
646    ) -> Result<T, ExecutionError>
647    where
648        Self: Sized,
649    {
650        if context.is_some() {
651            return Err(ExecutionError::Internal(
652                "package source context is unsupported for this error type",
653            ));
654        }
655
656        self.map_exec_err_with_op_idx()
657    }
658}
659
660/// Extension trait for mapping errors to `ExecutionError` without context.
661///
662/// Implement this for error types that may need to be converted when no
663/// error context is available (e.g., during initialization).
664pub trait MapExecErrNoCtx<T> {
665    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError>;
666}
667
668// OperationError implementations
669impl<T> MapExecErr<T> for Result<T, OperationError> {
670    #[inline(always)]
671    fn map_exec_err(self) -> Result<T, ExecutionError> {
672        match self {
673            Ok(v) => Ok(v),
674            Err(err) => {
675                let (label, source_file) = get_label_and_source_file();
676                Err(ExecutionError::OperationError { label, source_file, err })
677            },
678        }
679    }
680}
681
682impl<T> MapExecErrWithOpIdx<T> for Result<T, OperationError> {
683    #[inline(always)]
684    fn map_exec_err_with_op_idx(self) -> Result<T, ExecutionError> {
685        match self {
686            Ok(v) => Ok(v),
687            Err(err) => {
688                let (label, source_file) = get_label_and_source_file();
689                Err(ExecutionError::OperationError { label, source_file, err })
690            },
691        }
692    }
693
694    #[inline(always)]
695    fn map_exec_err_with_package_source_op_idx(
696        self,
697        context: Option<PackageSourceDebugContext<'_>>,
698        host: &(dyn BaseHost + '_),
699        op_idx: usize,
700    ) -> Result<T, ExecutionError> {
701        match (self, context) {
702            (Ok(v), _) => Ok(v),
703            (Err(err), Some(context)) => {
704                Err(err.with_package_source_context(context, host, Some(op_idx)))
705            },
706            (Err(err), None) => {
707                let (label, source_file) = get_label_and_source_file();
708                Err(ExecutionError::OperationError { label, source_file, err })
709            },
710        }
711    }
712}
713
714impl<T> MapExecErrNoCtx<T> for Result<T, OperationError> {
715    #[inline(always)]
716    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError> {
717        match self {
718            Ok(v) => Ok(v),
719            Err(err) => Err(ExecutionError::OperationError {
720                label: SourceSpan::UNKNOWN,
721                source_file: None,
722                err,
723            }),
724        }
725    }
726}
727
728// AdviceError implementations
729impl<T> MapExecErr<T> for Result<T, AdviceError> {
730    #[inline(always)]
731    fn map_exec_err(self) -> Result<T, ExecutionError> {
732        match self {
733            Ok(v) => Ok(v),
734            Err(err) => Err(advice_error_with_context(err)),
735        }
736    }
737}
738
739impl<T> MapExecErrNoCtx<T> for Result<T, AdviceError> {
740    #[inline(always)]
741    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError> {
742        match self {
743            Ok(v) => Ok(v),
744            Err(err) => Err(ExecutionError::AdviceError {
745                label: SourceSpan::UNKNOWN,
746                source_file: None,
747                err,
748            }),
749        }
750    }
751}
752
753// MemoryError implementations
754impl<T> MapExecErr<T> for Result<T, MemoryError> {
755    #[inline(always)]
756    fn map_exec_err(self) -> Result<T, ExecutionError> {
757        match self {
758            Ok(v) => Ok(v),
759            Err(err) => {
760                let (label, source_file) = get_label_and_source_file();
761                Err(ExecutionError::MemoryError { label, source_file, err })
762            },
763        }
764    }
765}
766
767impl<T> MapExecErrWithOpIdx<T> for Result<T, MemoryError> {
768    #[inline(always)]
769    fn map_exec_err_with_op_idx(self) -> Result<T, ExecutionError> {
770        match self {
771            Ok(v) => Ok(v),
772            Err(err) => {
773                let (label, source_file) = get_label_and_source_file();
774                Err(ExecutionError::MemoryError { label, source_file, err })
775            },
776        }
777    }
778
779    #[inline(always)]
780    fn map_exec_err_with_package_source_op_idx(
781        self,
782        context: Option<PackageSourceDebugContext<'_>>,
783        host: &(dyn BaseHost + '_),
784        op_idx: usize,
785    ) -> Result<T, ExecutionError> {
786        match (self, context) {
787            (Ok(v), _) => Ok(v),
788            (Err(err), Some(context)) => {
789                let (label, source_file) = label_and_source_file_from_location(
790                    context.assembly_location(Some(op_idx)).as_ref(),
791                    host,
792                );
793                Err(ExecutionError::MemoryError { label, source_file, err })
794            },
795            (Err(err), None) => {
796                let (label, source_file) = get_label_and_source_file();
797                Err(ExecutionError::MemoryError { label, source_file, err })
798            },
799        }
800    }
801}
802
803// SystemEventError implementations
804impl<T> MapExecErr<T> for Result<T, SystemEventError> {
805    #[inline(always)]
806    fn map_exec_err(self) -> Result<T, ExecutionError> {
807        match self {
808            Ok(v) => Ok(v),
809            Err(err) => {
810                let (label, source_file) = get_label_and_source_file();
811                Err(match err {
812                    SystemEventError::Advice(err) => {
813                        ExecutionError::AdviceError { label, source_file, err }
814                    },
815                    SystemEventError::Operation(err) => {
816                        ExecutionError::OperationError { label, source_file, err }
817                    },
818                    SystemEventError::Memory(err) => {
819                        ExecutionError::MemoryError { label, source_file, err }
820                    },
821                    SystemEventError::Deferred(err) => {
822                        ExecutionError::DeferredError { label, source_file, err }
823                    },
824                })
825            },
826        }
827    }
828}
829
830impl<T> MapExecErrWithOpIdx<T> for Result<T, SystemEventError> {
831    #[inline(always)]
832    fn map_exec_err_with_op_idx(self) -> Result<T, ExecutionError> {
833        match self {
834            Ok(v) => Ok(v),
835            Err(err) => {
836                let (label, source_file) = get_label_and_source_file();
837                Err(match err {
838                    SystemEventError::Advice(err) => {
839                        ExecutionError::AdviceError { label, source_file, err }
840                    },
841                    SystemEventError::Operation(err) => {
842                        ExecutionError::OperationError { label, source_file, err }
843                    },
844                    SystemEventError::Memory(err) => {
845                        ExecutionError::MemoryError { label, source_file, err }
846                    },
847                    SystemEventError::Deferred(err) => {
848                        ExecutionError::DeferredError { label, source_file, err }
849                    },
850                })
851            },
852        }
853    }
854
855    #[inline(always)]
856    fn map_exec_err_with_package_source_op_idx(
857        self,
858        context: Option<PackageSourceDebugContext<'_>>,
859        host: &(dyn BaseHost + '_),
860        op_idx: usize,
861    ) -> Result<T, ExecutionError> {
862        match (self, context) {
863            (Ok(v), _) => Ok(v),
864            (Err(err), Some(context)) => {
865                let (label, source_file) = label_and_source_file_from_location(
866                    context.assembly_location(Some(op_idx)).as_ref(),
867                    host,
868                );
869                Err(match err {
870                    SystemEventError::Advice(err) => {
871                        ExecutionError::AdviceError { label, source_file, err }
872                    },
873                    SystemEventError::Operation(err) => {
874                        ExecutionError::OperationError { label, source_file, err }
875                    },
876                    SystemEventError::Memory(err) => {
877                        ExecutionError::MemoryError { label, source_file, err }
878                    },
879                    SystemEventError::Deferred(err) => {
880                        ExecutionError::DeferredError { label, source_file, err }
881                    },
882                })
883            },
884            (Err(err), None) => {
885                let (label, source_file) = get_label_and_source_file();
886                Err(match err {
887                    SystemEventError::Advice(err) => {
888                        ExecutionError::AdviceError { label, source_file, err }
889                    },
890                    SystemEventError::Operation(err) => {
891                        ExecutionError::OperationError { label, source_file, err }
892                    },
893                    SystemEventError::Memory(err) => {
894                        ExecutionError::MemoryError { label, source_file, err }
895                    },
896                    SystemEventError::Deferred(err) => {
897                        ExecutionError::DeferredError { label, source_file, err }
898                    },
899                })
900            },
901        }
902    }
903}
904
905// IoError implementations
906impl<T> MapExecErrWithOpIdx<T> for Result<T, IoError> {
907    #[inline(always)]
908    fn map_exec_err_with_op_idx(self) -> Result<T, ExecutionError> {
909        match self {
910            Ok(v) => Ok(v),
911            Err(err) => {
912                let (label, source_file) = get_label_and_source_file();
913                Err(match err {
914                    IoError::Advice(err) => ExecutionError::AdviceError { label, source_file, err },
915                    IoError::Memory(err) => ExecutionError::MemoryError { label, source_file, err },
916                    IoError::Operation(err) => {
917                        ExecutionError::OperationError { label, source_file, err }
918                    },
919                    // Execution errors are already fully formed with their own message.
920                    IoError::Execution(boxed_err) => *boxed_err,
921                })
922            },
923        }
924    }
925
926    #[inline(always)]
927    fn map_exec_err_with_package_source_op_idx(
928        self,
929        context: Option<PackageSourceDebugContext<'_>>,
930        host: &(dyn BaseHost + '_),
931        op_idx: usize,
932    ) -> Result<T, ExecutionError> {
933        match (self, context) {
934            (Ok(v), _) => Ok(v),
935            (Err(IoError::Execution(boxed_err)), _) => Err(*boxed_err),
936            (Err(err), Some(context)) => {
937                let (label, source_file) = label_and_source_file_from_location(
938                    context.assembly_location(Some(op_idx)).as_ref(),
939                    host,
940                );
941                Err(match err {
942                    IoError::Advice(err) => ExecutionError::AdviceError { label, source_file, err },
943                    IoError::Memory(err) => ExecutionError::MemoryError { label, source_file, err },
944                    IoError::Operation(err) => {
945                        ExecutionError::OperationError { label, source_file, err }
946                    },
947                    IoError::Execution(_) => unreachable!("handled above"),
948                })
949            },
950            (Err(err), None) => {
951                let (label, source_file) = get_label_and_source_file();
952                Err(match err {
953                    IoError::Advice(err) => ExecutionError::AdviceError { label, source_file, err },
954                    IoError::Memory(err) => ExecutionError::MemoryError { label, source_file, err },
955                    IoError::Operation(err) => {
956                        ExecutionError::OperationError { label, source_file, err }
957                    },
958                    IoError::Execution(_) => unreachable!("handled above"),
959                })
960            },
961        }
962    }
963}
964
965// CryptoError implementations
966impl<T> MapExecErrWithOpIdx<T> for Result<T, CryptoError> {
967    #[inline(always)]
968    fn map_exec_err_with_op_idx(self) -> Result<T, ExecutionError> {
969        match self {
970            Ok(v) => Ok(v),
971            Err(err) => {
972                let (label, source_file) = get_label_and_source_file();
973                Err(match err {
974                    CryptoError::Advice(err) => {
975                        ExecutionError::AdviceError { label, source_file, err }
976                    },
977                    CryptoError::Operation(err) => {
978                        ExecutionError::OperationError { label, source_file, err }
979                    },
980                })
981            },
982        }
983    }
984
985    #[inline(always)]
986    fn map_exec_err_with_package_source_op_idx(
987        self,
988        context: Option<PackageSourceDebugContext<'_>>,
989        host: &(dyn BaseHost + '_),
990        op_idx: usize,
991    ) -> Result<T, ExecutionError> {
992        match (self, context) {
993            (Ok(v), _) => Ok(v),
994            (Err(err), Some(context)) => {
995                let (label, source_file) = label_and_source_file_from_location(
996                    context.assembly_location(Some(op_idx)).as_ref(),
997                    host,
998                );
999                Err(match err {
1000                    CryptoError::Advice(err) => {
1001                        ExecutionError::AdviceError { label, source_file, err }
1002                    },
1003                    CryptoError::Operation(err) => ExecutionError::OperationError {
1004                        label,
1005                        source_file,
1006                        err: err.with_package_debug_info(context.debug_info()),
1007                    },
1008                })
1009            },
1010            (Err(err), None) => {
1011                let (label, source_file) = get_label_and_source_file();
1012                Err(match err {
1013                    CryptoError::Advice(err) => {
1014                        ExecutionError::AdviceError { label, source_file, err }
1015                    },
1016                    CryptoError::Operation(err) => {
1017                        ExecutionError::OperationError { label, source_file, err }
1018                    },
1019                })
1020            },
1021        }
1022    }
1023}
1024
1025// AceEvalError implementations
1026impl<T> MapExecErrWithOpIdx<T> for Result<T, AceEvalError> {
1027    #[inline(always)]
1028    fn map_exec_err_with_op_idx(self) -> Result<T, ExecutionError> {
1029        match self {
1030            Ok(v) => Ok(v),
1031            Err(err) => {
1032                let (label, source_file) = get_label_and_source_file();
1033                Err(match err {
1034                    AceEvalError::Ace(error) => {
1035                        ExecutionError::AceChipError { label, source_file, error }
1036                    },
1037                    AceEvalError::Memory(err) => {
1038                        ExecutionError::MemoryError { label, source_file, err }
1039                    },
1040                })
1041            },
1042        }
1043    }
1044
1045    #[inline(always)]
1046    fn map_exec_err_with_package_source_op_idx(
1047        self,
1048        context: Option<PackageSourceDebugContext<'_>>,
1049        host: &(dyn BaseHost + '_),
1050        op_idx: usize,
1051    ) -> Result<T, ExecutionError> {
1052        match (self, context) {
1053            (Ok(v), _) => Ok(v),
1054            (Err(err), Some(context)) => {
1055                let (label, source_file) = label_and_source_file_from_location(
1056                    context.assembly_location(Some(op_idx)).as_ref(),
1057                    host,
1058                );
1059                Err(match err {
1060                    AceEvalError::Ace(error) => {
1061                        ExecutionError::AceChipError { label, source_file, error }
1062                    },
1063                    AceEvalError::Memory(err) => {
1064                        ExecutionError::MemoryError { label, source_file, err }
1065                    },
1066                })
1067            },
1068            (Err(err), None) => {
1069                let (label, source_file) = get_label_and_source_file();
1070                Err(match err {
1071                    AceEvalError::Ace(error) => {
1072                        ExecutionError::AceChipError { label, source_file, error }
1073                    },
1074                    AceEvalError::Memory(err) => {
1075                        ExecutionError::MemoryError { label, source_file, err }
1076                    },
1077                })
1078            },
1079        }
1080    }
1081}
1082
1083// TESTS
1084// ================================================================================================
1085
1086#[cfg(test)]
1087mod error_assertions {
1088    use alloc::sync::Arc;
1089
1090    use miden_core::mast::MastNodeId;
1091    use miden_debug_types::{ByteIndex, SourceId, Uri};
1092    use miden_mast_package::debug_info::{
1093        DebugSourceAsmOp, DebugSourceNode, PackageDebugInfoBuilder,
1094    };
1095
1096    use super::*;
1097
1098    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
1099    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
1100
1101    fn _assert_execution_error_bounds(err: ExecutionError) {
1102        _assert_error_is_send_sync_static(err);
1103    }
1104
1105    fn debug_asm_op(
1106        builder: &mut PackageDebugInfoBuilder,
1107        op_idx: u32,
1108        location: Option<Location>,
1109        context_name: &str,
1110        op_name: &str,
1111    ) -> DebugSourceAsmOp {
1112        DebugSourceAsmOp::new(
1113            op_idx,
1114            location.map(|location| builder.add_location(location)),
1115            builder.add_string(context_name),
1116            builder.add_string(op_name),
1117            1,
1118        )
1119    }
1120
1121    fn debug_source_node(exec_node: u32, asm_ops: Vec<DebugSourceAsmOp>) -> DebugSourceNode {
1122        let op_end = asm_ops.iter().map(|row| row.op_idx + 1).max().unwrap_or(1);
1123        DebugSourceNode {
1124            exec_node: MastNodeId::from(exec_node),
1125            children: Vec::new(),
1126            op_start: 0,
1127            op_end,
1128            asm_ops,
1129            debug_vars: Vec::new(),
1130            inline_calls: Vec::new(),
1131        }
1132    }
1133
1134    struct RecordingHost {
1135        expected_location: Location,
1136        returned_span: SourceSpan,
1137    }
1138
1139    impl BaseHost for RecordingHost {
1140        fn get_label_and_source_file(
1141            &self,
1142            location: &Location,
1143        ) -> (SourceSpan, Option<Arc<SourceFile>>) {
1144            assert_eq!(location, &self.expected_location);
1145            (self.returned_span, None)
1146        }
1147    }
1148
1149    #[test]
1150    fn package_source_context_resolves_by_source_occurrence() {
1151        let location_a = Location::new(
1152            Uri::new("file://pkg/first.masm"),
1153            ByteIndex::new(10),
1154            ByteIndex::new(13),
1155        );
1156        let location_b = Location::new(
1157            Uri::new("file://pkg/second.masm"),
1158            ByteIndex::new(20),
1159            ByteIndex::new(24),
1160        );
1161        let later_location_b = Location::new(
1162            Uri::new("file://pkg/second-later.masm"),
1163            ByteIndex::new(30),
1164            ByteIndex::new(35),
1165        );
1166        let mut builder = PackageDebugInfoBuilder::default();
1167        let source_a_op = debug_asm_op(&mut builder, 0, Some(location_a), "first", "add");
1168        let _source_a = builder.add_node(debug_source_node(0, vec![source_a_op])).unwrap();
1169        let source_b_later_op =
1170            debug_asm_op(&mut builder, 2, Some(later_location_b), "second_later", "mul");
1171        let source_b_op = debug_asm_op(&mut builder, 0, Some(location_b.clone()), "second", "add");
1172        let source_b = builder
1173            .add_node(debug_source_node(1, vec![source_b_later_op, source_b_op]))
1174            .unwrap();
1175        let debug_info = *builder.build();
1176        let host = RecordingHost {
1177            expected_location: location_b,
1178            returned_span: SourceSpan::new(SourceId::new(7), 20u32..24),
1179        };
1180        let context = PackageSourceDebugContext::new(&debug_info, source_b);
1181
1182        assert_eq!(context.assembly_location(None), Some(host.expected_location.clone()));
1183
1184        let err = OperationError::DivideByZero.with_package_source_context(context, &host, Some(0));
1185
1186        match err {
1187            ExecutionError::OperationError { label, source_file, err } => {
1188                assert_eq!(label, host.returned_span);
1189                assert!(source_file.is_none());
1190                assert!(matches!(err, OperationError::DivideByZero));
1191            },
1192            err => panic!("expected operation error, got {err:?}"),
1193        }
1194    }
1195
1196    #[test]
1197    fn package_source_context_without_location_uses_unknown_span() {
1198        let mut builder = PackageDebugInfoBuilder::default();
1199        let asm_op = debug_asm_op(&mut builder, 0, None, "missing_location", "add");
1200        let source_node_id = builder.add_node(debug_source_node(0, vec![asm_op])).unwrap();
1201        let debug_info = *builder.build();
1202        let host = RecordingHost {
1203            expected_location: Location::new(
1204                Uri::new("file://unused.masm"),
1205                ByteIndex::new(0),
1206                ByteIndex::new(0),
1207            ),
1208            returned_span: SourceSpan::new(SourceId::new(7), 20u32..24),
1209        };
1210        let context = PackageSourceDebugContext::new(&debug_info, source_node_id);
1211
1212        let err = advice_error_with_package_source_context(
1213            AdviceError::StackReadFailed,
1214            context,
1215            &host,
1216            Some(0),
1217        );
1218
1219        match err {
1220            ExecutionError::AdviceError { label, source_file, err } => {
1221                assert_eq!(label, SourceSpan::UNKNOWN);
1222                assert!(source_file.is_none());
1223                assert!(matches!(err, AdviceError::StackReadFailed));
1224            },
1225            err => panic!("expected advice error, got {err:?}"),
1226        }
1227    }
1228
1229    #[test]
1230    fn package_debug_info_without_source_node_restores_error_message() {
1231        let mut builder = PackageDebugInfoBuilder::default();
1232        assert!(builder.add_error_message(7, Arc::from("some error message")));
1233        let debug_info = *builder.build();
1234        let context = PackageSourceDebugContext::new_optional(&debug_info, None);
1235        let err = Err::<(), _>(OperationError::FailedAssertion {
1236            err_code: Felt::from_u32(7),
1237            err_msg: None,
1238        })
1239        .map_exec_err_with_package_source_op_idx(
1240            Some(context),
1241            &RecordingHost {
1242                expected_location: Location::new(
1243                    Uri::new("file://unused.masm"),
1244                    ByteIndex::new(0),
1245                    ByteIndex::new(0),
1246                ),
1247                returned_span: SourceSpan::new(SourceId::new(7), 20u32..24),
1248            },
1249            0,
1250        )
1251        .unwrap_err();
1252
1253        match err {
1254            ExecutionError::OperationError {
1255                label,
1256                source_file,
1257                err: OperationError::FailedAssertion { err_msg, .. },
1258            } => {
1259                assert_eq!(label, SourceSpan::UNKNOWN);
1260                assert!(source_file.is_none());
1261                assert_eq!(err_msg.as_deref(), Some("some error message"));
1262            },
1263            err => panic!("expected failed assertion operation error, got {err:?}"),
1264        }
1265    }
1266
1267    #[test]
1268    fn package_debug_info_restores_merkle_path_error_message() {
1269        let mut builder = PackageDebugInfoBuilder::default();
1270        assert!(builder.add_error_message(7, Arc::from("some error message")));
1271        let debug_info = *builder.build();
1272        let err = OperationError::MerklePathVerificationFailed {
1273            inner: Box::new(MerklePathVerificationFailedInner {
1274                value: Word::default(),
1275                index: Felt::from_u32(3),
1276                root: Word::default(),
1277                err_code: Felt::from_u32(7),
1278                err_msg: None,
1279            }),
1280        }
1281        .with_package_debug_info(&debug_info);
1282
1283        match err {
1284            OperationError::MerklePathVerificationFailed { inner } => {
1285                assert_eq!(inner.err_msg.as_deref(), Some("some error message"));
1286            },
1287            err => panic!("expected MerklePathVerificationFailed, got {err:?}"),
1288        }
1289    }
1290}