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    /// 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
102impl 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
143impl 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
174impl 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.
182pub 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
206fn 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`.
219pub 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}