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#[derive(Clone)]
11pub struct Trap {
12 inner: Arc<TrapInner>,
13}
14
15#[derive(Debug)]
17enum TrapReason {
18 Message(String),
20
21 I32Exit(i32),
23
24 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 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 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 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 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 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 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}