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