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