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}
51
52#[derive(Debug, Serialize, ThisError)]
53#[serde(tag = "type", content = "data")]
54#[non_exhaustive]
55pub enum MethodResolutionError {
56    #[error("method {name:?} has invalid signature: expected no arguments and no return value")]
57    InvalidSignature { name: String },
58    #[error("method {name:?} not found")]
59    MethodNotFound { name: String },
60}
61
62#[derive(Debug, Serialize, ThisError)]
63#[serde(tag = "type", content = "data")]
64#[non_exhaustive]
65pub enum HostError {
66    #[error("invalid register id: {id}")]
67    InvalidRegisterId { id: u64 },
68    #[error("invalid memory access")]
69    InvalidMemoryAccess,
70    #[error(
71        "{} panicked: {message}{}",
72        match .context {
73            PanicContext::Guest => "guest",
74            PanicContext::Host => "host",
75        },
76        match .location {
77            Location::Unknown => String::new(),
78            Location::At { file, line, column } => format!(" at {file}:{line}:{column}"),
79        }
80    )]
81    Panic {
82        context: PanicContext,
83        message: String,
84        #[serde(skip_serializing_if = "Location::is_unknown")]
85        location: Location,
86    },
87    #[error("invalid UTF-8 string")]
88    BadUTF8,
89    #[error("deserialization error")]
90    DeserializationError,
91    #[error("integer overflow")]
92    IntegerOverflow,
93    #[error("key length overflow")]
94    KeyLengthOverflow,
95    #[error("value length overflow")]
96    ValueLengthOverflow,
97    #[error("logs overflow")]
98    LogsOverflow,
99    #[error("events overflow")]
100    EventsOverflow,
101    #[error("event kind size overflow")]
102    EventKindSizeOverflow,
103    #[error("event data size overflow")]
104    EventDataSizeOverflow,
105    #[error("blob operations not supported (NodeClient not available)")]
106    BlobsNotSupported,
107    #[error("invalid blob handle")]
108    InvalidBlobHandle,
109    #[error("too many blob handles (max: {max})")]
110    TooManyBlobHandles { max: u64 },
111    #[error("blob buffer too large (size: {size}, max: {max})")]
112    BlobBufferTooLarge { size: u64, max: u64 },
113    #[error("total blob memory exceeded (current: {current}, max: {max})")]
114    TotalBlobMemoryExceeded { current: u64, max: u64 },
115    #[error("blob write too large (size: {size}, max: {max})")]
116    BlobWriteTooLarge { size: u64, max: u64 },
117    #[error("context does not have permission to access this blob handle")]
118    BlobContextMismatch,
119    #[error("too many blob handles open")]
120    BlobHandleLimitExceeded,
121    #[error("total blob memory usage exceeds limit")]
122    BlobMemoryLimitExceeded,
123}
124
125#[derive(Copy, Clone, Debug, Serialize)]
126#[expect(
127    clippy::exhaustive_enums,
128    reason = "There are no more possible variants"
129)]
130pub enum PanicContext {
131    Guest,
132    Host,
133}
134
135#[derive(Copy, Clone, Debug, Serialize, ThisError)]
136#[non_exhaustive]
137pub enum WasmTrap {
138    #[error("stack overflow")]
139    StackOverflow,
140    #[error("memory out of bounds")]
141    MemoryOutOfBounds,
142    #[error("heap misaligned")]
143    HeapMisaligned,
144    #[error("table access out of bounds")]
145    TableAccessOutOfBounds,
146    #[error("indirect call to null")]
147    IndirectCallToNull,
148    #[error("bad signature")]
149    BadSignature,
150    #[error("illegal arithmetic operation")]
151    IllegalArithmetic,
152    #[error("unreachable code reached")]
153    Unreachable,
154    #[error("unaligned atomic operation")]
155    UnalignedAtomic,
156    #[error("indeterminate trap")]
157    Indeterminate,
158}
159
160#[derive(Debug, Serialize)]
161#[serde(untagged)]
162#[non_exhaustive]
163pub enum Location {
164    At {
165        file: String,
166        line: u32,
167        column: u32,
168    },
169    Unknown,
170}
171
172impl Location {
173    const fn is_unknown(&self) -> bool {
174        matches!(self, Self::Unknown)
175    }
176}
177
178impl From<&PanicLocation<'_>> for Location {
179    fn from(location: &PanicLocation<'_>) -> Self {
180        Self::At {
181            file: location.file().to_owned(),
182            line: location.line(),
183            column: location.column(),
184        }
185    }
186}
187
188impl From<ExportError> for FunctionCallError {
189    fn from(err: ExportError) -> Self {
190        match err {
191            ExportError::Missing(name) => {
192                Self::MethodResolutionError(MethodResolutionError::MethodNotFound { name })
193            }
194            ExportError::IncompatibleType => unreachable!(),
195        }
196    }
197}
198
199// TODO: We should change this to use TryFrom instead of panicking in a From.
200#[expect(
201    clippy::fallible_impl_from,
202    reason = "TODO: This needs to be refactored"
203)]
204impl From<InstantiationError> for FunctionCallError {
205    fn from(err: InstantiationError) -> Self {
206        match err {
207            InstantiationError::Link(err) => err.into(),
208            InstantiationError::Start(err) => err.into(),
209            InstantiationError::CpuFeature(err) => {
210                panic!("host CPU does not support a required feature: {err}")
211            }
212            InstantiationError::DifferentStores => {
213                panic!("one of the imports is incompatible with this execution instance")
214            }
215            InstantiationError::DifferentArchOS => {
216                panic!("the module was compiled for a different architecture or operating system")
217            }
218        }
219    }
220}
221
222impl From<RuntimeError> for FunctionCallError {
223    fn from(err: RuntimeError) -> Self {
224        match err.to_trap() {
225            Some(TrapCode::StackOverflow) => Self::WasmTrap(WasmTrap::StackOverflow),
226            Some(TrapCode::HeapAccessOutOfBounds | TrapCode::TableAccessOutOfBounds) => {
227                Self::WasmTrap(WasmTrap::MemoryOutOfBounds)
228            }
229            Some(TrapCode::HeapMisaligned) => Self::WasmTrap(WasmTrap::HeapMisaligned),
230            Some(TrapCode::IndirectCallToNull) => Self::WasmTrap(WasmTrap::IndirectCallToNull),
231            Some(TrapCode::BadSignature) => Self::WasmTrap(WasmTrap::BadSignature),
232            Some(
233                TrapCode::IntegerOverflow
234                | TrapCode::IntegerDivisionByZero
235                | TrapCode::BadConversionToInteger,
236            ) => Self::WasmTrap(WasmTrap::IllegalArithmetic),
237            Some(TrapCode::UnreachableCodeReached) => Self::WasmTrap(WasmTrap::Unreachable),
238            Some(TrapCode::UnalignedAtomic) => Self::WasmTrap(WasmTrap::UnalignedAtomic),
239            None => Self::WasmTrap(WasmTrap::Indeterminate),
240        }
241    }
242}