wasmi_core/
trap.rs

1use crate::HostError;
2use core::fmt::{self, Display};
3use std::{boxed::Box, string::String};
4
5#[cfg(feature = "std")]
6use std::error::Error as StdError;
7
8/// Error type which can be returned by Wasm code or by the host environment.
9///
10/// Under some conditions, Wasm execution may produce a [`Trap`],
11/// which immediately aborts execution.
12/// Traps cannot be handled by WebAssembly code, but are reported to the
13/// host embedder.
14#[derive(Debug)]
15pub struct Trap {
16    /// The cloneable reason of a [`Trap`].
17    reason: Box<TrapReason>,
18}
19
20#[test]
21fn trap_size() {
22    assert_eq!(
23        core::mem::size_of::<Trap>(),
24        core::mem::size_of::<*const ()>()
25    );
26}
27
28/// The reason of a [`Trap`].
29#[derive(Debug)]
30enum TrapReason {
31    /// Traps during Wasm execution.
32    InstructionTrap(TrapCode),
33    /// An `i32` exit status code.
34    ///
35    /// # Note
36    ///
37    /// This is useful for some WASI functions.
38    I32Exit(i32),
39    /// An error described by a display message.
40    Message(Box<str>),
41    /// Traps and errors during host execution.
42    Host(Box<dyn HostError>),
43}
44
45impl TrapReason {
46    /// Returns the classic `i32` exit program code of a `Trap` if any.
47    ///
48    /// Otherwise returns `None`.
49    pub fn i32_exit_status(&self) -> Option<i32> {
50        if let Self::I32Exit(status) = self {
51            return Some(*status);
52        }
53        None
54    }
55
56    /// Returns a shared reference to the [`HostError`] if any.
57    #[inline]
58    pub fn as_host(&self) -> Option<&dyn HostError> {
59        if let Self::Host(host_error) = self {
60            return Some(&**host_error);
61        }
62        None
63    }
64
65    /// Returns an exclusive reference to the [`HostError`] if any.
66    #[inline]
67    pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> {
68        if let Self::Host(host_error) = self {
69            return Some(&mut **host_error);
70        }
71        None
72    }
73
74    /// Consumes `self` to return the [`HostError`] if any.
75    #[inline]
76    pub fn into_host(self) -> Option<Box<dyn HostError>> {
77        if let Self::Host(host_error) = self {
78            return Some(host_error);
79        }
80        None
81    }
82
83    /// Returns the [`TrapCode`] traps originating from Wasm execution.
84    #[inline]
85    pub fn trap_code(&self) -> Option<TrapCode> {
86        if let Self::InstructionTrap(trap_code) = self {
87            return Some(*trap_code);
88        }
89        None
90    }
91}
92
93impl Trap {
94    /// Create a new [`Trap`] from the [`TrapReason`].
95    fn with_reason(reason: TrapReason) -> Self {
96        Self {
97            reason: Box::new(reason),
98        }
99    }
100
101    /// Creates a new [`Trap`] described by a `message`.
102    #[cold] // traps are exceptional, this helps move handling off the main path
103    pub fn new<T>(message: T) -> Self
104    where
105        T: Into<String>,
106    {
107        Self::with_reason(TrapReason::Message(message.into().into_boxed_str()))
108    }
109
110    /// Downcasts the [`Trap`] into the `T: HostError` if possible.
111    ///
112    /// Returns `None` otherwise.
113    #[inline]
114    pub fn downcast_ref<T>(&self) -> Option<&T>
115    where
116        T: HostError,
117    {
118        self.reason
119            .as_host()
120            .and_then(<(dyn HostError + 'static)>::downcast_ref)
121    }
122
123    /// Downcasts the [`Trap`] into the `T: HostError` if possible.
124    ///
125    /// Returns `None` otherwise.
126    #[inline]
127    pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
128    where
129        T: HostError,
130    {
131        self.reason
132            .as_host_mut()
133            .and_then(<(dyn HostError + 'static)>::downcast_mut)
134    }
135
136    /// Consumes `self` to downcast the [`Trap`] into the `T: HostError` if possible.
137    ///
138    /// Returns `None` otherwise.
139    #[inline]
140    pub fn downcast<T>(self) -> Option<T>
141    where
142        T: HostError,
143    {
144        self.reason
145            .into_host()
146            .and_then(|error| error.downcast().ok())
147            .map(|boxed| *boxed)
148    }
149
150    /// Creates a new `Trap` representing an explicit program exit with a classic `i32`
151    /// exit status value.
152    #[cold] // see Trap::new
153    pub fn i32_exit(status: i32) -> Self {
154        Self::with_reason(TrapReason::I32Exit(status))
155    }
156
157    /// Returns the classic `i32` exit program code of a `Trap` if any.
158    ///
159    /// Otherwise returns `None`.
160    #[inline]
161    pub fn i32_exit_status(&self) -> Option<i32> {
162        self.reason.i32_exit_status()
163    }
164
165    /// Returns the [`TrapCode`] traps originating from Wasm execution.
166    #[inline]
167    pub fn trap_code(&self) -> Option<TrapCode> {
168        self.reason.trap_code()
169    }
170}
171
172impl From<TrapCode> for Trap {
173    #[cold] // see Trap::new
174    fn from(error: TrapCode) -> Self {
175        Self::with_reason(TrapReason::InstructionTrap(error))
176    }
177}
178
179impl<E> From<E> for Trap
180where
181    E: HostError,
182{
183    #[inline]
184    #[cold] // see Trap::new
185    fn from(host_error: E) -> Self {
186        Self::with_reason(TrapReason::Host(Box::new(host_error)))
187    }
188}
189
190impl Display for TrapReason {
191    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192        match self {
193            Self::InstructionTrap(trap_code) => Display::fmt(trap_code, f),
194            Self::I32Exit(status) => write!(f, "Exited with i32 exit status {status}"),
195            Self::Message(message) => write!(f, "{message}"),
196            Self::Host(host_error) => Display::fmt(host_error, f),
197        }
198    }
199}
200
201impl Display for Trap {
202    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203        <TrapReason as Display>::fmt(&self.reason, f)
204    }
205}
206
207#[cfg(feature = "std")]
208impl StdError for Trap {
209    fn description(&self) -> &str {
210        self.trap_code().map_or("", |code| code.trap_message())
211    }
212}
213
214/// Error type which can be thrown by wasm code or by host environment.
215///
216/// See [`Trap`] for details.
217///
218/// [`Trap`]: struct.Trap.html
219#[derive(Debug, Copy, Clone, PartialEq, Eq)]
220pub enum TrapCode {
221    /// Wasm code executed `unreachable` opcode.
222    ///
223    /// This indicates that unreachable Wasm code was actually reached.
224    /// This opcode have a similar purpose as `ud2` in x86.
225    UnreachableCodeReached,
226
227    /// Attempt to load or store at the address which
228    /// lies outside of bounds of the memory.
229    ///
230    /// Since addresses are interpreted as unsigned integers, out of bounds access
231    /// can't happen with negative addresses (i.e. they will always wrap).
232    MemoryOutOfBounds,
233
234    /// Attempt to access table element at index which
235    /// lies outside of bounds.
236    ///
237    /// This typically can happen when `call_indirect` is executed
238    /// with index that lies out of bounds.
239    ///
240    /// Since indexes are interpreted as unsigned integers, out of bounds access
241    /// can't happen with negative indexes (i.e. they will always wrap).
242    TableOutOfBounds,
243
244    /// Indicates that a `call_indirect` instruction called a function at
245    /// an uninitialized (i.e. `null`) table index.
246    IndirectCallToNull,
247
248    /// Attempt to divide by zero.
249    ///
250    /// This trap typically can happen if `div` or `rem` is executed with
251    /// zero as divider.
252    IntegerDivisionByZero,
253
254    /// An integer arithmetic operation caused an overflow.
255    ///
256    /// This can happen when trying to do signed division (or get the remainder)
257    /// -2<sup>N-1</sup> over -1. This is because the result +2<sup>N-1</sup>
258    /// isn't representable as a N-bit signed integer.
259    IntegerOverflow,
260
261    /// Attempted to make an invalid conversion to an integer type.
262    ///
263    /// This can for example happen when trying to truncate NaNs,
264    /// infinity, or value for which the result is out of range into an integer.
265    BadConversionToInteger,
266
267    /// Stack overflow.
268    ///
269    /// This is likely caused by some infinite or very deep recursion.
270    /// Extensive inlining might also be the cause of stack overflow.
271    StackOverflow,
272
273    /// Attempt to invoke a function with mismatching signature.
274    ///
275    /// This can happen with indirect calls as they always
276    /// specify the expected signature of function. If an indirect call is executed
277    /// with an index that points to a function with signature different of what is
278    /// expected by this indirect call, this trap is raised.
279    BadSignature,
280
281    /// This trap is raised when a WebAssembly execution ran out of fuel.
282    ///
283    /// The Wasmi execution engine can be configured to instrument its
284    /// internal bytecode so that fuel is consumed for each executed instruction.
285    /// This is useful to deterministically halt or yield a WebAssembly execution.
286    OutOfFuel,
287
288    /// This trap is raised when a growth operation was attempted and an
289    /// installed `wasmi::ResourceLimiter` returned `Err(...)` from the
290    /// associated `table_growing` or `memory_growing` method, indicating a
291    /// desire on the part of the embedder to trap the interpreter rather than
292    /// merely fail the growth operation.
293    GrowthOperationLimited,
294}
295
296impl TrapCode {
297    /// Returns the trap message as specified by the WebAssembly specification.
298    ///
299    /// # Note
300    ///
301    /// This API is primarily useful for the Wasm spec testsuite but might have
302    /// other uses since it avoid heap memory allocation in certain cases.
303    pub fn trap_message(&self) -> &'static str {
304        match self {
305            Self::UnreachableCodeReached => "wasm `unreachable` instruction executed",
306            Self::MemoryOutOfBounds => "out of bounds memory access",
307            Self::TableOutOfBounds => "undefined element: out of bounds table access",
308            Self::IndirectCallToNull => "uninitialized element 2", // TODO: fixme, remove the trailing " 2" again
309            Self::IntegerDivisionByZero => "integer divide by zero",
310            Self::IntegerOverflow => "integer overflow",
311            Self::BadConversionToInteger => "invalid conversion to integer",
312            Self::StackOverflow => "call stack exhausted",
313            Self::BadSignature => "indirect call type mismatch",
314            Self::OutOfFuel => "all fuel consumed by WebAssembly",
315            Self::GrowthOperationLimited => "growth operation limited",
316        }
317    }
318}
319
320impl Display for TrapCode {
321    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
322        write!(f, "{}", self.trap_message())
323    }
324}