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 prior 117 /// to returning. 118 CannotBlockSyncTask, 119 120 /// A component tried to lift a `char` with an invalid bit pattern. 121 InvalidChar, 122 123 /// Debug assertion generated for a fused adapter regarding the expected 124 /// completion of a string encoding operation. 125 DebugAssertStringEncodingFinished, 126 127 /// Debug assertion generated for a fused adapter regarding a string 128 /// encoding operation. 129 DebugAssertEqualCodeUnits, 130 131 /// Debug assertion generated for a fused adapter regarding the expected 132 /// value of the `may_enter` flag for an instance. 133 /// 134 /// TODO: Remove this once 135 /// https://github.com/bytecodealliance/wasmtime/pull/12153 has been merged. 136 DebugAssertMayEnterUnset, 137 138 /// Debug assertion generated for a fused adapter regarding the alignment of 139 /// a pointer. 140 DebugAssertPointerAligned, 141 142 /// Debug assertion generated for a fused adapter regarding the upper bits 143 /// of a 64-bit value. 144 DebugAssertUpperBitsUnset, 145 146 /// A component tried to lift or lower a string past the end of its memory. 147 StringOutOfBounds, 148 149 /// A component tried to lift or lower a list past the end of its memory. 150 ListOutOfBounds, 151 152 /// A component used an invalid discriminant when lowering a variant value. 153 InvalidDiscriminant, 154 155 /// A component passed an unaligned pointer when lifting or lowering a 156 /// value. 157 UnalignedPointer, 158 // if adding a variant here be sure to update the `check!` macro below 159 } 160 161 impl Trap { 162 /// Converts a byte back into a `Trap` if its in-bounds 163 pub fn from_u8(byte: u8) -> Option<Trap> { 164 // FIXME: this could use some sort of derive-like thing to avoid having to 165 // deduplicate the names here. 166 // 167 // This simply converts from the a `u8`, to the `Trap` enum. 168 macro_rules! check { 169 ($($name:ident)*) => ($(if byte == Trap::$name as u8 { 170 return Some(Trap::$name); 171 })*); 172 } 173 174 check! { 175 StackOverflow 176 MemoryOutOfBounds 177 HeapMisaligned 178 TableOutOfBounds 179 IndirectCallToNull 180 BadSignature 181 IntegerOverflow 182 IntegerDivisionByZero 183 BadConversionToInteger 184 UnreachableCodeReached 185 Interrupt 186 AlwaysTrapAdapter 187 OutOfFuel 188 AtomicWaitNonSharedMemory 189 NullReference 190 ArrayOutOfBounds 191 AllocationTooLarge 192 CastFailure 193 CannotEnterComponent 194 NoAsyncResult 195 UnhandledTag 196 ContinuationAlreadyConsumed 197 DisabledOpcode 198 AsyncDeadlock 199 CannotLeaveComponent 200 CannotBlockSyncTask 201 InvalidChar 202 DebugAssertStringEncodingFinished 203 DebugAssertEqualCodeUnits 204 DebugAssertMayEnterUnset 205 DebugAssertPointerAligned 206 DebugAssertUpperBitsUnset 207 StringOutOfBounds 208 ListOutOfBounds 209 InvalidDiscriminant 210 UnalignedPointer 211 } 212 213 None 214 } 215 } 216 217 impl fmt::Display for Trap { 218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 219 use Trap::*; 220 221 let desc = match self { 222 StackOverflow => "call stack exhausted", 223 MemoryOutOfBounds => "out of bounds memory access", 224 HeapMisaligned => "unaligned atomic", 225 TableOutOfBounds => "undefined element: out of bounds table access", 226 IndirectCallToNull => "uninitialized element", 227 BadSignature => "indirect call type mismatch", 228 IntegerOverflow => "integer overflow", 229 IntegerDivisionByZero => "integer divide by zero", 230 BadConversionToInteger => "invalid conversion to integer", 231 UnreachableCodeReached => "wasm `unreachable` instruction executed", 232 Interrupt => "interrupt", 233 AlwaysTrapAdapter => "degenerate component adapter called", 234 OutOfFuel => "all fuel consumed by WebAssembly", 235 AtomicWaitNonSharedMemory => "atomic wait on non-shared memory", 236 NullReference => "null reference", 237 ArrayOutOfBounds => "out of bounds array access", 238 AllocationTooLarge => "allocation size too large", 239 CastFailure => "cast failure", 240 CannotEnterComponent => "cannot enter component instance", 241 NoAsyncResult => "async-lifted export failed to produce a result", 242 UnhandledTag => "unhandled tag", 243 ContinuationAlreadyConsumed => "continuation already consumed", 244 DisabledOpcode => "pulley opcode disabled at compile time was executed", 245 AsyncDeadlock => "deadlock detected: event loop cannot make further progress", 246 CannotLeaveComponent => "cannot leave component instance", 247 CannotBlockSyncTask => "cannot block a synchronous task before returning", 248 InvalidChar => "invalid `char` bit pattern", 249 DebugAssertStringEncodingFinished => "should have finished string encoding", 250 DebugAssertEqualCodeUnits => "code units should be equal", 251 DebugAssertMayEnterUnset => "`may_enter` flag should be unset", 252 DebugAssertPointerAligned => "pointer should be aligned", 253 DebugAssertUpperBitsUnset => "upper bits should be unset", 254 StringOutOfBounds => "string content out-of-bounds", 255 ListOutOfBounds => "list content out-of-bounds", 256 InvalidDiscriminant => "invalid variant discriminant", 257 UnalignedPointer => "unaligned pointer", 258 }; 259 write!(f, "wasm trap: {desc}") 260 } 261 } 262 263 impl core::error::Error for Trap {} 264 265 /// Decodes the provided trap information section and attempts to find the trap 266 /// code corresponding to the `offset` specified. 267 /// 268 /// The `section` provided is expected to have been built by 269 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative 270 /// offset within the text section of the compilation image. 271 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> { 272 let (offsets, traps) = parse(section)?; 273 274 // The `offsets` table is sorted in the trap section so perform a binary 275 // search of the contents of this section to find whether `offset` is an 276 // entry in the section. Note that this is a precise search because trap pcs 277 // should always be precise as well as our metadata about them, which means 278 // we expect an exact match to correspond to a trap opcode. 279 // 280 // Once an index is found within the `offsets` array then that same index is 281 // used to lookup from the `traps` list of bytes to get the trap code byte 282 // corresponding to this offset. 283 let offset = u32::try_from(offset).ok()?; 284 let index = offsets 285 .binary_search_by_key(&offset, |val| val.get(LittleEndian)) 286 .ok()?; 287 debug_assert!(index < traps.len()); 288 let byte = *traps.get(index)?; 289 290 let trap = Trap::from_u8(byte); 291 debug_assert!(trap.is_some(), "missing mapping for {byte}"); 292 trap 293 } 294 295 fn parse(section: &[u8]) -> Option<(&[U32Bytes<LittleEndian>], &[u8])> { 296 let mut section = Bytes(section); 297 // NB: this matches the encoding written by `append_to` above. 298 let count = section.read::<U32Bytes<LittleEndian>>().ok()?; 299 let count = usize::try_from(count.get(LittleEndian)).ok()?; 300 let (offsets, traps) = 301 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?; 302 debug_assert_eq!(traps.len(), count); 303 Some((offsets, traps)) 304 } 305 306 /// Returns an iterator over all of the traps encoded in `section`, which should 307 /// have been produced by `TrapEncodingBuilder`. 308 pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> { 309 let (offsets, traps) = parse(section)?; 310 Some( 311 offsets 312 .iter() 313 .zip(traps) 314 .map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())), 315 ) 316 } 317