miden_processor/
errors.rs

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