1 use object::{Bytes, LittleEndian, U32Bytes};
2 use std::fmt;
3 
4 /// Information about trap.
5 #[derive(Debug, PartialEq, Eq, Clone)]
6 pub 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)]
26 pub 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 get the bits of a null `i31ref`.
78     NullI31Ref,
79 
80     /// When the `component-model` feature is enabled this trap represents a
81     /// scenario where one component tried to call another component but it
82     /// would have violated the reentrance rules of the component model,
83     /// triggering a trap instead.
84     CannotEnterComponent,
85     // if adding a variant here be sure to update the `check!` macro below
86 }
87 
88 impl Trap {
89     /// Converts a byte back into a `Trap` if its in-bounds
90     pub fn from_u8(byte: u8) -> Option<Trap> {
91         // FIXME: this could use some sort of derive-like thing to avoid having to
92         // deduplicate the names here.
93         //
94         // This simply converts from the a `u8`, to the `Trap` enum.
95         macro_rules! check {
96             ($($name:ident)*) => ($(if byte == Trap::$name as u8 {
97                 return Some(Trap::$name);
98             })*);
99         }
100 
101         check! {
102             StackOverflow
103             MemoryOutOfBounds
104             HeapMisaligned
105             TableOutOfBounds
106             IndirectCallToNull
107             BadSignature
108             IntegerOverflow
109             IntegerDivisionByZero
110             BadConversionToInteger
111             UnreachableCodeReached
112             Interrupt
113             AlwaysTrapAdapter
114             OutOfFuel
115             AtomicWaitNonSharedMemory
116             NullReference
117             NullI31Ref
118             CannotEnterComponent
119         }
120 
121         None
122     }
123 }
124 
125 impl fmt::Display for Trap {
126     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127         use Trap::*;
128 
129         let desc = match self {
130             StackOverflow => "call stack exhausted",
131             MemoryOutOfBounds => "out of bounds memory access",
132             HeapMisaligned => "unaligned atomic",
133             TableOutOfBounds => "undefined element: out of bounds table access",
134             IndirectCallToNull => "uninitialized element",
135             BadSignature => "indirect call type mismatch",
136             IntegerOverflow => "integer overflow",
137             IntegerDivisionByZero => "integer divide by zero",
138             BadConversionToInteger => "invalid conversion to integer",
139             UnreachableCodeReached => "wasm `unreachable` instruction executed",
140             Interrupt => "interrupt",
141             AlwaysTrapAdapter => "degenerate component adapter called",
142             OutOfFuel => "all fuel consumed by WebAssembly",
143             AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
144             NullReference => "null reference",
145             NullI31Ref => "null i31 reference",
146             CannotEnterComponent => "cannot enter component instance",
147         };
148         write!(f, "wasm trap: {desc}")
149     }
150 }
151 
152 impl std::error::Error for Trap {}
153 
154 /// Decodes the provided trap information section and attempts to find the trap
155 /// code corresponding to the `offset` specified.
156 ///
157 /// The `section` provided is expected to have been built by
158 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
159 /// offset within the text section of the compilation image.
160 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
161     let mut section = Bytes(section);
162     // NB: this matches the encoding written by `append_to` above.
163     let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
164     let count = usize::try_from(count.get(LittleEndian)).ok()?;
165     let (offsets, traps) =
166         object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
167     debug_assert_eq!(traps.len(), count);
168 
169     // The `offsets` table is sorted in the trap section so perform a binary
170     // search of the contents of this section to find whether `offset` is an
171     // entry in the section. Note that this is a precise search because trap pcs
172     // should always be precise as well as our metadata about them, which means
173     // we expect an exact match to correspond to a trap opcode.
174     //
175     // Once an index is found within the `offsets` array then that same index is
176     // used to lookup from the `traps` list of bytes to get the trap code byte
177     // corresponding to this offset.
178     let offset = u32::try_from(offset).ok()?;
179     let index = offsets
180         .binary_search_by_key(&offset, |val| val.get(LittleEndian))
181         .ok()?;
182     debug_assert!(index < traps.len());
183     let byte = *traps.get(index)?;
184 
185     let trap = Trap::from_u8(byte);
186     debug_assert!(trap.is_some(), "missing mapping for {}", byte);
187     trap
188 }
189