wasmer_engine/trap/
error.rs

1use super::frame_info::{FrameInfo, GlobalFrameInfo, FRAME_INFO};
2use backtrace::Backtrace;
3use std::error::Error;
4use std::fmt;
5use std::sync::Arc;
6use wasmer_vm::{raise_user_trap, Trap, TrapCode};
7
8/// A struct representing an aborted instruction execution, with a message
9/// indicating the cause.
10#[derive(Clone)]
11pub struct RuntimeError {
12    inner: Arc<RuntimeErrorInner>,
13}
14
15/// The source of the `RuntimeError`.
16#[derive(Debug)]
17enum RuntimeErrorSource {
18    Generic(String),
19    OOM,
20    User(Box<dyn Error + Send + Sync>),
21    Trap(TrapCode),
22}
23
24impl fmt::Display for RuntimeErrorSource {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            Self::Generic(s) => write!(f, "{}", s),
28            Self::User(s) => write!(f, "{}", s),
29            Self::OOM => write!(f, "Wasmer VM out of memory"),
30            Self::Trap(s) => write!(f, "{}", s.message()),
31        }
32    }
33}
34
35struct RuntimeErrorInner {
36    /// The source error (this can be a custom user `Error` or a [`TrapCode`])
37    source: RuntimeErrorSource,
38    /// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`).
39    wasm_trace: Vec<FrameInfo>,
40    /// The native backtrace
41    native_trace: Backtrace,
42}
43
44fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
45    (t, t)
46}
47
48impl RuntimeError {
49    /// Creates a new generic `RuntimeError` with the given `message`.
50    ///
51    /// # Example
52    /// ```
53    /// let trap = wasmer_engine::RuntimeError::new("unexpected error");
54    /// assert_eq!("unexpected error", trap.message());
55    /// ```
56    pub fn new<I: Into<String>>(message: I) -> Self {
57        let info = FRAME_INFO.read().unwrap();
58        let msg = message.into();
59        Self::new_with_trace(
60            &info,
61            None,
62            RuntimeErrorSource::Generic(msg),
63            Backtrace::new_unresolved(),
64        )
65    }
66
67    /// Create a new RuntimeError from a Trap.
68    pub fn from_trap(trap: Trap) -> Self {
69        let info = FRAME_INFO.read().unwrap();
70        match trap {
71            // A user error
72            Trap::User(error) => {
73                match error.downcast::<Self>() {
74                    // The error is already a RuntimeError, we return it directly
75                    Ok(runtime_error) => *runtime_error,
76                    Err(e) => Self::new_with_trace(
77                        &info,
78                        None,
79                        RuntimeErrorSource::User(e),
80                        Backtrace::new_unresolved(),
81                    ),
82                }
83            }
84            // A trap caused by the VM being Out of Memory
85            Trap::OOM { backtrace } => {
86                Self::new_with_trace(&info, None, RuntimeErrorSource::OOM, backtrace)
87            }
88            // A trap caused by an error on the generated machine code for a Wasm function
89            Trap::Wasm {
90                pc,
91                signal_trap,
92                backtrace,
93            } => {
94                let code = info
95                    .lookup_trap_info(pc)
96                    .map_or(signal_trap.unwrap_or(TrapCode::StackOverflow), |info| {
97                        info.trap_code
98                    });
99                Self::new_with_trace(&info, Some(pc), RuntimeErrorSource::Trap(code), backtrace)
100            }
101            // A trap triggered manually from the Wasmer runtime
102            Trap::Lib {
103                trap_code,
104                backtrace,
105            } => Self::new_with_trace(&info, None, RuntimeErrorSource::Trap(trap_code), backtrace),
106        }
107    }
108
109    /// Raises a custom user Error
110    #[deprecated(since = "2.1.1", note = "return a Result from host functions instead")]
111    pub fn raise(error: Box<dyn Error + Send + Sync>) -> ! {
112        unsafe { raise_user_trap(error) }
113    }
114
115    /// Creates a custom user Error.
116    ///
117    /// This error object can be passed through Wasm frames and later retrieved
118    /// using the `downcast` method.
119    pub fn user(error: Box<dyn Error + Send + Sync>) -> Self {
120        match error.downcast::<Self>() {
121            // The error is already a RuntimeError, we return it directly
122            Ok(runtime_error) => *runtime_error,
123            Err(error) => {
124                let info = FRAME_INFO.read().unwrap();
125                Self::new_with_trace(
126                    &info,
127                    None,
128                    RuntimeErrorSource::User(error),
129                    Backtrace::new_unresolved(),
130                )
131            }
132        }
133    }
134
135    fn new_with_trace(
136        info: &GlobalFrameInfo,
137        trap_pc: Option<usize>,
138        source: RuntimeErrorSource,
139        native_trace: Backtrace,
140    ) -> Self {
141        let frames: Vec<usize> = native_trace
142            .frames()
143            .iter()
144            .filter_map(|frame| {
145                let pc = frame.ip() as usize;
146                if pc == 0 {
147                    None
148                } else {
149                    // Note that we need to be careful about the pc we pass in here to
150                    // lookup frame information. This program counter is used to
151                    // translate back to an original source location in the origin wasm
152                    // module. If this pc is the exact pc that the trap happened at,
153                    // then we look up that pc precisely. Otherwise backtrace
154                    // information typically points at the pc *after* the call
155                    // instruction (because otherwise it's likely a call instruction on
156                    // the stack). In that case we want to lookup information for the
157                    // previous instruction (the call instruction) so we subtract one as
158                    // the lookup.
159                    let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 };
160                    Some(pc_to_lookup)
161                }
162            })
163            .collect();
164
165        // Let's construct the trace
166        let wasm_trace = frames
167            .into_iter()
168            .filter_map(|pc| info.lookup_frame_info(pc))
169            .collect::<Vec<_>>();
170
171        Self {
172            inner: Arc::new(RuntimeErrorInner {
173                source,
174                wasm_trace,
175                native_trace,
176            }),
177        }
178    }
179
180    /// Returns a reference the `message` stored in `Trap`.
181    pub fn message(&self) -> String {
182        self.inner.source.to_string()
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    /// Attempts to downcast the `RuntimeError` to a concrete type.
192    pub fn downcast<T: Error + 'static>(self) -> Result<T, Self> {
193        match Arc::try_unwrap(self.inner) {
194            // We only try to downcast user errors
195            Ok(RuntimeErrorInner {
196                source: RuntimeErrorSource::User(err),
197                ..
198            }) if err.is::<T>() => Ok(*err.downcast::<T>().unwrap()),
199            Ok(inner) => Err(Self {
200                inner: Arc::new(inner),
201            }),
202            Err(inner) => Err(Self { inner }),
203        }
204    }
205
206    /// Returns trap code, if it's a Trap
207    pub fn to_trap(self) -> Option<TrapCode> {
208        if let RuntimeErrorSource::Trap(trap_code) = self.inner.source {
209            Some(trap_code)
210        } else {
211            None
212        }
213    }
214
215    /// Returns true if the `RuntimeError` is the same as T
216    pub fn is<T: Error + 'static>(&self) -> bool {
217        match &self.inner.source {
218            RuntimeErrorSource::User(err) => err.is::<T>(),
219            _ => false,
220        }
221    }
222}
223
224impl fmt::Debug for RuntimeError {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        f.debug_struct("RuntimeError")
227            .field("source", &self.inner.source)
228            .field("wasm_trace", &self.inner.wasm_trace)
229            .field("native_trace", &self.inner.native_trace)
230            .finish()
231    }
232}
233
234impl fmt::Display for RuntimeError {
235    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236        write!(f, "RuntimeError: {}", self.message())?;
237        let trace = self.trace();
238        if trace.is_empty() {
239            return Ok(());
240        }
241        for frame in self.trace().iter() {
242            let name = frame.module_name();
243            let func_index = frame.func_index();
244            writeln!(f)?;
245            write!(f, "    at ")?;
246            match frame.function_name() {
247                Some(name) => match rustc_demangle::try_demangle(name) {
248                    Ok(name) => write!(f, "{}", name)?,
249                    Err(_) => write!(f, "{}", name)?,
250                },
251                None => write!(f, "<unnamed>")?,
252            }
253            write!(
254                f,
255                " ({}[{}]:0x{:x})",
256                name,
257                func_index,
258                frame.module_offset()
259            )?;
260        }
261        Ok(())
262    }
263}
264
265impl std::error::Error for RuntimeError {
266    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
267        match &self.inner.source {
268            RuntimeErrorSource::User(err) => Some(&**err),
269            RuntimeErrorSource::Trap(err) => Some(err),
270            _ => None,
271        }
272    }
273}
274
275impl From<Trap> for RuntimeError {
276    fn from(trap: Trap) -> Self {
277        Self::from_trap(trap)
278    }
279}