wasmtime_environ/
trap_encoding.rs

1use core::fmt;
2use object::{Bytes, LittleEndian, U32Bytes};
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: 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")]
26pub 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
161impl 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
217impl 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
263impl 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.
271pub 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
295fn 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`.
308pub 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}