Skip to main content

calimero_runtime/
errors.rs

1#[cfg(test)]
2#[path = "tests/errors.rs"]
3mod tests;
4
5use core::panic::Location as PanicLocation;
6
7use serde::Serialize;
8use thiserror::Error as ThisError;
9use wasmer::{ExportError, InstantiationError, LinkError, RuntimeError};
10use wasmer_types::{CompileError, TrapCode};
11
12#[derive(Debug, ThisError)]
13#[non_exhaustive]
14pub enum VMRuntimeError {
15    #[error(transparent)]
16    StorageError(StorageError),
17
18    #[error(transparent)]
19    HostError(HostError),
20}
21
22#[derive(Copy, Clone, Debug, ThisError)]
23#[non_exhaustive]
24pub enum StorageError {}
25
26#[derive(Debug, Serialize, ThisError)]
27#[serde(tag = "type", content = "data")]
28#[non_exhaustive]
29pub enum FunctionCallError {
30    #[error("compilation error: {}", .source)]
31    CompilationError {
32        #[from]
33        #[serde(skip)]
34        source: CompileError,
35    },
36    #[error("link error: {}", .source)]
37    LinkError {
38        #[from]
39        #[serde(skip)]
40        source: LinkError,
41    },
42    #[error(transparent)]
43    MethodResolutionError(MethodResolutionError),
44    #[error(transparent)]
45    WasmTrap(WasmTrap),
46    #[error(transparent)]
47    HostError(HostError),
48    #[error("the method call returned an error: {0:?}")]
49    ExecutionError(Vec<u8>),
50    #[error("module size limit (max_module_size) exceeded: {size} bytes > {max} bytes limit")]
51    ModuleSizeLimitExceeded { size: u64, max: u64 },
52}
53
54#[derive(Debug, Serialize, ThisError)]
55#[serde(tag = "type", content = "data")]
56#[non_exhaustive]
57pub enum MethodResolutionError {
58    #[error("method {name:?} has invalid signature: expected no arguments and no return value")]
59    InvalidSignature { name: String },
60    #[error("method {name:?} not found")]
61    MethodNotFound { name: String },
62    #[error("method name too long: {length} bytes (max: {max})")]
63    MethodNameTooLong {
64        name: String,
65        length: usize,
66        max: u64,
67    },
68    #[error("method name contains invalid character at position {position}: {character:?}")]
69    InvalidMethodNameCharacter {
70        name: String,
71        character: char,
72        position: usize,
73    },
74    #[error("method name is empty")]
75    EmptyMethodName,
76}
77
78#[derive(Debug, Serialize, ThisError)]
79#[serde(tag = "type", content = "data")]
80#[non_exhaustive]
81pub enum HostError {
82    #[error("invalid register id: {id}")]
83    InvalidRegisterId { id: u64 },
84    #[error("invalid memory access")]
85    InvalidMemoryAccess,
86    #[error(
87        "{} panicked: {message}{}",
88        match .context {
89            PanicContext::Guest => "guest",
90            PanicContext::Host => "host",
91        },
92        match .location {
93            Location::Unknown => String::new(),
94            Location::At { file, line, column } => format!(" at {file}:{line}:{column}"),
95        }
96    )]
97    Panic {
98        context: PanicContext,
99        message: String,
100        #[serde(skip_serializing_if = "Location::is_unknown")]
101        location: Location,
102    },
103    #[error("invalid UTF-8 string")]
104    BadUTF8,
105    #[error("deserialization error")]
106    DeserializationError,
107    #[error("serialization error")]
108    SerializationError,
109    #[error("integer overflow")]
110    IntegerOverflow,
111    #[error("key length overflow")]
112    KeyLengthOverflow,
113    #[error("value length overflow")]
114    ValueLengthOverflow,
115    #[error("log size overflow")]
116    LogLengthOverflow,
117    #[error("logs overflow")]
118    LogsOverflow,
119    #[error("events overflow")]
120    EventsOverflow,
121    #[error("event kind size overflow")]
122    EventKindSizeOverflow,
123    #[error("event data size overflow")]
124    EventDataSizeOverflow,
125    #[error("xcalls overflow")]
126    XCallsOverflow,
127    #[error("xcall function size overflow")]
128    XCallFunctionSizeOverflow,
129    #[error("xcall params size overflow")]
130    XCallParamsSizeOverflow,
131    #[error("blob operations not supported (NodeClient not available)")]
132    BlobsNotSupported,
133    #[error("invalid blob handle")]
134    InvalidBlobHandle,
135    #[error("too many blob handles (max: {max})")]
136    TooManyBlobHandles { max: u64 },
137    #[error("blob buffer too large (size: {size}, max: {max})")]
138    BlobBufferTooLarge { size: u64, max: u64 },
139    #[error("total blob memory exceeded (current: {current}, max: {max})")]
140    TotalBlobMemoryExceeded { current: u64, max: u64 },
141    #[error("blob write too large (size: {size}, max: {max})")]
142    BlobWriteTooLarge { size: u64, max: u64 },
143    #[error("context does not have permission to access this blob handle")]
144    BlobContextMismatch,
145    #[error("too many blob handles open")]
146    BlobHandleLimitExceeded,
147    #[error("total blob memory usage exceeds limit")]
148    BlobMemoryLimitExceeded,
149    #[error("incorrect ed25519 public key")]
150    Ed25519IncorrectPublicKey,
151    #[error("alias already exists: {0}")]
152    AliasAlreadyExists(String),
153    #[error("alias too long: {0}")]
154    AliasTooLong(usize),
155    #[error("node client is not available")]
156    NodeClientNotAvailable,
157}
158
159#[derive(Copy, Clone, Debug, Serialize)]
160#[expect(
161    clippy::exhaustive_enums,
162    reason = "There are no more possible variants"
163)]
164pub enum PanicContext {
165    Guest,
166    Host,
167}
168
169#[derive(Copy, Clone, Debug, Serialize, ThisError)]
170#[non_exhaustive]
171pub enum WasmTrap {
172    #[error("stack overflow")]
173    StackOverflow,
174    #[error("memory out of bounds")]
175    MemoryOutOfBounds,
176    #[error("heap misaligned")]
177    HeapMisaligned,
178    #[error("table access out of bounds")]
179    TableAccessOutOfBounds,
180    #[error("indirect call to null")]
181    IndirectCallToNull,
182    #[error("bad signature")]
183    BadSignature,
184    #[error("illegal arithmetic operation")]
185    IllegalArithmetic,
186    #[error("unreachable code reached")]
187    Unreachable,
188    #[error("unaligned atomic operation")]
189    UnalignedAtomic,
190    #[error("indeterminate trap")]
191    Indeterminate,
192}
193
194#[derive(Debug, Serialize)]
195#[serde(untagged)]
196#[non_exhaustive]
197pub enum Location {
198    At {
199        file: String,
200        line: u32,
201        column: u32,
202    },
203    Unknown,
204}
205
206impl Location {
207    const fn is_unknown(&self) -> bool {
208        matches!(self, Self::Unknown)
209    }
210}
211
212impl From<&PanicLocation<'_>> for Location {
213    fn from(location: &PanicLocation<'_>) -> Self {
214        Self::At {
215            file: location.file().to_owned(),
216            line: location.line(),
217            column: location.column(),
218        }
219    }
220}
221
222impl From<ExportError> for FunctionCallError {
223    fn from(err: ExportError) -> Self {
224        match err {
225            ExportError::Missing(name) => {
226                Self::MethodResolutionError(MethodResolutionError::MethodNotFound { name })
227            }
228            ExportError::IncompatibleType => unreachable!(),
229        }
230    }
231}
232
233// TODO: We should change this to use TryFrom instead of panicking in a From.
234#[expect(
235    clippy::fallible_impl_from,
236    reason = "TODO: This needs to be refactored"
237)]
238impl From<InstantiationError> for FunctionCallError {
239    fn from(err: InstantiationError) -> Self {
240        match err {
241            InstantiationError::Link(err) => err.into(),
242            InstantiationError::Start(err) => err.into(),
243            InstantiationError::CpuFeature(err) => {
244                panic!("host CPU does not support a required feature: {err}")
245            }
246            InstantiationError::DifferentStores => {
247                panic!("one of the imports is incompatible with this execution instance")
248            }
249            InstantiationError::DifferentArchOS => {
250                panic!("the module was compiled for a different architecture or operating system")
251            }
252        }
253    }
254}
255
256impl From<RuntimeError> for FunctionCallError {
257    fn from(err: RuntimeError) -> Self {
258        match err.to_trap() {
259            Some(TrapCode::StackOverflow) => Self::WasmTrap(WasmTrap::StackOverflow),
260            Some(TrapCode::HeapAccessOutOfBounds | TrapCode::TableAccessOutOfBounds) => {
261                Self::WasmTrap(WasmTrap::MemoryOutOfBounds)
262            }
263            Some(TrapCode::HeapMisaligned) => Self::WasmTrap(WasmTrap::HeapMisaligned),
264            Some(TrapCode::IndirectCallToNull) => Self::WasmTrap(WasmTrap::IndirectCallToNull),
265            Some(TrapCode::BadSignature) => Self::WasmTrap(WasmTrap::BadSignature),
266            Some(
267                TrapCode::IntegerOverflow
268                | TrapCode::IntegerDivisionByZero
269                | TrapCode::BadConversionToInteger,
270            ) => Self::WasmTrap(WasmTrap::IllegalArithmetic),
271            Some(TrapCode::UnreachableCodeReached) => Self::WasmTrap(WasmTrap::Unreachable),
272            Some(TrapCode::UnalignedAtomic) => Self::WasmTrap(WasmTrap::UnalignedAtomic),
273            Some(TrapCode::UncaughtException) => Self::WasmTrap(WasmTrap::Indeterminate),
274            None => Self::WasmTrap(WasmTrap::Indeterminate),
275        }
276    }
277}