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