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