miden_processor/
errors.rs

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