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}