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    ContextId, DebugError, Felt, Host, 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("FRI operation failed: {0}")]
298    FriError(String),
299    #[error(
300        "invalid crypto operation: Merkle path length {path_len} does not match expected depth {depth}"
301    )]
302    InvalidMerklePathLength { path_len: usize, depth: Felt },
303    #[error("when returning from a call, stack depth must be {MIN_STACK_DEPTH}, but was {depth}")]
304    InvalidStackDepthOnReturn { depth: usize },
305    #[error("attempted to calculate integer logarithm with zero argument")]
306    #[diagnostic(help("ilog2 requires a non-zero argument"))]
307    LogArgumentZero,
308    #[error(
309        "MAST forest in host indexed by procedure root {root_digest} doesn't contain that root"
310    )]
311    MalformedMastForestInHost { root_digest: Word },
312    #[error("merkle path verification failed for value {value} at index {index} in the Merkle tree with root {root} (error {err})",
313      value = to_hex(inner.value.as_bytes()),
314      root = to_hex(inner.root.as_bytes()),
315      index = inner.index,
316      err = match &inner.err_msg {
317        Some(msg) => format!("message: {msg}"),
318        None => format!("code: {}", inner.err_code),
319      }
320    )]
321    MerklePathVerificationFailed {
322        inner: Box<MerklePathVerificationFailedInner>,
323    },
324    #[error("operation expected a binary value, but got {value}")]
325    NotBinaryValue { value: Felt },
326    #[error("if statement expected a binary value on top of the stack, but got {value}")]
327    NotBinaryValueIf { value: Felt },
328    #[error("loop condition must be a binary value, but got {value}")]
329    #[diagnostic(help(
330        "this could happen either when first entering the loop, or any subsequent iteration"
331    ))]
332    NotBinaryValueLoop { value: Felt },
333    #[error("operation expected u32 values, but got values: {values:?}")]
334    NotU32Values { values: Vec<Felt> },
335    #[error("syscall failed: procedure with root {proc_root} was not found in the kernel")]
336    SyscallTargetNotInKernel { proc_root: Word },
337    #[error("failed to execute the operation for internal reason: {0}")]
338    Internal(&'static str),
339}
340
341impl OperationError {
342    /// Wraps this error with execution context to produce an `ExecutionError`.
343    ///
344    /// This is useful when working with `ControlFlow` or other non-`Result` return types
345    /// where the `OperationResultExt::map_exec_err` extension trait cannot be used directly.
346    pub fn with_context(
347        self,
348        mast_forest: &MastForest,
349        node_id: MastNodeId,
350        host: &impl Host,
351    ) -> ExecutionError {
352        let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
353        ExecutionError::OperationError { label, source_file, err: self }
354    }
355}
356
357/// Inner data for `OperationError::MerklePathVerificationFailed`.
358///
359/// Boxed to reduce the size of `OperationError`.
360#[derive(Debug, Clone)]
361pub struct MerklePathVerificationFailedInner {
362    pub value: Word,
363    pub index: Felt,
364    pub root: Word,
365    pub err_code: Felt,
366    pub err_msg: Option<Arc<str>>,
367}
368
369// EXTENSION TRAITS
370// ================================================================================================
371
372/// Computes the label and source file for error context.
373///
374/// This function is called by the extension traits to compute source location
375/// only when an error occurs. Since errors are rare, the cost of decorator
376/// traversal is acceptable.
377fn get_label_and_source_file(
378    op_idx: Option<usize>,
379    mast_forest: &MastForest,
380    node_id: MastNodeId,
381    host: &impl Host,
382) -> (SourceSpan, Option<Arc<SourceFile>>) {
383    mast_forest
384        .get_assembly_op(node_id, op_idx)
385        .and_then(|assembly_op| assembly_op.location())
386        .map_or_else(
387            || (SourceSpan::UNKNOWN, None),
388            |location| host.get_label_and_source_file(location),
389        )
390}
391
392/// Wraps an `AdviceError` with execution context to produce an `ExecutionError`.
393///
394/// This is useful when working with `ControlFlow` or other non-`Result` return types
395/// where the extension traits cannot be used directly.
396pub fn advice_error_with_context(
397    err: AdviceError,
398    mast_forest: &MastForest,
399    node_id: MastNodeId,
400    host: &impl Host,
401) -> ExecutionError {
402    let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
403    ExecutionError::AdviceError { label, source_file, err }
404}
405
406/// Wraps an `EventError` with execution context to produce an `ExecutionError`.
407///
408/// This is useful when working with `ControlFlow` or other non-`Result` return types
409/// where an extension trait on `Result` cannot be used directly.
410pub fn event_error_with_context(
411    error: EventError,
412    mast_forest: &MastForest,
413    node_id: MastNodeId,
414    host: &impl Host,
415    event_id: EventId,
416    event_name: Option<EventName>,
417) -> ExecutionError {
418    let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
419    ExecutionError::EventError {
420        label,
421        source_file,
422        event_id,
423        event_name,
424        error,
425    }
426}
427
428/// Creates a `ProcedureNotFound` error with execution context.
429pub fn procedure_not_found_with_context(
430    root_digest: Word,
431    mast_forest: &MastForest,
432    node_id: MastNodeId,
433    host: &impl Host,
434) -> ExecutionError {
435    let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
436    ExecutionError::ProcedureNotFound { label, source_file, root_digest }
437}
438
439// CONSOLIDATED EXTENSION TRAITS (plafer's approach)
440// ================================================================================================
441//
442// Three traits organized by method signature rather than by error type:
443// 1. MapExecErr - for errors with basic context (forest, node_id, host)
444// 2. MapExecErrWithOpIdx - for errors in basic blocks that need op_idx
445// 3. MapExecErrNoCtx - for errors without any context
446
447/// Extension trait for mapping errors to `ExecutionError` with basic context.
448///
449/// Implement this for error types that can be converted to `ExecutionError` using
450/// just the MAST forest, node ID, and host for source location lookup.
451pub trait MapExecErr<T> {
452    fn map_exec_err(
453        self,
454        mast_forest: &MastForest,
455        node_id: MastNodeId,
456        host: &impl Host,
457    ) -> Result<T, ExecutionError>;
458}
459
460/// Extension trait for mapping errors to `ExecutionError` with op index context.
461///
462/// Implement this for error types that occur within basic blocks where the
463/// operation index is available for more precise source location.
464pub trait MapExecErrWithOpIdx<T> {
465    fn map_exec_err_with_op_idx(
466        self,
467        mast_forest: &MastForest,
468        node_id: MastNodeId,
469        host: &impl Host,
470        op_idx: usize,
471    ) -> Result<T, ExecutionError>;
472}
473
474/// Extension trait for mapping errors to `ExecutionError` without context.
475///
476/// Implement this for error types that may need to be converted when no
477/// error context is available (e.g., during initialization).
478pub trait MapExecErrNoCtx<T> {
479    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError>;
480}
481
482// OperationError implementations
483impl<T> MapExecErr<T> for Result<T, OperationError> {
484    #[inline(always)]
485    fn map_exec_err(
486        self,
487        mast_forest: &MastForest,
488        node_id: MastNodeId,
489        host: &impl Host,
490    ) -> Result<T, ExecutionError> {
491        match self {
492            Ok(v) => Ok(v),
493            Err(err) => {
494                let (label, source_file) =
495                    get_label_and_source_file(None, mast_forest, node_id, host);
496                Err(ExecutionError::OperationError { label, source_file, err })
497            },
498        }
499    }
500}
501
502impl<T> MapExecErrWithOpIdx<T> for Result<T, OperationError> {
503    #[inline(always)]
504    fn map_exec_err_with_op_idx(
505        self,
506        mast_forest: &MastForest,
507        node_id: MastNodeId,
508        host: &impl Host,
509        op_idx: usize,
510    ) -> Result<T, ExecutionError> {
511        match self {
512            Ok(v) => Ok(v),
513            Err(err) => {
514                let (label, source_file) =
515                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
516                Err(ExecutionError::OperationError { label, source_file, err })
517            },
518        }
519    }
520}
521
522impl<T> MapExecErrNoCtx<T> for Result<T, OperationError> {
523    #[inline(always)]
524    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError> {
525        match self {
526            Ok(v) => Ok(v),
527            Err(err) => Err(ExecutionError::OperationError {
528                label: SourceSpan::UNKNOWN,
529                source_file: None,
530                err,
531            }),
532        }
533    }
534}
535
536// AdviceError implementations
537impl<T> MapExecErr<T> for Result<T, AdviceError> {
538    #[inline(always)]
539    fn map_exec_err(
540        self,
541        mast_forest: &MastForest,
542        node_id: MastNodeId,
543        host: &impl Host,
544    ) -> Result<T, ExecutionError> {
545        match self {
546            Ok(v) => Ok(v),
547            Err(err) => Err(advice_error_with_context(err, mast_forest, node_id, host)),
548        }
549    }
550}
551
552impl<T> MapExecErrNoCtx<T> for Result<T, AdviceError> {
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::AdviceError {
558                label: SourceSpan::UNKNOWN,
559                source_file: None,
560                err,
561            }),
562        }
563    }
564}
565
566// MemoryError implementations
567impl<T> MapExecErr<T> for Result<T, MemoryError> {
568    #[inline(always)]
569    fn map_exec_err(
570        self,
571        mast_forest: &MastForest,
572        node_id: MastNodeId,
573        host: &impl Host,
574    ) -> Result<T, ExecutionError> {
575        match self {
576            Ok(v) => Ok(v),
577            Err(err) => {
578                let (label, source_file) =
579                    get_label_and_source_file(None, mast_forest, node_id, host);
580                Err(ExecutionError::MemoryError { label, source_file, err })
581            },
582        }
583    }
584}
585
586impl<T> MapExecErrWithOpIdx<T> for Result<T, MemoryError> {
587    #[inline(always)]
588    fn map_exec_err_with_op_idx(
589        self,
590        mast_forest: &MastForest,
591        node_id: MastNodeId,
592        host: &impl Host,
593        op_idx: usize,
594    ) -> Result<T, ExecutionError> {
595        match self {
596            Ok(v) => Ok(v),
597            Err(err) => {
598                let (label, source_file) =
599                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
600                Err(ExecutionError::MemoryError { label, source_file, err })
601            },
602        }
603    }
604}
605
606// SystemEventError implementations
607impl<T> MapExecErr<T> for Result<T, SystemEventError> {
608    #[inline(always)]
609    fn map_exec_err(
610        self,
611        mast_forest: &MastForest,
612        node_id: MastNodeId,
613        host: &impl Host,
614    ) -> Result<T, ExecutionError> {
615        match self {
616            Ok(v) => Ok(v),
617            Err(err) => {
618                let (label, source_file) =
619                    get_label_and_source_file(None, mast_forest, node_id, host);
620                Err(match err {
621                    SystemEventError::Advice(err) => {
622                        ExecutionError::AdviceError { label, source_file, err }
623                    },
624                    SystemEventError::Operation(err) => {
625                        ExecutionError::OperationError { label, source_file, err }
626                    },
627                    SystemEventError::Memory(err) => {
628                        ExecutionError::MemoryError { label, source_file, err }
629                    },
630                })
631            },
632        }
633    }
634}
635
636impl<T> MapExecErrWithOpIdx<T> for Result<T, SystemEventError> {
637    #[inline(always)]
638    fn map_exec_err_with_op_idx(
639        self,
640        mast_forest: &MastForest,
641        node_id: MastNodeId,
642        host: &impl Host,
643        op_idx: usize,
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(Some(op_idx), 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
666// IoError implementations
667impl<T> MapExecErrWithOpIdx<T> for Result<T, IoError> {
668    #[inline(always)]
669    fn map_exec_err_with_op_idx(
670        self,
671        mast_forest: &MastForest,
672        node_id: MastNodeId,
673        host: &impl Host,
674        op_idx: usize,
675    ) -> Result<T, ExecutionError> {
676        match self {
677            Ok(v) => Ok(v),
678            Err(err) => {
679                let (label, source_file) =
680                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
681                Err(match err {
682                    IoError::Advice(err) => ExecutionError::AdviceError { label, source_file, err },
683                    IoError::Memory(err) => ExecutionError::MemoryError { label, source_file, err },
684                    IoError::Operation(err) => {
685                        ExecutionError::OperationError { label, source_file, err }
686                    },
687                    // Execution errors are already fully formed with their own message.
688                    IoError::Execution(boxed_err) => *boxed_err,
689                })
690            },
691        }
692    }
693}
694
695// CryptoError implementations
696impl<T> MapExecErrWithOpIdx<T> for Result<T, CryptoError> {
697    #[inline(always)]
698    fn map_exec_err_with_op_idx(
699        self,
700        mast_forest: &MastForest,
701        node_id: MastNodeId,
702        host: &impl Host,
703        op_idx: usize,
704    ) -> Result<T, ExecutionError> {
705        match self {
706            Ok(v) => Ok(v),
707            Err(err) => {
708                let (label, source_file) =
709                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
710                Err(match err {
711                    CryptoError::Advice(err) => {
712                        ExecutionError::AdviceError { label, source_file, err }
713                    },
714                    CryptoError::Operation(err) => {
715                        ExecutionError::OperationError { label, source_file, err }
716                    },
717                })
718            },
719        }
720    }
721}
722
723// AceEvalError implementations
724impl<T> MapExecErrWithOpIdx<T> for Result<T, AceEvalError> {
725    #[inline(always)]
726    fn map_exec_err_with_op_idx(
727        self,
728        mast_forest: &MastForest,
729        node_id: MastNodeId,
730        host: &impl Host,
731        op_idx: usize,
732    ) -> Result<T, ExecutionError> {
733        match self {
734            Ok(v) => Ok(v),
735            Err(err) => {
736                let (label, source_file) =
737                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
738                Err(match err {
739                    AceEvalError::Ace(error) => {
740                        ExecutionError::AceChipError { label, source_file, error }
741                    },
742                    AceEvalError::Memory(err) => {
743                        ExecutionError::MemoryError { label, source_file, err }
744                    },
745                })
746            },
747        }
748    }
749}
750
751// TESTS
752// ================================================================================================
753
754#[cfg(test)]
755mod error_assertions {
756    use super::*;
757
758    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
759    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
760
761    fn _assert_execution_error_bounds(err: ExecutionError) {
762        _assert_error_is_send_sync_static(err);
763    }
764}