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}