1 use core::fmt;
2 use object::{Bytes, LittleEndian, U32};
3 
4 /// Information about trap.
5 #[derive(Debug, PartialEq, Eq, Clone)]
6 pub 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 
16 macro_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.
63 generate_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         /// Cannot issue a read/write on a future/stream while there is a
220         /// pending operation already.
221         ConcurrentFutureStreamOp = "cannot have concurrent operations active on a future/stream",
222 
223         // if adding a variant here be sure to update `trap.rs` and `trap.h` as
224         // mentioned above
225     }
226 }
227 
228 impl core::error::Error for Trap {}
229 
230 /// Decodes the provided trap information section and attempts to find the trap
231 /// code corresponding to the `offset` specified.
232 ///
233 /// The `section` provided is expected to have been built by
234 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
235 /// offset within the text section of the compilation image.
lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap>236 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
237     let (offsets, traps) = parse(section)?;
238 
239     // The `offsets` table is sorted in the trap section so perform a binary
240     // search of the contents of this section to find whether `offset` is an
241     // entry in the section. Note that this is a precise search because trap pcs
242     // should always be precise as well as our metadata about them, which means
243     // we expect an exact match to correspond to a trap opcode.
244     //
245     // Once an index is found within the `offsets` array then that same index is
246     // used to lookup from the `traps` list of bytes to get the trap code byte
247     // corresponding to this offset.
248     let offset = u32::try_from(offset).ok()?;
249     let index = offsets
250         .binary_search_by_key(&offset, |val| val.get(LittleEndian))
251         .ok()?;
252     debug_assert!(index < traps.len());
253     let byte = *traps.get(index)?;
254 
255     let trap = Trap::from_u8(byte);
256     debug_assert!(trap.is_some(), "missing mapping for {byte}");
257     trap
258 }
259 
parse(section: &[u8]) -> Option<(&[U32<LittleEndian>], &[u8])>260 fn parse(section: &[u8]) -> Option<(&[U32<LittleEndian>], &[u8])> {
261     let mut section = Bytes(section);
262     // NB: this matches the encoding written by `append_to` above.
263     let count = section.read::<U32<LittleEndian>>().ok()?;
264     let count = usize::try_from(count.get(LittleEndian)).ok()?;
265     let (offsets, traps) = object::slice_from_bytes::<U32<LittleEndian>>(section.0, count).ok()?;
266     debug_assert_eq!(traps.len(), count);
267     Some((offsets, traps))
268 }
269 
270 /// Returns an iterator over all of the traps encoded in `section`, which should
271 /// have been produced by `TrapEncodingBuilder`.
iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_>272 pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> {
273     let (offsets, traps) = parse(section)?;
274     Some(
275         offsets
276             .iter()
277             .zip(traps)
278             .map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())),
279     )
280 }
281