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