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