miden_processor/
errors.rs

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