Skip to main content

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