substrate_wasmtime/
trap.rs

1use crate::frame_info::{GlobalFrameInfo, FRAME_INFO};
2use crate::FrameInfo;
3use backtrace::Backtrace;
4use std::fmt;
5use std::sync::Arc;
6use wasmtime_environ::ir::TrapCode;
7
8/// A struct representing an aborted instruction execution, with a message
9/// indicating the cause.
10#[derive(Clone)]
11pub struct Trap {
12    inner: Arc<TrapInner>,
13}
14
15/// State describing the occasion which evoked a trap.
16#[derive(Debug)]
17enum TrapReason {
18    /// An error message describing a trap.
19    Message(String),
20
21    /// An `i32` exit status describing an explicit program exit.
22    I32Exit(i32),
23
24    /// A structured error describing a trap.
25    Error(Box<dyn std::error::Error + Send + Sync>),
26}
27
28impl fmt::Display for TrapReason {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            TrapReason::Message(s) => write!(f, "{}", s),
32            TrapReason::I32Exit(status) => write!(f, "Exited with i32 exit status {}", status),
33            TrapReason::Error(e) => write!(f, "{}", e),
34        }
35    }
36}
37
38struct TrapInner {
39    reason: TrapReason,
40    wasm_trace: Vec<FrameInfo>,
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 Trap {
49    /// Creates a new `Trap` with `message`.
50    /// # Example
51    /// ```
52    /// let trap = wasmtime::Trap::new("unexpected error");
53    /// assert!(trap.to_string().contains("unexpected error"));
54    /// ```
55    pub fn new<I: Into<String>>(message: I) -> Self {
56        let info = FRAME_INFO.read().unwrap();
57        let reason = TrapReason::Message(message.into());
58        Trap::new_with_trace(&info, None, reason, Backtrace::new_unresolved())
59    }
60
61    /// Creates a new `Trap` representing an explicit program exit with a classic `i32`
62    /// exit status value.
63    pub fn i32_exit(status: i32) -> Self {
64        Trap {
65            inner: Arc::new(TrapInner {
66                reason: TrapReason::I32Exit(status),
67                wasm_trace: Vec::new(),
68                native_trace: Backtrace::from(Vec::new()),
69            }),
70        }
71    }
72
73    pub(crate) fn from_runtime(runtime_trap: wasmtime_runtime::Trap) -> Self {
74        let info = FRAME_INFO.read().unwrap();
75        match runtime_trap {
76            wasmtime_runtime::Trap::User(error) => Trap::from(error),
77            wasmtime_runtime::Trap::Jit {
78                pc,
79                backtrace,
80                maybe_interrupted,
81            } => {
82                let mut code = info
83                    .lookup_trap_info(pc)
84                    .map(|info| info.trap_code)
85                    .unwrap_or(TrapCode::StackOverflow);
86                if maybe_interrupted && code == TrapCode::StackOverflow {
87                    code = TrapCode::Interrupt;
88                }
89                Trap::new_wasm(&info, Some(pc), code, backtrace)
90            }
91            wasmtime_runtime::Trap::Wasm {
92                trap_code,
93                backtrace,
94            } => Trap::new_wasm(&info, None, trap_code, backtrace),
95            wasmtime_runtime::Trap::OOM { backtrace } => {
96                let reason = TrapReason::Message("out of memory".to_string());
97                Trap::new_with_trace(&info, None, reason, backtrace)
98            }
99        }
100    }
101
102    fn new_wasm(
103        info: &GlobalFrameInfo,
104        trap_pc: Option<usize>,
105        code: TrapCode,
106        backtrace: Backtrace,
107    ) -> Self {
108        use wasmtime_environ::ir::TrapCode::*;
109        let desc = match code {
110            StackOverflow => "call stack exhausted",
111            HeapOutOfBounds => "out of bounds memory access",
112            TableOutOfBounds => "undefined element: out of bounds table access",
113            IndirectCallToNull => "uninitialized element",
114            BadSignature => "indirect call type mismatch",
115            IntegerOverflow => "integer overflow",
116            IntegerDivisionByZero => "integer divide by zero",
117            BadConversionToInteger => "invalid conversion to integer",
118            UnreachableCodeReached => "unreachable",
119            Interrupt => "interrupt",
120            User(_) => unreachable!(),
121        };
122        let msg = TrapReason::Message(format!("wasm trap: {}", desc));
123        Trap::new_with_trace(info, trap_pc, msg, backtrace)
124    }
125
126    fn new_with_trace(
127        info: &GlobalFrameInfo,
128        trap_pc: Option<usize>,
129        reason: TrapReason,
130        native_trace: Backtrace,
131    ) -> Self {
132        let mut wasm_trace = Vec::new();
133        for frame in native_trace.frames() {
134            let pc = frame.ip() as usize;
135            if pc == 0 {
136                continue;
137            }
138            // Note that we need to be careful about the pc we pass in here to
139            // lookup frame information. This program counter is used to
140            // translate back to an original source location in the origin wasm
141            // module. If this pc is the exact pc that the trap happened at,
142            // then we look up that pc precisely. Otherwise backtrace
143            // information typically points at the pc *after* the call
144            // instruction (because otherwise it's likely a call instruction on
145            // the stack). In that case we want to lookup information for the
146            // previous instruction (the call instruction) so we subtract one as
147            // the lookup.
148            let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 };
149            if let Some(info) = info.lookup_frame_info(pc_to_lookup) {
150                wasm_trace.push(info);
151            }
152        }
153        Trap {
154            inner: Arc::new(TrapInner {
155                reason,
156                wasm_trace,
157                native_trace,
158            }),
159        }
160    }
161
162    /// If the trap was the result of an explicit program exit with a classic
163    /// `i32` exit status value, return the value, otherwise return `None`.
164    pub fn i32_exit_status(&self) -> Option<i32> {
165        match self.inner.reason {
166            TrapReason::I32Exit(status) => Some(status),
167            _ => None,
168        }
169    }
170
171    /// Returns a list of function frames in WebAssembly code that led to this
172    /// trap happening.
173    pub fn trace(&self) -> &[FrameInfo] {
174        &self.inner.wasm_trace
175    }
176}
177
178impl fmt::Debug for Trap {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        f.debug_struct("Trap")
181            .field("reason", &self.inner.reason)
182            .field("wasm_trace", &self.inner.wasm_trace)
183            .field("native_trace", &self.inner.native_trace)
184            .finish()
185    }
186}
187
188impl fmt::Display for Trap {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        write!(f, "{}", self.inner.reason)?;
191        let trace = self.trace();
192        if trace.is_empty() {
193            return Ok(());
194        }
195        writeln!(f, "\nwasm backtrace:")?;
196        for (i, frame) in self.trace().iter().enumerate() {
197            let name = frame.module_name().unwrap_or("<unknown>");
198            write!(f, "  {}: {:#6x} - {}!", i, frame.module_offset(), name)?;
199            match frame.func_name() {
200                Some(name) => match rustc_demangle::try_demangle(name) {
201                    Ok(name) => write!(f, "{}", name)?,
202                    Err(_) => write!(f, "{}", name)?,
203                },
204                None => write!(f, "<wasm function {}>", frame.func_index())?,
205            }
206            writeln!(f, "")?;
207        }
208        Ok(())
209    }
210}
211
212impl std::error::Error for Trap {
213    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
214        match &self.inner.reason {
215            TrapReason::Error(e) => e.source(),
216            TrapReason::I32Exit(_) | TrapReason::Message(_) => None,
217        }
218    }
219}
220
221impl From<anyhow::Error> for Trap {
222    fn from(e: anyhow::Error) -> Trap {
223        Box::<dyn std::error::Error + Send + Sync>::from(e).into()
224    }
225}
226
227impl From<Box<dyn std::error::Error + Send + Sync>> for Trap {
228    fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Trap {
229        // If the top-level error is already a trap, don't be redundant and just return it.
230        if let Some(trap) = e.downcast_ref::<Trap>() {
231            trap.clone()
232        } else {
233            let info = FRAME_INFO.read().unwrap();
234            let reason = TrapReason::Error(e.into());
235            Trap::new_with_trace(&info, None, reason, Backtrace::new_unresolved())
236        }
237    }
238}