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