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