wasmer/
error.rs

1use std::sync::Arc;
2use thiserror::Error;
3use wasmer_types::{FrameInfo, ImportError, TrapCode};
4
5use crate::{AsStoreMut, AsStoreRef, BackendTrap as Trap, Exception, Value};
6
7// This type used to be exported from this module, so re-export for
8// backwards compatibility.
9pub use wasmer_types::error::AtomicsError;
10
11/// The WebAssembly.LinkError object indicates an error during
12/// module instantiation (besides traps from the start function).
13///
14/// This is based on the [link error][link-error] API.
15///
16/// [link-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError
17#[derive(Debug, Clone)]
18#[cfg_attr(feature = "std", derive(Error))]
19#[cfg_attr(feature = "std", error("Link error: {0}"))]
20pub enum LinkError {
21    /// An error occurred when checking the import types.
22    #[cfg_attr(feature = "std", error("Error while importing {0:?}.{1:?}: {2}"))]
23    Import(String, String, ImportError),
24
25    /// A trap occurred during linking.
26    #[cfg_attr(feature = "std", error("RuntimeError occurred during linking: {0}"))]
27    Trap(#[cfg_attr(feature = "std", source)] RuntimeError),
28    /// Insufficient resources available for linking.
29    #[cfg_attr(feature = "std", error("Insufficient resources: {0}"))]
30    Resource(String),
31}
32
33/// An error while instantiating a module.
34///
35/// This is not a common WebAssembly error, however
36/// we need to differentiate from a `LinkError` (an error
37/// that happens while linking, on instantiation), a
38/// Trap that occurs when calling the WebAssembly module
39/// start function, and an error when initializing the user's
40/// host environments.
41#[derive(Debug, Clone)]
42#[cfg_attr(feature = "std", derive(Error))]
43pub enum InstantiationError {
44    /// A linking occurred during instantiation.
45    #[cfg_attr(feature = "std", error(transparent))]
46    Link(LinkError),
47
48    /// A runtime error occurred while invoking the start function
49    #[cfg_attr(feature = "std", error(transparent))]
50    Start(RuntimeError),
51
52    /// The module was compiled with a CPU feature that is not available on
53    /// the current host.
54    #[cfg_attr(feature = "std", error("missing required CPU features: {0:?}"))]
55    CpuFeature(String),
56
57    /// Import from a different [`Store`][super::Store].
58    /// This error occurs when an import from a different store is used.
59    #[cfg_attr(feature = "std", error("cannot mix imports from different stores"))]
60    DifferentStores,
61
62    /// Import from a different Store.
63    /// This error occurs when an import from a different store is used.
64    #[cfg_attr(feature = "std", error("incorrect OS or architecture"))]
65    DifferentArchOS,
66}
67
68/// A struct representing an aborted instruction execution, with a message
69/// indicating the cause.
70#[derive(Clone)]
71pub struct RuntimeError {
72    pub(crate) inner: Arc<RuntimeErrorInner>,
73}
74
75#[derive(Debug)]
76struct RuntimeStringError {
77    details: String,
78}
79
80impl RuntimeStringError {
81    fn new(msg: String) -> Self {
82        Self { details: msg }
83    }
84}
85
86impl std::fmt::Display for RuntimeStringError {
87    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
88        write!(f, "{}", self.details)
89    }
90}
91
92impl std::error::Error for RuntimeStringError {
93    fn description(&self) -> &str {
94        &self.details
95    }
96}
97
98pub(crate) struct RuntimeErrorInner {
99    /// The source error
100    pub(crate) source: Trap,
101    /// The trap code (if any)
102    trap_code: Option<TrapCode>,
103    /// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`).
104    wasm_trace: Vec<FrameInfo>,
105}
106
107impl RuntimeError {
108    /// Creates a new generic `RuntimeError` with the given `message`.
109    ///
110    /// # Example
111    /// ```
112    /// let trap = wasmer::RuntimeError::new("unexpected error");
113    /// assert_eq!("unexpected error", trap.message());
114    /// ```
115    pub fn new<I: Into<String>>(message: I) -> Self {
116        let msg = message.into();
117        let source = RuntimeStringError::new(msg);
118        Self::user(Box::new(source))
119    }
120
121    /// Creates `RuntimeError` from an error and a WasmTrace
122    ///
123    /// # Example
124    /// ```ignore
125    /// let wasm_trace = vec![wasmer_types::FrameInfo::new(
126    ///   "my_module".to_string(),
127    ///   0,
128    ///   Some("my_function".to_string()),
129    ///   0.into(),
130    ///   2.into()
131    /// )];
132    /// let trap = wasmer::RuntimeError::new_from_source(my_error, wasm_trace, None);
133    /// assert_eq!("unexpected error", trap.message());
134    /// ```
135    pub fn new_from_source(
136        source: Trap,
137        wasm_trace: Vec<FrameInfo>,
138        trap_code: Option<TrapCode>,
139    ) -> Self {
140        Self {
141            inner: Arc::new(RuntimeErrorInner {
142                source,
143                wasm_trace,
144                trap_code,
145            }),
146        }
147    }
148
149    /// Creates a custom user Error.
150    ///
151    /// This error object can be passed through Wasm frames and later retrieved
152    /// using the `downcast` method.
153    pub fn user(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
154        match error.downcast::<Self>() {
155            Ok(err) => *err,
156            Err(error) => error.into(),
157        }
158    }
159
160    /// Creates a `RuntimeError` containing an exception.
161    ///
162    /// If this error is returned from an imported function, the exception
163    /// will be thrown in the WebAssembly code instead of the usual trapping.
164    pub fn exception(ctx: &impl AsStoreRef, exception: Exception) -> Self {
165        let exnref = exception.vm_exceptionref();
166        let store = ctx.as_store_ref();
167        match store.inner.objects {
168            #[cfg(feature = "sys")]
169            crate::StoreObjects::Sys(ref store_objects) => {
170                crate::backend::sys::vm::Trap::uncaught_exception(
171                    exnref.unwrap_sys_ref().clone(),
172                    store_objects,
173                )
174                .into()
175            }
176            _ => panic!("exceptions are only supported in the `sys` backend"),
177        }
178    }
179
180    /// Returns a reference the `message` stored in `Trap`.
181    pub fn message(&self) -> String {
182        if let Some(trap_code) = self.inner.trap_code {
183            trap_code.message().to_string()
184        } else {
185            self.inner.source.to_string()
186        }
187    }
188
189    /// Returns a list of function frames in WebAssembly code that led to this
190    /// trap happening.
191    pub fn trace(&self) -> &[FrameInfo] {
192        &self.inner.wasm_trace
193    }
194
195    /// Returns trap code, if it's a Trap
196    pub fn to_trap(self) -> Option<TrapCode> {
197        self.inner.trap_code
198    }
199
200    // /// Returns trap code, if it's a Trap
201    // pub fn to_source(self) -> &'static Trap {
202    //     &self.inner.as_ref().source
203    // }
204
205    /// Attempts to downcast the `RuntimeError` to a concrete type.
206    pub fn downcast<T: std::error::Error + 'static>(self) -> Result<T, Self> {
207        match Arc::try_unwrap(self.inner) {
208            Ok(inner) if inner.source.is::<T>() => Ok(inner.source.downcast::<T>().unwrap()),
209            Ok(inner) => Err(Self {
210                inner: Arc::new(inner),
211            }),
212            Err(inner) => Err(Self { inner }),
213        }
214    }
215
216    /// Attempts to downcast the `RuntimeError` to a concrete type.
217    pub fn downcast_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
218        self.inner.as_ref().source.downcast_ref::<T>()
219    }
220
221    /// Returns true if the `RuntimeError` is the same as T
222    pub fn is<T: std::error::Error + 'static>(&self) -> bool {
223        self.inner.source.is::<T>()
224    }
225
226    /// Returns true if the `RuntimeError` is an uncaught exception.
227    pub fn is_exception(&self) -> bool {
228        self.inner.source.is_exception()
229    }
230
231    /// If the `RuntimeError` is an uncaught exception, returns it.
232    pub fn to_exception(&self) -> Option<Exception> {
233        self.inner.source.to_exception()
234    }
235
236    /// Returns a displayable version of the `RuntimeError` that also shows exception payloads.
237    pub fn display<'a>(&'a self, store: &'a mut impl AsStoreMut) -> RuntimeErrorDisplay<'a> {
238        if let Some(exception) = self.to_exception() {
239            RuntimeErrorDisplay::Exception(exception.payload(store), self.trace())
240        } else {
241            RuntimeErrorDisplay::Other(self)
242        }
243    }
244
245    /// Write the WASM trace to the given formatter, if we have one.
246    pub fn write_trace(trace: &[FrameInfo], f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        if trace.is_empty() {
248            return Ok(());
249        }
250        for frame in trace.iter() {
251            let name = frame.module_name();
252            let func_index = frame.func_index();
253            writeln!(f)?;
254            write!(f, "    at ")?;
255            match frame.function_name() {
256                Some(name) => write!(f, "{}", symbolic_demangle::demangle(name))?,
257                None => write!(f, "<unnamed>")?,
258            }
259            write!(
260                f,
261                " ({}[{}]:0x{:x})",
262                name,
263                func_index,
264                frame.module_offset()
265            )?;
266        }
267        Ok(())
268    }
269
270    pub(crate) fn from_dyn(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
271        match err.downcast::<Self>() {
272            Ok(runtime_error) => *runtime_error,
273            Err(error) => Trap::user(error),
274        }
275    }
276}
277
278impl std::fmt::Debug for RuntimeError {
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        f.debug_struct("RuntimeError")
281            .field("source", &self.inner.source)
282            .field("wasm_trace", &self.inner.wasm_trace)
283            .finish()
284    }
285}
286
287impl std::fmt::Display for RuntimeError {
288    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289        write!(f, "RuntimeError: {}", self.message())?;
290        Self::write_trace(self.trace(), f)
291    }
292}
293
294/// A displayable version of the `RuntimeError` that also shows exception payloads.
295pub enum RuntimeErrorDisplay<'a> {
296    /// The error is an uncaught exception, with its payload and trace.
297    Exception(Vec<Value>, &'a [FrameInfo]),
298    /// The error is not an exception, just display it.
299    Other(&'a RuntimeError),
300}
301
302impl std::fmt::Display for RuntimeErrorDisplay<'_> {
303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304        match self {
305            RuntimeErrorDisplay::Exception(payload, trace) => {
306                write!(f, "Uncaught exception")?;
307                if !payload.is_empty() {
308                    write!(f, " with payload: {payload:?}")?;
309                }
310                RuntimeError::write_trace(trace, f)
311            }
312            RuntimeErrorDisplay::Other(err) => write!(f, "{err}"),
313        }
314    }
315}
316
317impl std::error::Error for RuntimeError {
318    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
319        self.inner.source.source()
320    }
321}
322
323impl From<Box<dyn std::error::Error + Send + Sync>> for RuntimeError {
324    fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
325        match error.downcast::<Self>() {
326            // The error is already a RuntimeError, we return it directly
327            Ok(runtime_error) => *runtime_error,
328            Err(error) => Trap::user(error),
329        }
330    }
331}