wasmtime_environ/trap_encoding.rs
1use core::fmt;
2use object::{Bytes, LittleEndian, U32Bytes};
3
4/// Information about trap.
5#[derive(Debug, PartialEq, Eq, Clone)]
6pub struct TrapInformation {
7 /// The offset of the trapping instruction in native code.
8 ///
9 /// This is relative to the beginning of the function.
10 pub code_offset: u32,
11
12 /// Code of the trap.
13 pub trap_code: Trap,
14}
15
16macro_rules! generate_trap_type {
17 (pub enum Trap {
18 $(
19 $(#[$doc:meta])*
20 $name:ident = $msg:tt,
21 )*
22 }) => {
23 #[non_exhaustive]
24 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
25 #[expect(missing_docs, reason = "self-describing variants")]
26 pub enum Trap {
27 $(
28 $(#[$doc])*
29 $name,
30 )*
31 }
32
33 impl Trap {
34 /// Converts a byte back into a `Trap` if its in-bounds
35 pub fn from_u8(byte: u8) -> Option<Trap> {
36 $(
37 if byte == Trap::$name as u8 {
38 return Some(Trap::$name);
39 }
40 )*
41 None
42 }
43 }
44
45 impl fmt::Display for Trap {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 let desc = match self {
48 $(Self::$name => $msg,)*
49 };
50 write!(f, "wasm trap: {desc}")
51 }
52 }
53 }
54}
55
56// The code can be accessed from the c-api, where the possible values are
57// translated into enum values defined there:
58//
59// * the const assertions in c-api/src/trap.rs, and
60// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
61//
62// These need to be kept in sync.
63generate_trap_type! {
64 pub enum Trap {
65 /// The current stack space was exhausted.
66 StackOverflow = "call stack exhausted",
67
68 /// An out-of-bounds memory access.
69 MemoryOutOfBounds = "out of bounds memory access",
70
71 /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
72 HeapMisaligned = "unaligned atomic",
73
74 /// An out-of-bounds access to a table.
75 TableOutOfBounds = "undefined element: out of bounds table access",
76
77 /// Indirect call to a null table entry.
78 IndirectCallToNull = "uninitialized element",
79
80 /// Signature mismatch on indirect call.
81 BadSignature = "indirect call type mismatch",
82
83 /// An integer arithmetic operation caused an overflow.
84 IntegerOverflow = "integer overflow",
85
86 /// An integer division by zero.
87 IntegerDivisionByZero = "integer divide by zero",
88
89 /// Failed float-to-int conversion.
90 BadConversionToInteger = "invalid conversion to integer",
91
92 /// Code that was supposed to have been unreachable was reached.
93 UnreachableCodeReached = "wasm `unreachable` instruction executed",
94
95 /// Execution has potentially run too long and may be interrupted.
96 Interrupt = "interrupt",
97
98 /// When wasm code is configured to consume fuel and it runs out of fuel
99 /// then this trap will be raised.
100 OutOfFuel = "all fuel consumed by WebAssembly",
101
102 /// Used to indicate that a trap was raised by atomic wait operations on non shared memory.
103 AtomicWaitNonSharedMemory = "atomic wait on non-shared memory",
104
105 /// Call to a null reference.
106 NullReference = "null reference",
107
108 /// Attempt to access beyond the bounds of an array.
109 ArrayOutOfBounds = "out of bounds array access",
110
111 /// Attempted an allocation that was too large to succeed.
112 AllocationTooLarge = "allocation size too large",
113
114 /// Attempted to cast a reference to a type that it is not an instance of.
115 CastFailure = "cast failure",
116
117 /// When the `component-model` feature is enabled this trap represents a
118 /// scenario where one component tried to call another component but it
119 /// would have violated the reentrance rules of the component model,
120 /// triggering a trap instead.
121 CannotEnterComponent = "cannot enter component instance",
122
123 /// Async-lifted export failed to produce a result by calling `task.return`
124 /// before returning `STATUS_DONE` and/or after all host tasks completed.
125 NoAsyncResult = "async-lifted export failed to produce a result",
126
127 /// We are suspending to a tag for which there is no active handler.
128 UnhandledTag = "unhandled tag",
129
130 /// Attempt to resume a continuation twice.
131 ContinuationAlreadyConsumed = "continuation already consumed",
132
133 /// A Pulley opcode was executed at runtime when the opcode was disabled at
134 /// compile time.
135 DisabledOpcode = "pulley opcode disabled at compile time was executed",
136
137 /// Async event loop deadlocked; i.e. it cannot make further progress given
138 /// that all host tasks have completed and any/all host-owned stream/future
139 /// handles have been dropped.
140 AsyncDeadlock = "deadlock detected: event loop cannot make further progress",
141
142 /// When the `component-model` feature is enabled this trap represents a
143 /// scenario where a component instance tried to call an import or intrinsic
144 /// when it wasn't allowed to, e.g. from a post-return function.
145 CannotLeaveComponent = "cannot leave component instance",
146
147 /// A synchronous task attempted to make a potentially blocking call prior
148 /// to returning.
149 CannotBlockSyncTask = "cannot block a synchronous task before returning",
150
151 /// A component tried to lift a `char` with an invalid bit pattern.
152 InvalidChar = "invalid `char` bit pattern",
153
154 /// Debug assertion generated for a fused adapter regarding the expected
155 /// completion of a string encoding operation.
156 DebugAssertStringEncodingFinished = "should have finished string encoding",
157
158 /// Debug assertion generated for a fused adapter regarding a string
159 /// encoding operation.
160 DebugAssertEqualCodeUnits = "code units should be equal",
161
162 /// Debug assertion generated for a fused adapter regarding the alignment of
163 /// a pointer.
164 DebugAssertPointerAligned = "pointer should be aligned",
165
166 /// Debug assertion generated for a fused adapter regarding the upper bits
167 /// of a 64-bit value.
168 DebugAssertUpperBitsUnset = "upper bits should be unset",
169
170 /// A component tried to lift or lower a string past the end of its memory.
171 StringOutOfBounds = "string content out-of-bounds",
172
173 /// A component tried to lift or lower a list past the end of its memory.
174 ListOutOfBounds = "list content out-of-bounds",
175
176 /// A component used an invalid discriminant when lowering a variant value.
177 InvalidDiscriminant = "invalid variant discriminant",
178
179 /// A component passed an unaligned pointer when lifting or lowering a
180 /// value.
181 UnalignedPointer = "unaligned pointer",
182
183 /// `task.cancel` was called by a task which has not been cancelled.
184 TaskCancelNotCancelled = "`task.cancel` called by task which has not been cancelled",
185
186 /// `task.return` or `task.cancel` was called more than once for the
187 /// current task.
188 TaskCancelOrReturnTwice = "`task.return` or `task.cancel` called more than once for current task",
189
190 /// `subtask.cancel` was called after terminal status was already
191 /// delivered.
192 SubtaskCancelAfterTerminal = "`subtask.cancel` called after terminal status delivered",
193
194 /// Invalid `task.return` signature and/or options for the current task.
195 TaskReturnInvalid = "invalid `task.return` signature and/or options for current task",
196
197 /// Cannot drop waitable set with waiters in it.
198 WaitableSetDropHasWaiters = "cannot drop waitable set with waiters",
199
200 /// Cannot drop a subtask which has not yet resolved.
201 SubtaskDropNotResolved = "cannot drop a subtask which has not yet resolved",
202
203 /// Start function does not match the expected type.
204 ThreadNewIndirectInvalidType = "start function does not match expected type (currently only `(i32) -> ()` is supported)",
205
206 /// The start function index points to an uninitialized function.
207 ThreadNewIndirectUninitialized = "the start function index points to an uninitialized function",
208
209 /// Backpressure-related intrinsics overflowed the built-in 16-bit
210 /// counter.
211 BackpressureOverflow = "backpressure counter overflow",
212
213 /// Invalid code returned from `callback` of `async`-lifted function.
214 UnsupportedCallbackCode = "unsupported callback code",
215
216 /// Cannot resume a thread which is not suspended.
217 CannotResumeThread = "cannot resume thread which is not suspended",
218
219 // if adding a variant here be sure to update `trap.rs` and `trap.h` as
220 // mentioned above
221 }
222}
223
224impl core::error::Error for Trap {}
225
226/// Decodes the provided trap information section and attempts to find the trap
227/// code corresponding to the `offset` specified.
228///
229/// The `section` provided is expected to have been built by
230/// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
231/// offset within the text section of the compilation image.
232pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
233 let (offsets, traps) = parse(section)?;
234
235 // The `offsets` table is sorted in the trap section so perform a binary
236 // search of the contents of this section to find whether `offset` is an
237 // entry in the section. Note that this is a precise search because trap pcs
238 // should always be precise as well as our metadata about them, which means
239 // we expect an exact match to correspond to a trap opcode.
240 //
241 // Once an index is found within the `offsets` array then that same index is
242 // used to lookup from the `traps` list of bytes to get the trap code byte
243 // corresponding to this offset.
244 let offset = u32::try_from(offset).ok()?;
245 let index = offsets
246 .binary_search_by_key(&offset, |val| val.get(LittleEndian))
247 .ok()?;
248 debug_assert!(index < traps.len());
249 let byte = *traps.get(index)?;
250
251 let trap = Trap::from_u8(byte);
252 debug_assert!(trap.is_some(), "missing mapping for {byte}");
253 trap
254}
255
256fn parse(section: &[u8]) -> Option<(&[U32Bytes<LittleEndian>], &[u8])> {
257 let mut section = Bytes(section);
258 // NB: this matches the encoding written by `append_to` above.
259 let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
260 let count = usize::try_from(count.get(LittleEndian)).ok()?;
261 let (offsets, traps) =
262 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
263 debug_assert_eq!(traps.len(), count);
264 Some((offsets, traps))
265}
266
267/// Returns an iterator over all of the traps encoded in `section`, which should
268/// have been produced by `TrapEncodingBuilder`.
269pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> {
270 let (offsets, traps) = parse(section)?;
271 Some(
272 offsets
273 .iter()
274 .zip(traps)
275 .map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())),
276 )
277}