miden_processor/
errors.rs

1// Allow unused assignments - required by miette::Diagnostic derive macro
2#![allow(unused_assignments)]
3
4use alloc::{sync::Arc, vec::Vec};
5
6use miden_air::RowIndex;
7use miden_core::{
8    EventId, EventName, Felt, QuadFelt, Word,
9    mast::{DecoratorId, MastForest, MastNodeErrorContext, MastNodeId},
10    stack::MIN_STACK_DEPTH,
11    utils::to_hex,
12};
13use miden_debug_types::{SourceFile, SourceSpan};
14use miden_utils_diagnostics::{Diagnostic, miette};
15use winter_prover::ProverError;
16
17use crate::{BaseHost, EventError, MemoryError, host::advice::AdviceError};
18// EXECUTION ERROR
19// ================================================================================================
20
21#[derive(Debug, thiserror::Error, Diagnostic)]
22pub enum ExecutionError {
23    #[error("advice provider error at clock cycle {clk}")]
24    #[diagnostic()]
25    AdviceError {
26        #[label]
27        label: SourceSpan,
28        #[source_code]
29        source_file: Option<Arc<SourceFile>>,
30        clk: RowIndex,
31        #[source]
32        #[diagnostic_source]
33        err: AdviceError,
34    },
35    #[error("external node with mast root {0} resolved to an external node")]
36    CircularExternalNode(Word),
37    #[error("exceeded the allowed number of max cycles {0}")]
38    CycleLimitExceeded(u32),
39    #[error("decorator id {decorator_id} does not exist in MAST forest")]
40    DecoratorNotFoundInForest { decorator_id: DecoratorId },
41    #[error("division by zero at clock cycle {clk}")]
42    #[diagnostic()]
43    DivideByZero {
44        #[label]
45        label: SourceSpan,
46        #[source_code]
47        source_file: Option<Arc<SourceFile>>,
48        clk: RowIndex,
49    },
50    #[error("failed to execute the dynamic code block provided by the stack with root {hex}; the block could not be found",
51      hex = .digest.to_hex()
52    )]
53    #[diagnostic()]
54    DynamicNodeNotFound {
55        #[label]
56        label: SourceSpan,
57        #[source_code]
58        source_file: Option<Arc<SourceFile>>,
59        digest: Word,
60    },
61    #[error("error during processing of event {}", match event_name {
62        Some(name) => format!("'{}' (ID: {})", name, event_id),
63        None => format!("with ID: {}", event_id),
64    })]
65    #[diagnostic()]
66    EventError {
67        #[label]
68        label: SourceSpan,
69        #[source_code]
70        source_file: Option<Arc<SourceFile>>,
71        event_id: EventId,
72        event_name: Option<EventName>,
73        #[source]
74        error: EventError,
75    },
76    #[error("attempted to add event handler for '{event}' (already registered)")]
77    DuplicateEventHandler { event: EventName },
78    #[error("attempted to add event handler for '{event}' (reserved system event)")]
79    ReservedEventNamespace { event: EventName },
80    #[error("assertion failed at clock cycle {clk} with error {}",
81      match err_msg {
82        Some(msg) => format!("message: {msg}"),
83        None => format!("code: {err_code}"),
84      }
85    )]
86    #[diagnostic()]
87    FailedAssertion {
88        #[label]
89        label: SourceSpan,
90        #[source_code]
91        source_file: Option<Arc<SourceFile>>,
92        clk: RowIndex,
93        err_code: Felt,
94        err_msg: Option<Arc<str>>,
95    },
96    #[error("failed to execute the program for internal reason: {0}")]
97    FailedToExecuteProgram(&'static str),
98    #[error("FRI domain segment value cannot exceed 3, but was {0}")]
99    InvalidFriDomainSegment(u64),
100    #[error("degree-respecting projection is inconsistent: expected {0} but was {1}")]
101    InvalidFriLayerFolding(QuadFelt, QuadFelt),
102    #[error(
103        "when returning from a call or dyncall, stack depth must be {MIN_STACK_DEPTH}, but was {depth}"
104    )]
105    #[diagnostic()]
106    InvalidStackDepthOnReturn {
107        #[label("when returning from this call site")]
108        label: SourceSpan,
109        #[source_code]
110        source_file: Option<Arc<SourceFile>>,
111        depth: usize,
112    },
113    #[error("attempted to calculate integer logarithm with zero argument at clock cycle {clk}")]
114    #[diagnostic()]
115    LogArgumentZero {
116        #[label]
117        label: SourceSpan,
118        #[source_code]
119        source_file: Option<Arc<SourceFile>>,
120        clk: RowIndex,
121    },
122    #[error("malformed signature key: {key_type}")]
123    #[diagnostic(help("the secret key associated with the provided public key is malformed"))]
124    MalformedSignatureKey {
125        #[label]
126        label: SourceSpan,
127        #[source_code]
128        source_file: Option<Arc<SourceFile>>,
129        key_type: &'static str,
130    },
131    #[error(
132        "MAST forest in host indexed by procedure root {root_digest} doesn't contain that root"
133    )]
134    MalformedMastForestInHost {
135        #[label]
136        label: SourceSpan,
137        #[source_code]
138        source_file: Option<Arc<SourceFile>>,
139        root_digest: Word,
140    },
141    #[error("node id {node_id} does not exist in MAST forest")]
142    MastNodeNotFoundInForest { node_id: MastNodeId },
143    #[error(transparent)]
144    #[diagnostic(transparent)]
145    MemoryError(MemoryError),
146    #[error("no MAST forest contains the procedure with root digest {root_digest}")]
147    NoMastForestWithProcedure {
148        #[label]
149        label: SourceSpan,
150        #[source_code]
151        source_file: Option<Arc<SourceFile>>,
152        root_digest: Word,
153    },
154    #[error("merkle path verification failed for value {value} at index {index} in the Merkle tree with root {root} (error {err})",
155      value = to_hex(value.as_bytes()),
156      root = to_hex(root.as_bytes()),
157      err = match err_msg {
158        Some(msg) => format!("message: {msg}"),
159        None => format!("code: {err_code}"),
160      }
161    )]
162    MerklePathVerificationFailed {
163        #[label]
164        label: SourceSpan,
165        #[source_code]
166        source_file: Option<Arc<SourceFile>>,
167        value: Word,
168        index: Felt,
169        root: Word,
170        err_code: Felt,
171        err_msg: Option<Arc<str>>,
172    },
173    #[error("if statement expected a binary value on top of the stack, but got {value}")]
174    #[diagnostic()]
175    NotBinaryValueIf {
176        #[label]
177        label: SourceSpan,
178        #[source_code]
179        source_file: Option<Arc<SourceFile>>,
180        value: Felt,
181    },
182    #[error("operation expected a binary value, but got {value}")]
183    #[diagnostic()]
184    NotBinaryValueOp {
185        #[label]
186        label: SourceSpan,
187        #[source_code]
188        source_file: Option<Arc<SourceFile>>,
189        value: Felt,
190    },
191    #[error("loop condition must be a binary value, but got {value}")]
192    #[diagnostic(help(
193        "this could happen either when first entering the loop, or any subsequent iteration"
194    ))]
195    NotBinaryValueLoop {
196        #[label]
197        label: SourceSpan,
198        #[source_code]
199        source_file: Option<Arc<SourceFile>>,
200        value: Felt,
201    },
202    #[error("operation expected u32 values, but got values: {values:?} (error code: {err_code})")]
203    NotU32Values {
204        #[label]
205        label: SourceSpan,
206        #[source_code]
207        source_file: Option<Arc<SourceFile>>,
208        values: Vec<Felt>,
209        err_code: Felt,
210    },
211    #[error(
212        "Operand stack input is {input} but it is expected to fit in a u32 at clock cycle {clk}"
213    )]
214    #[diagnostic()]
215    NotU32StackValue {
216        #[label]
217        label: SourceSpan,
218        #[source_code]
219        source_file: Option<Arc<SourceFile>>,
220        clk: RowIndex,
221        input: u64,
222    },
223    #[error("stack should have at most {MIN_STACK_DEPTH} elements at the end of program execution, but had {} elements", MIN_STACK_DEPTH + .0)]
224    OutputStackOverflow(usize),
225    #[error("a program has already been executed in this process")]
226    ProgramAlreadyExecuted,
227    #[error("proof generation failed")]
228    ProverError(#[source] ProverError),
229    #[error("smt node {node_hex} not found", node_hex = to_hex(node.as_bytes()))]
230    SmtNodeNotFound {
231        #[label]
232        label: SourceSpan,
233        #[source_code]
234        source_file: Option<Arc<SourceFile>>,
235        node: Word,
236    },
237    #[error("expected pre-image length of node {node_hex} to be a multiple of 8 but was {preimage_len}",
238      node_hex = to_hex(node.as_bytes()),
239    )]
240    SmtNodePreImageNotValid {
241        #[label]
242        label: SourceSpan,
243        #[source_code]
244        source_file: Option<Arc<SourceFile>>,
245        node: Word,
246        preimage_len: usize,
247    },
248    #[error("syscall failed: procedure with root {hex} was not found in the kernel",
249      hex = to_hex(proc_root.as_bytes())
250    )]
251    SyscallTargetNotInKernel {
252        #[label]
253        label: SourceSpan,
254        #[source_code]
255        source_file: Option<Arc<SourceFile>>,
256        proc_root: Word,
257    },
258    #[error("failed to execute arithmetic circuit evaluation operation: {error}")]
259    #[diagnostic()]
260    AceChipError {
261        #[label("this call failed")]
262        label: SourceSpan,
263        #[source_code]
264        source_file: Option<Arc<SourceFile>>,
265        error: AceError,
266    },
267    #[error("execution yielded unexpected precompiles")]
268    UnexpectedPrecompiles,
269}
270
271impl ExecutionError {
272    pub fn advice_error(
273        err: AdviceError,
274        clk: RowIndex,
275        err_ctx: &impl ErrorContext,
276    ) -> ExecutionError {
277        let (label, source_file) = err_ctx.label_and_source_file();
278        ExecutionError::AdviceError { label, source_file, err, clk }
279    }
280
281    pub fn divide_by_zero(clk: RowIndex, err_ctx: &impl ErrorContext) -> Self {
282        let (label, source_file) = err_ctx.label_and_source_file();
283        Self::DivideByZero { clk, label, source_file }
284    }
285
286    pub fn input_not_u32(clk: RowIndex, input: u64, err_ctx: &impl ErrorContext) -> Self {
287        let (label, source_file) = err_ctx.label_and_source_file();
288        Self::NotU32StackValue { clk, input, label, source_file }
289    }
290
291    pub fn dynamic_node_not_found(digest: Word, err_ctx: &impl ErrorContext) -> Self {
292        let (label, source_file) = err_ctx.label_and_source_file();
293
294        Self::DynamicNodeNotFound { label, source_file, digest }
295    }
296
297    pub fn event_error(
298        error: EventError,
299        event_id: EventId,
300        event_name: Option<EventName>,
301        err_ctx: &impl ErrorContext,
302    ) -> Self {
303        let (label, source_file) = err_ctx.label_and_source_file();
304
305        Self::EventError {
306            label,
307            source_file,
308            event_id,
309            event_name,
310            error,
311        }
312    }
313
314    pub fn failed_assertion(
315        clk: RowIndex,
316        err_code: Felt,
317        err_msg: Option<Arc<str>>,
318        err_ctx: &impl ErrorContext,
319    ) -> Self {
320        let (label, source_file) = err_ctx.label_and_source_file();
321
322        Self::FailedAssertion {
323            label,
324            source_file,
325            clk,
326            err_code,
327            err_msg,
328        }
329    }
330
331    pub fn invalid_stack_depth_on_return(depth: usize, err_ctx: &impl ErrorContext) -> Self {
332        let (label, source_file) = err_ctx.label_and_source_file();
333        Self::InvalidStackDepthOnReturn { label, source_file, depth }
334    }
335
336    pub fn log_argument_zero(clk: RowIndex, err_ctx: &impl ErrorContext) -> Self {
337        let (label, source_file) = err_ctx.label_and_source_file();
338        Self::LogArgumentZero { label, source_file, clk }
339    }
340
341    pub fn malfored_mast_forest_in_host(root_digest: Word, err_ctx: &impl ErrorContext) -> Self {
342        let (label, source_file) = err_ctx.label_and_source_file();
343        Self::MalformedMastForestInHost { label, source_file, root_digest }
344    }
345
346    pub fn malformed_signature_key(key_type: &'static str, err_ctx: &impl ErrorContext) -> Self {
347        let (label, source_file) = err_ctx.label_and_source_file();
348        Self::MalformedSignatureKey { label, source_file, key_type }
349    }
350
351    pub fn merkle_path_verification_failed(
352        value: Word,
353        index: Felt,
354        root: Word,
355        err_code: Felt,
356        err_msg: Option<Arc<str>>,
357        err_ctx: &impl ErrorContext,
358    ) -> Self {
359        let (label, source_file) = err_ctx.label_and_source_file();
360
361        Self::MerklePathVerificationFailed {
362            label,
363            source_file,
364            value,
365            index,
366            root,
367            err_code,
368            err_msg,
369        }
370    }
371
372    pub fn no_mast_forest_with_procedure(root_digest: Word, err_ctx: &impl ErrorContext) -> Self {
373        let (label, source_file) = err_ctx.label_and_source_file();
374        Self::NoMastForestWithProcedure { label, source_file, root_digest }
375    }
376
377    pub fn not_binary_value_if(value: Felt, err_ctx: &impl ErrorContext) -> Self {
378        let (label, source_file) = err_ctx.label_and_source_file();
379        Self::NotBinaryValueIf { label, source_file, value }
380    }
381
382    pub fn not_binary_value_op(value: Felt, err_ctx: &impl ErrorContext) -> Self {
383        let (label, source_file) = err_ctx.label_and_source_file();
384        Self::NotBinaryValueOp { label, source_file, value }
385    }
386
387    pub fn not_binary_value_loop(value: Felt, err_ctx: &impl ErrorContext) -> Self {
388        let (label, source_file) = err_ctx.label_and_source_file();
389        Self::NotBinaryValueLoop { label, source_file, value }
390    }
391
392    pub fn not_u32_value(value: Felt, err_code: Felt, err_ctx: &impl ErrorContext) -> Self {
393        let (label, source_file) = err_ctx.label_and_source_file();
394        Self::NotU32Values {
395            label,
396            source_file,
397            values: vec![value],
398            err_code,
399        }
400    }
401
402    pub fn not_u32_values(values: Vec<Felt>, err_code: Felt, err_ctx: &impl ErrorContext) -> Self {
403        let (label, source_file) = err_ctx.label_and_source_file();
404        Self::NotU32Values { label, source_file, values, err_code }
405    }
406
407    pub fn smt_node_not_found(node: Word, err_ctx: &impl ErrorContext) -> Self {
408        let (label, source_file) = err_ctx.label_and_source_file();
409        Self::SmtNodeNotFound { label, source_file, node }
410    }
411
412    pub fn smt_node_preimage_not_valid(
413        node: Word,
414        preimage_len: usize,
415        err_ctx: &impl ErrorContext,
416    ) -> Self {
417        let (label, source_file) = err_ctx.label_and_source_file();
418        Self::SmtNodePreImageNotValid { label, source_file, node, preimage_len }
419    }
420
421    pub fn syscall_target_not_in_kernel(proc_root: Word, err_ctx: &impl ErrorContext) -> Self {
422        let (label, source_file) = err_ctx.label_and_source_file();
423        Self::SyscallTargetNotInKernel { label, source_file, proc_root }
424    }
425
426    pub fn failed_arithmetic_evaluation(err_ctx: &impl ErrorContext, error: AceError) -> Self {
427        let (label, source_file) = err_ctx.label_and_source_file();
428        Self::AceChipError { label, source_file, error }
429    }
430}
431
432impl AsRef<dyn Diagnostic> for ExecutionError {
433    fn as_ref(&self) -> &(dyn Diagnostic + 'static) {
434        self
435    }
436}
437
438// ACE ERROR
439// ================================================================================================
440
441#[derive(Debug, thiserror::Error)]
442pub enum AceError {
443    #[error("num of variables should be word aligned and non-zero but was {0}")]
444    NumVarIsNotWordAlignedOrIsEmpty(u64),
445    #[error("num of evaluation gates should be word aligned and non-zero but was {0}")]
446    NumEvalIsNotWordAlignedOrIsEmpty(u64),
447    #[error("circuit does not evaluate to zero")]
448    CircuitNotEvaluateZero,
449    #[error("failed to read from memory")]
450    FailedMemoryRead,
451    #[error("failed to decode instruction")]
452    FailedDecodeInstruction,
453    #[error("failed to read from the wiring bus")]
454    FailedWireBusRead,
455    #[error("num of wires must be less than 2^30 but was {0}")]
456    TooManyWires(u64),
457}
458
459// ERROR CONTEXT
460// ===============================================================================================
461
462/// Constructs an error context for the given node in the MAST forest.
463///
464/// When the `no_err_ctx` feature is disabled, this macro returns a proper error context; otherwise,
465/// it returns `()`. That is, this macro is designed to be zero-cost when the `no_err_ctx` feature
466/// is enabled.
467///
468/// Usage:
469/// - `err_ctx!(mast_forest, node, source_manager)` - creates basic error context
470/// - `err_ctx!(mast_forest, node, source_manager, op_idx)` - creates error context with operation
471///   index
472#[cfg(not(feature = "no_err_ctx"))]
473#[macro_export]
474macro_rules! err_ctx {
475    ($mast_forest:expr, $node:expr, $host:expr) => {
476        $crate::errors::ErrorContextImpl::new($mast_forest, $node, $host)
477    };
478    ($mast_forest:expr, $node:expr, $host:expr, $op_idx:expr) => {
479        $crate::errors::ErrorContextImpl::new_with_op_idx($mast_forest, $node, $host, $op_idx)
480    };
481}
482
483/// Constructs an error context for the given node in the MAST forest.
484///
485/// When the `no_err_ctx` feature is disabled, this macro returns a proper error context; otherwise,
486/// it returns `()`. That is, this macro is designed to be zero-cost when the `no_err_ctx` feature
487/// is enabled.
488///
489/// Usage:
490/// - `err_ctx!(mast_forest, node, source_manager)` - creates basic error context
491/// - `err_ctx!(mast_forest, node, source_manager, op_idx)` - creates error context with operation
492///   index
493#[cfg(feature = "no_err_ctx")]
494#[macro_export]
495macro_rules! err_ctx {
496    ($mast_forest:expr, $node:expr, $host:expr) => {{ () }};
497    ($mast_forest:expr, $node:expr, $host:expr, $op_idx:expr) => {{ () }};
498}
499
500/// Trait defining the interface for error context providers.
501///
502/// This trait contains the same methods as `ErrorContext` to provide a common
503/// interface for error context functionality.
504pub trait ErrorContext {
505    /// Returns the label and source file associated with the error context, if any.
506    ///
507    /// Note that `SourceSpan::UNKNOWN` will be returned to indicate an empty span.
508    fn label_and_source_file(&self) -> (SourceSpan, Option<Arc<SourceFile>>);
509}
510
511/// Context information to be used when reporting errors.
512pub struct ErrorContextImpl {
513    label: SourceSpan,
514    source_file: Option<Arc<SourceFile>>,
515}
516
517impl ErrorContextImpl {
518    #[allow(dead_code)]
519    pub fn new(
520        mast_forest: &MastForest,
521        node: &impl MastNodeErrorContext,
522        host: &impl BaseHost,
523    ) -> Self {
524        let (label, source_file) =
525            Self::precalc_label_and_source_file(None, mast_forest, node, host);
526        Self { label, source_file }
527    }
528
529    #[allow(dead_code)]
530    pub fn new_with_op_idx(
531        mast_forest: &MastForest,
532        node: &impl MastNodeErrorContext,
533        host: &impl BaseHost,
534        op_idx: usize,
535    ) -> Self {
536        let op_idx = op_idx.into();
537        let (label, source_file) =
538            Self::precalc_label_and_source_file(op_idx, mast_forest, node, host);
539        Self { label, source_file }
540    }
541
542    fn precalc_label_and_source_file(
543        op_idx: Option<usize>,
544        mast_forest: &MastForest,
545        node: &impl MastNodeErrorContext,
546        host: &impl BaseHost,
547    ) -> (SourceSpan, Option<Arc<SourceFile>>) {
548        node.get_assembly_op(mast_forest, op_idx)
549            .and_then(|assembly_op| assembly_op.location())
550            .map_or_else(
551                || (SourceSpan::UNKNOWN, None),
552                |location| host.get_label_and_source_file(location),
553            )
554    }
555}
556
557impl ErrorContext for ErrorContextImpl {
558    fn label_and_source_file(&self) -> (SourceSpan, Option<Arc<SourceFile>>) {
559        (self.label, self.source_file.clone())
560    }
561}
562
563impl ErrorContext for () {
564    fn label_and_source_file(&self) -> (SourceSpan, Option<Arc<SourceFile>>) {
565        (SourceSpan::UNKNOWN, None)
566    }
567}
568
569// TESTS
570// ================================================================================================
571
572#[cfg(test)]
573mod error_assertions {
574    use super::*;
575
576    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
577    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
578
579    fn _assert_execution_error_bounds(err: ExecutionError) {
580        _assert_error_is_send_sync_static(err);
581    }
582}