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