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#[allow(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    // if adding a variant here be sure to update the `check!` macro below
96}
97
98impl Trap {
99    /// Converts a byte back into a `Trap` if its in-bounds
100    pub fn from_u8(byte: u8) -> Option<Trap> {
101        // FIXME: this could use some sort of derive-like thing to avoid having to
102        // deduplicate the names here.
103        //
104        // This simply converts from the a `u8`, to the `Trap` enum.
105        macro_rules! check {
106            ($($name:ident)*) => ($(if byte == Trap::$name as u8 {
107                return Some(Trap::$name);
108            })*);
109        }
110
111        check! {
112            StackOverflow
113            MemoryOutOfBounds
114            HeapMisaligned
115            TableOutOfBounds
116            IndirectCallToNull
117            BadSignature
118            IntegerOverflow
119            IntegerDivisionByZero
120            BadConversionToInteger
121            UnreachableCodeReached
122            Interrupt
123            AlwaysTrapAdapter
124            OutOfFuel
125            AtomicWaitNonSharedMemory
126            NullReference
127            ArrayOutOfBounds
128            AllocationTooLarge
129            CastFailure
130            CannotEnterComponent
131            NoAsyncResult
132        }
133
134        None
135    }
136}
137
138impl fmt::Display for Trap {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        use Trap::*;
141
142        let desc = match self {
143            StackOverflow => "call stack exhausted",
144            MemoryOutOfBounds => "out of bounds memory access",
145            HeapMisaligned => "unaligned atomic",
146            TableOutOfBounds => "undefined element: out of bounds table access",
147            IndirectCallToNull => "uninitialized element",
148            BadSignature => "indirect call type mismatch",
149            IntegerOverflow => "integer overflow",
150            IntegerDivisionByZero => "integer divide by zero",
151            BadConversionToInteger => "invalid conversion to integer",
152            UnreachableCodeReached => "wasm `unreachable` instruction executed",
153            Interrupt => "interrupt",
154            AlwaysTrapAdapter => "degenerate component adapter called",
155            OutOfFuel => "all fuel consumed by WebAssembly",
156            AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
157            NullReference => "null reference",
158            ArrayOutOfBounds => "out of bounds array access",
159            AllocationTooLarge => "allocation size too large",
160            CastFailure => "cast failure",
161            CannotEnterComponent => "cannot enter component instance",
162            NoAsyncResult => "async-lifted export failed to produce a result",
163        };
164        write!(f, "wasm trap: {desc}")
165    }
166}
167
168impl core::error::Error for Trap {}
169
170/// Decodes the provided trap information section and attempts to find the trap
171/// code corresponding to the `offset` specified.
172///
173/// The `section` provided is expected to have been built by
174/// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
175/// offset within the text section of the compilation image.
176pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
177    let mut section = Bytes(section);
178    // NB: this matches the encoding written by `append_to` above.
179    let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
180    let count = usize::try_from(count.get(LittleEndian)).ok()?;
181    let (offsets, traps) =
182        object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
183    debug_assert_eq!(traps.len(), count);
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}