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.
117     CannotBlockSyncTask,
118     // if adding a variant here be sure to update the `check!` macro below
119 }
120 
121 impl Trap {
122     /// Converts a byte back into a `Trap` if its in-bounds
123     pub fn from_u8(byte: u8) -> Option<Trap> {
124         // FIXME: this could use some sort of derive-like thing to avoid having to
125         // deduplicate the names here.
126         //
127         // This simply converts from the a `u8`, to the `Trap` enum.
128         macro_rules! check {
129             ($($name:ident)*) => ($(if byte == Trap::$name as u8 {
130                 return Some(Trap::$name);
131             })*);
132         }
133 
134         check! {
135             StackOverflow
136             MemoryOutOfBounds
137             HeapMisaligned
138             TableOutOfBounds
139             IndirectCallToNull
140             BadSignature
141             IntegerOverflow
142             IntegerDivisionByZero
143             BadConversionToInteger
144             UnreachableCodeReached
145             Interrupt
146             AlwaysTrapAdapter
147             OutOfFuel
148             AtomicWaitNonSharedMemory
149             NullReference
150             ArrayOutOfBounds
151             AllocationTooLarge
152             CastFailure
153             CannotEnterComponent
154             NoAsyncResult
155             UnhandledTag
156             ContinuationAlreadyConsumed
157             DisabledOpcode
158             AsyncDeadlock
159             CannotLeaveComponent
160             CannotBlockSyncTask
161         }
162 
163         None
164     }
165 }
166 
167 impl fmt::Display for Trap {
168     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169         use Trap::*;
170 
171         let desc = match self {
172             StackOverflow => "call stack exhausted",
173             MemoryOutOfBounds => "out of bounds memory access",
174             HeapMisaligned => "unaligned atomic",
175             TableOutOfBounds => "undefined element: out of bounds table access",
176             IndirectCallToNull => "uninitialized element",
177             BadSignature => "indirect call type mismatch",
178             IntegerOverflow => "integer overflow",
179             IntegerDivisionByZero => "integer divide by zero",
180             BadConversionToInteger => "invalid conversion to integer",
181             UnreachableCodeReached => "wasm `unreachable` instruction executed",
182             Interrupt => "interrupt",
183             AlwaysTrapAdapter => "degenerate component adapter called",
184             OutOfFuel => "all fuel consumed by WebAssembly",
185             AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
186             NullReference => "null reference",
187             ArrayOutOfBounds => "out of bounds array access",
188             AllocationTooLarge => "allocation size too large",
189             CastFailure => "cast failure",
190             CannotEnterComponent => "cannot enter component instance",
191             NoAsyncResult => "async-lifted export failed to produce a result",
192             UnhandledTag => "unhandled tag",
193             ContinuationAlreadyConsumed => "continuation already consumed",
194             DisabledOpcode => "pulley opcode disabled at compile time was executed",
195             AsyncDeadlock => "deadlock detected: event loop cannot make further progress",
196             CannotLeaveComponent => "cannot leave component instance",
197             CannotBlockSyncTask => "cannot block a synchronous task before returning",
198         };
199         write!(f, "wasm trap: {desc}")
200     }
201 }
202 
203 impl core::error::Error for Trap {}
204 
205 /// Decodes the provided trap information section and attempts to find the trap
206 /// code corresponding to the `offset` specified.
207 ///
208 /// The `section` provided is expected to have been built by
209 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
210 /// offset within the text section of the compilation image.
211 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
212     let (offsets, traps) = parse(section)?;
213 
214     // The `offsets` table is sorted in the trap section so perform a binary
215     // search of the contents of this section to find whether `offset` is an
216     // entry in the section. Note that this is a precise search because trap pcs
217     // should always be precise as well as our metadata about them, which means
218     // we expect an exact match to correspond to a trap opcode.
219     //
220     // Once an index is found within the `offsets` array then that same index is
221     // used to lookup from the `traps` list of bytes to get the trap code byte
222     // corresponding to this offset.
223     let offset = u32::try_from(offset).ok()?;
224     let index = offsets
225         .binary_search_by_key(&offset, |val| val.get(LittleEndian))
226         .ok()?;
227     debug_assert!(index < traps.len());
228     let byte = *traps.get(index)?;
229 
230     let trap = Trap::from_u8(byte);
231     debug_assert!(trap.is_some(), "missing mapping for {byte}");
232     trap
233 }
234 
235 fn parse(section: &[u8]) -> Option<(&[U32Bytes<LittleEndian>], &[u8])> {
236     let mut section = Bytes(section);
237     // NB: this matches the encoding written by `append_to` above.
238     let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
239     let count = usize::try_from(count.get(LittleEndian)).ok()?;
240     let (offsets, traps) =
241         object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
242     debug_assert_eq!(traps.len(), count);
243     Some((offsets, traps))
244 }
245 
246 /// Returns an iterator over all of the traps encoded in `section`, which should
247 /// have been produced by `TrapEncodingBuilder`.
248 pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> {
249     let (offsets, traps) = parse(section)?;
250     Some(
251         offsets
252             .iter()
253             .zip(traps)
254             .map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())),
255     )
256 }
257