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::{SourceFile, SourceSpan};
8use miden_utils_diagnostics::{Diagnostic, miette};
9
10use crate::{
11    BaseHost, ContextId, DebugError, Felt, TraceError, Word,
12    advice::AdviceError,
13    event::{EventError, EventId, EventName},
14    fast::SystemEventError,
15    mast::{MastForest, MastNodeId},
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 (event handlers, debug handlers, trace handlers).
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    #[error("debug handler error: {err}")]
164    DebugHandlerError {
165        #[source]
166        err: DebugError,
167    },
168    #[error("trace handler error for trace ID {trace_id}: {err}")]
169    TraceHandlerError {
170        trace_id: u32,
171        #[source]
172        err: TraceError,
173    },
174}
175
176// IO ERROR
177// ================================================================================================
178
179/// Context-free error type for IO operations.
180///
181/// This enum wraps errors from the advice provider and memory subsystems without
182/// carrying source location context. Context is added at the call site via
183/// `IoResultExt::map_io_err`.
184#[derive(Debug, thiserror::Error, Diagnostic)]
185pub enum IoError {
186    #[error(transparent)]
187    Advice(#[from] AdviceError),
188    #[error(transparent)]
189    Memory(#[from] MemoryError),
190    #[error(transparent)]
191    #[diagnostic(transparent)]
192    Operation(#[from] OperationError),
193    /// Stack operation error (increment/decrement size failures).
194    ///
195    /// These are internal execution errors that don't need additional context
196    /// since they already carry their own error information.
197    #[error(transparent)]
198    #[diagnostic(transparent)]
199    Execution(Box<ExecutionError>),
200}
201
202impl From<ExecutionError> for IoError {
203    fn from(err: ExecutionError) -> Self {
204        IoError::Execution(Box::new(err))
205    }
206}
207
208// MEMORY ERROR
209// ================================================================================================
210
211/// Lightweight error type for memory operations.
212///
213/// This enum captures error conditions without expensive context information (no source location,
214/// no file references). When a `MemoryError` propagates up to become an `ExecutionError`, the
215/// context is resolved lazily via `MapExecErr::map_exec_err`.
216#[derive(Debug, thiserror::Error, Diagnostic)]
217pub enum MemoryError {
218    #[error("memory address cannot exceed 2^32 but was {addr}")]
219    AddressOutOfBounds { addr: u64 },
220    #[error(
221        "memory address {addr} in context {ctx} was read and written, or written twice, in the same clock cycle {clk}"
222    )]
223    IllegalMemoryAccess { ctx: ContextId, addr: u32, clk: Felt },
224    #[error(
225        "memory range start address cannot exceed end address, but was ({start_addr}, {end_addr})"
226    )]
227    InvalidMemoryRange { start_addr: u64, end_addr: u64 },
228    #[error("word access at memory address {addr} in context {ctx} is unaligned")]
229    #[diagnostic(help(
230        "ensure that the memory address accessed is aligned to a word boundary (it is a multiple of 4)"
231    ))]
232    UnalignedWordAccess { addr: u32, ctx: ContextId },
233    #[error("failed to read from memory: {0}")]
234    MemoryReadFailed(String),
235}
236
237// CRYPTO ERROR
238// ================================================================================================
239
240/// Context-free error type for cryptographic operations (Merkle path verification, updates).
241///
242/// This enum wraps errors from the advice provider and operation subsystems without carrying
243/// source location context. Context is added at the call site via
244/// `CryptoResultExt::map_crypto_err`.
245#[derive(Debug, thiserror::Error, Diagnostic)]
246pub enum CryptoError {
247    #[error(transparent)]
248    Advice(#[from] AdviceError),
249    #[error(transparent)]
250    #[diagnostic(transparent)]
251    Operation(#[from] OperationError),
252}
253
254// OPERATION ERROR
255// ================================================================================================
256
257/// Lightweight error type for operations that can fail.
258///
259/// This enum captures error conditions without expensive context information (no source location,
260/// no file references). When an `OperationError` propagates up to become an `ExecutionError`, the
261/// context is resolved lazily via extension traits like `OperationResultExt::map_exec_err`.
262///
263/// # Adding new errors (for contributors)
264///
265/// **Use `OperationError` when:**
266/// - The error occurs during operation execution (e.g., assertion failures, type mismatches)
267/// - Context can be resolved at the call site via the extension traits
268/// - The error needs both a human-readable message and optional diagnostic help
269///
270/// **Avoid duplicating error context.** Context is added by the extension traits,
271/// so do NOT add `label` or `source_file` fields to the variant.
272///
273/// **Pattern at call sites:**
274/// ```ignore
275/// // Return OperationError and let the caller wrap it:
276/// fn some_op() -> Result<(), OperationError> {
277///     Err(OperationError::DivideByZero)
278/// }
279///
280/// // Caller wraps with context lazily:
281/// some_op().map_exec_err(mast_forest, node_id, host)?;
282/// ```
283///
284/// For wrapper errors (`AdviceError`, `EventError`, `AceError`), use the corresponding extension
285/// traits (`AdviceResultExt`, `AceResultExt`) or helper functions (`advice_error_with_context`,
286/// `event_error_with_context`).
287#[derive(Debug, Clone, thiserror::Error, Diagnostic)]
288pub enum OperationError {
289    #[error("external node with mast root {0} resolved to an external node")]
290    CircularExternalNode(Word),
291    #[error("division by zero")]
292    #[diagnostic(help(
293        "ensure the divisor (second stack element) is non-zero before division or modulo operations"
294    ))]
295    DivideByZero,
296    #[error(
297        "assertion failed with error {}",
298        match err_msg {
299            Some(msg) => format!("message: {msg}"),
300            None => format!("code: {err_code}"),
301        }
302    )]
303    #[diagnostic(help(
304        "assertions validate program invariants. Review the assertion condition and ensure all prerequisites are met"
305    ))]
306    FailedAssertion {
307        err_code: Felt,
308        err_msg: Option<Arc<str>>,
309    },
310    #[error(
311        "u32 assertion failed with error {}: invalid values: {invalid_values:?}",
312        match err_msg {
313            Some(msg) => format!("message: {msg}"),
314            None => format!("code: {err_code}"),
315        }
316    )]
317    #[diagnostic(help(
318        "u32assert2 requires both stack values to be valid 32-bit unsigned integers"
319    ))]
320    U32AssertionFailed {
321        err_code: Felt,
322        err_msg: Option<Arc<str>>,
323        invalid_values: Vec<Felt>,
324    },
325    #[error("FRI operation failed: {0}")]
326    FriError(String),
327    #[error(
328        "invalid crypto operation: Merkle path length {path_len} does not match expected depth {depth}"
329    )]
330    InvalidMerklePathLength { path_len: usize, depth: Felt },
331    #[error("when returning from a call, stack depth must be {MIN_STACK_DEPTH}, but was {depth}")]
332    InvalidStackDepthOnReturn { depth: usize },
333    #[error("attempted to calculate integer logarithm with zero argument")]
334    #[diagnostic(help("ilog2 requires a non-zero argument"))]
335    LogArgumentZero,
336    #[error(
337        "MAST forest in host indexed by procedure root {root_digest} doesn't contain that root"
338    )]
339    MalformedMastForestInHost { root_digest: Word },
340    #[error("merkle path verification failed for value {value} at index {index} in the Merkle tree with root {root} (error {err})",
341      value = to_hex(inner.value.as_bytes()),
342      root = to_hex(inner.root.as_bytes()),
343      index = inner.index,
344      err = match &inner.err_msg {
345        Some(msg) => format!("message: {msg}"),
346        None => format!("code: {}", inner.err_code),
347      }
348    )]
349    MerklePathVerificationFailed {
350        inner: Box<MerklePathVerificationFailedInner>,
351    },
352    #[error("operation expected a binary value, but got {value}")]
353    NotBinaryValue { value: Felt },
354    #[error("if statement expected a binary value on top of the stack, but got {value}")]
355    NotBinaryValueIf { value: Felt },
356    #[error("loop condition must be a binary value, but got {value}")]
357    #[diagnostic(help(
358        "this could happen either when first entering the loop, or any subsequent iteration"
359    ))]
360    NotBinaryValueLoop { value: Felt },
361    #[error("operation expected u32 values, but got values: {values:?}")]
362    NotU32Values { values: Vec<Felt> },
363    #[error("syscall failed: procedure with root {proc_root} was not found in the kernel")]
364    SyscallTargetNotInKernel { proc_root: Word },
365    #[error("failed to execute the operation for internal reason: {0}")]
366    Internal(&'static str),
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(
375        self,
376        mast_forest: &MastForest,
377        node_id: MastNodeId,
378        host: &impl BaseHost,
379    ) -> ExecutionError {
380        let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
381        ExecutionError::OperationError { label, source_file, err: self }
382    }
383}
384
385/// Inner data for `OperationError::MerklePathVerificationFailed`.
386///
387/// Boxed to reduce the size of `OperationError`.
388#[derive(Debug, Clone)]
389pub struct MerklePathVerificationFailedInner {
390    pub value: Word,
391    pub index: Felt,
392    pub root: Word,
393    pub err_code: Felt,
394    pub err_msg: Option<Arc<str>>,
395}
396
397// EXTENSION TRAITS
398// ================================================================================================
399
400/// Computes the label and source file for error context.
401///
402/// This function is called by the extension traits to compute source location
403/// only when an error occurs. Since errors are rare, the cost of decorator
404/// traversal is acceptable.
405fn get_label_and_source_file(
406    op_idx: Option<usize>,
407    mast_forest: &MastForest,
408    node_id: MastNodeId,
409    host: &impl BaseHost,
410) -> (SourceSpan, Option<Arc<SourceFile>>) {
411    mast_forest
412        .get_assembly_op(node_id, op_idx)
413        .and_then(|assembly_op| assembly_op.location())
414        .map_or_else(
415            || (SourceSpan::UNKNOWN, None),
416            |location| host.get_label_and_source_file(location),
417        )
418}
419
420/// Wraps an `AdviceError` with execution context to produce an `ExecutionError`.
421///
422/// This is useful when working with `ControlFlow` or other non-`Result` return types
423/// where the extension traits cannot be used directly.
424pub fn advice_error_with_context(
425    err: AdviceError,
426    mast_forest: &MastForest,
427    node_id: MastNodeId,
428    host: &impl BaseHost,
429    op_idx: Option<usize>,
430) -> ExecutionError {
431    let (label, source_file) = get_label_and_source_file(op_idx, mast_forest, node_id, host);
432    ExecutionError::AdviceError { label, source_file, err }
433}
434
435/// Wraps an `EventError` with execution context to produce an `ExecutionError`.
436///
437/// This is useful when working with `ControlFlow` or other non-`Result` return types
438/// where an extension trait on `Result` cannot be used directly.
439pub fn event_error_with_context(
440    error: EventError,
441    mast_forest: &MastForest,
442    node_id: MastNodeId,
443    host: &impl BaseHost,
444    op_idx: Option<usize>,
445    event_id: EventId,
446    event_name: Option<EventName>,
447) -> ExecutionError {
448    let (label, source_file) = get_label_and_source_file(op_idx, mast_forest, node_id, host);
449    ExecutionError::EventError {
450        label,
451        source_file,
452        event_id,
453        event_name,
454        error,
455    }
456}
457
458/// Creates a `ProcedureNotFound` error with execution context.
459pub fn procedure_not_found_with_context(
460    root_digest: Word,
461    mast_forest: &MastForest,
462    node_id: MastNodeId,
463    host: &impl BaseHost,
464) -> ExecutionError {
465    let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
466    ExecutionError::ProcedureNotFound { label, source_file, root_digest }
467}
468
469// CONSOLIDATED EXTENSION TRAITS (plafer's approach)
470// ================================================================================================
471//
472// Three traits organized by method signature rather than by error type:
473// 1. MapExecErr - for errors with basic context (forest, node_id, host)
474// 2. MapExecErrWithOpIdx - for errors in basic blocks that need op_idx
475// 3. MapExecErrNoCtx - for errors without any context
476
477/// Extension trait for mapping errors to `ExecutionError` with basic context.
478///
479/// Implement this for error types that can be converted to `ExecutionError` using
480/// just the MAST forest, node ID, and host for source location lookup.
481pub trait MapExecErr<T> {
482    fn map_exec_err(
483        self,
484        mast_forest: &MastForest,
485        node_id: MastNodeId,
486        host: &impl BaseHost,
487    ) -> Result<T, ExecutionError>;
488}
489
490/// Extension trait for mapping errors to `ExecutionError` with op index context.
491///
492/// Implement this for error types that occur within basic blocks where the
493/// operation index is available for more precise source location.
494pub trait MapExecErrWithOpIdx<T> {
495    fn map_exec_err_with_op_idx(
496        self,
497        mast_forest: &MastForest,
498        node_id: MastNodeId,
499        host: &impl BaseHost,
500        op_idx: usize,
501    ) -> Result<T, ExecutionError>;
502}
503
504/// Extension trait for mapping errors to `ExecutionError` without context.
505///
506/// Implement this for error types that may need to be converted when no
507/// error context is available (e.g., during initialization).
508pub trait MapExecErrNoCtx<T> {
509    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError>;
510}
511
512// OperationError implementations
513impl<T> MapExecErr<T> for Result<T, OperationError> {
514    #[inline(always)]
515    fn map_exec_err(
516        self,
517        mast_forest: &MastForest,
518        node_id: MastNodeId,
519        host: &impl BaseHost,
520    ) -> Result<T, ExecutionError> {
521        match self {
522            Ok(v) => Ok(v),
523            Err(err) => {
524                let (label, source_file) =
525                    get_label_and_source_file(None, mast_forest, node_id, host);
526                Err(ExecutionError::OperationError { label, source_file, err })
527            },
528        }
529    }
530}
531
532impl<T> MapExecErrWithOpIdx<T> for Result<T, OperationError> {
533    #[inline(always)]
534    fn map_exec_err_with_op_idx(
535        self,
536        mast_forest: &MastForest,
537        node_id: MastNodeId,
538        host: &impl BaseHost,
539        op_idx: usize,
540    ) -> Result<T, ExecutionError> {
541        match self {
542            Ok(v) => Ok(v),
543            Err(err) => {
544                let (label, source_file) =
545                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
546                Err(ExecutionError::OperationError { label, source_file, err })
547            },
548        }
549    }
550}
551
552impl<T> MapExecErrNoCtx<T> for Result<T, OperationError> {
553    #[inline(always)]
554    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError> {
555        match self {
556            Ok(v) => Ok(v),
557            Err(err) => Err(ExecutionError::OperationError {
558                label: SourceSpan::UNKNOWN,
559                source_file: None,
560                err,
561            }),
562        }
563    }
564}
565
566// AdviceError implementations
567impl<T> MapExecErr<T> for Result<T, AdviceError> {
568    #[inline(always)]
569    fn map_exec_err(
570        self,
571        mast_forest: &MastForest,
572        node_id: MastNodeId,
573        host: &impl BaseHost,
574    ) -> Result<T, ExecutionError> {
575        match self {
576            Ok(v) => Ok(v),
577            Err(err) => Err(advice_error_with_context(err, mast_forest, node_id, host, None)),
578        }
579    }
580}
581
582impl<T> MapExecErrNoCtx<T> for Result<T, AdviceError> {
583    #[inline(always)]
584    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError> {
585        match self {
586            Ok(v) => Ok(v),
587            Err(err) => Err(ExecutionError::AdviceError {
588                label: SourceSpan::UNKNOWN,
589                source_file: None,
590                err,
591            }),
592        }
593    }
594}
595
596// MemoryError implementations
597impl<T> MapExecErr<T> for Result<T, MemoryError> {
598    #[inline(always)]
599    fn map_exec_err(
600        self,
601        mast_forest: &MastForest,
602        node_id: MastNodeId,
603        host: &impl BaseHost,
604    ) -> Result<T, ExecutionError> {
605        match self {
606            Ok(v) => Ok(v),
607            Err(err) => {
608                let (label, source_file) =
609                    get_label_and_source_file(None, mast_forest, node_id, host);
610                Err(ExecutionError::MemoryError { label, source_file, err })
611            },
612        }
613    }
614}
615
616impl<T> MapExecErrWithOpIdx<T> for Result<T, MemoryError> {
617    #[inline(always)]
618    fn map_exec_err_with_op_idx(
619        self,
620        mast_forest: &MastForest,
621        node_id: MastNodeId,
622        host: &impl BaseHost,
623        op_idx: usize,
624    ) -> Result<T, ExecutionError> {
625        match self {
626            Ok(v) => Ok(v),
627            Err(err) => {
628                let (label, source_file) =
629                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
630                Err(ExecutionError::MemoryError { label, source_file, err })
631            },
632        }
633    }
634}
635
636// SystemEventError implementations
637impl<T> MapExecErr<T> for Result<T, SystemEventError> {
638    #[inline(always)]
639    fn map_exec_err(
640        self,
641        mast_forest: &MastForest,
642        node_id: MastNodeId,
643        host: &impl BaseHost,
644    ) -> Result<T, ExecutionError> {
645        match self {
646            Ok(v) => Ok(v),
647            Err(err) => {
648                let (label, source_file) =
649                    get_label_and_source_file(None, mast_forest, node_id, host);
650                Err(match err {
651                    SystemEventError::Advice(err) => {
652                        ExecutionError::AdviceError { label, source_file, err }
653                    },
654                    SystemEventError::Operation(err) => {
655                        ExecutionError::OperationError { label, source_file, err }
656                    },
657                    SystemEventError::Memory(err) => {
658                        ExecutionError::MemoryError { label, source_file, err }
659                    },
660                })
661            },
662        }
663    }
664}
665
666impl<T> MapExecErrWithOpIdx<T> for Result<T, SystemEventError> {
667    #[inline(always)]
668    fn map_exec_err_with_op_idx(
669        self,
670        mast_forest: &MastForest,
671        node_id: MastNodeId,
672        host: &impl BaseHost,
673        op_idx: usize,
674    ) -> Result<T, ExecutionError> {
675        match self {
676            Ok(v) => Ok(v),
677            Err(err) => {
678                let (label, source_file) =
679                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
680                Err(match err {
681                    SystemEventError::Advice(err) => {
682                        ExecutionError::AdviceError { label, source_file, err }
683                    },
684                    SystemEventError::Operation(err) => {
685                        ExecutionError::OperationError { label, source_file, err }
686                    },
687                    SystemEventError::Memory(err) => {
688                        ExecutionError::MemoryError { label, source_file, err }
689                    },
690                })
691            },
692        }
693    }
694}
695
696// IoError implementations
697impl<T> MapExecErrWithOpIdx<T> for Result<T, IoError> {
698    #[inline(always)]
699    fn map_exec_err_with_op_idx(
700        self,
701        mast_forest: &MastForest,
702        node_id: MastNodeId,
703        host: &impl BaseHost,
704        op_idx: usize,
705    ) -> Result<T, ExecutionError> {
706        match self {
707            Ok(v) => Ok(v),
708            Err(err) => {
709                let (label, source_file) =
710                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
711                Err(match err {
712                    IoError::Advice(err) => ExecutionError::AdviceError { label, source_file, err },
713                    IoError::Memory(err) => ExecutionError::MemoryError { label, source_file, err },
714                    IoError::Operation(err) => {
715                        ExecutionError::OperationError { label, source_file, err }
716                    },
717                    // Execution errors are already fully formed with their own message.
718                    IoError::Execution(boxed_err) => *boxed_err,
719                })
720            },
721        }
722    }
723}
724
725// CryptoError implementations
726impl<T> MapExecErrWithOpIdx<T> for Result<T, CryptoError> {
727    #[inline(always)]
728    fn map_exec_err_with_op_idx(
729        self,
730        mast_forest: &MastForest,
731        node_id: MastNodeId,
732        host: &impl BaseHost,
733        op_idx: usize,
734    ) -> Result<T, ExecutionError> {
735        match self {
736            Ok(v) => Ok(v),
737            Err(err) => {
738                let (label, source_file) =
739                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
740                Err(match err {
741                    CryptoError::Advice(err) => {
742                        ExecutionError::AdviceError { label, source_file, err }
743                    },
744                    CryptoError::Operation(err) => {
745                        ExecutionError::OperationError { label, source_file, err }
746                    },
747                })
748            },
749        }
750    }
751}
752
753// AceEvalError implementations
754impl<T> MapExecErrWithOpIdx<T> for Result<T, AceEvalError> {
755    #[inline(always)]
756    fn map_exec_err_with_op_idx(
757        self,
758        mast_forest: &MastForest,
759        node_id: MastNodeId,
760        host: &impl BaseHost,
761        op_idx: usize,
762    ) -> Result<T, ExecutionError> {
763        match self {
764            Ok(v) => Ok(v),
765            Err(err) => {
766                let (label, source_file) =
767                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
768                Err(match err {
769                    AceEvalError::Ace(error) => {
770                        ExecutionError::AceChipError { label, source_file, error }
771                    },
772                    AceEvalError::Memory(err) => {
773                        ExecutionError::MemoryError { label, source_file, err }
774                    },
775                })
776            },
777        }
778    }
779}
780
781// TESTS
782// ================================================================================================
783
784#[cfg(test)]
785mod error_assertions {
786    use super::*;
787
788    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
789    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
790
791    fn _assert_execution_error_bounds(err: ExecutionError) {
792        _assert_error_is_send_sync_static(err);
793    }
794}