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