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 // * the const assertions 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 wasm code is configured to consume fuel and it runs out of fuel 61 /// then this trap will be raised. 62 OutOfFuel, 63 64 /// Used to indicate that a trap was raised by atomic wait operations on non shared memory. 65 AtomicWaitNonSharedMemory, 66 67 /// Call to a null reference. 68 NullReference, 69 70 /// Attempt to access beyond the bounds of an array. 71 ArrayOutOfBounds, 72 73 /// Attempted an allocation that was too large to succeed. 74 AllocationTooLarge, 75 76 /// Attempted to cast a reference to a type that it is not an instance of. 77 CastFailure, 78 79 /// When the `component-model` feature is enabled this trap represents a 80 /// scenario where one component tried to call another component but it 81 /// would have violated the reentrance rules of the component model, 82 /// triggering a trap instead. 83 CannotEnterComponent, 84 85 /// Async-lifted export failed to produce a result by calling `task.return` 86 /// before returning `STATUS_DONE` and/or after all host tasks completed. 87 NoAsyncResult, 88 89 /// We are suspending to a tag for which there is no active handler. 90 UnhandledTag, 91 92 /// Attempt to resume a continuation twice. 93 ContinuationAlreadyConsumed, 94 95 /// A Pulley opcode was executed at runtime when the opcode was disabled at 96 /// compile time. 97 DisabledOpcode, 98 99 /// Async event loop deadlocked; i.e. it cannot make further progress given 100 /// that all host tasks have completed and any/all host-owned stream/future 101 /// handles have been dropped. 102 AsyncDeadlock, 103 104 /// When the `component-model` feature is enabled this trap represents a 105 /// scenario where a component instance tried to call an import or intrinsic 106 /// when it wasn't allowed to, e.g. from a post-return function. 107 CannotLeaveComponent, 108 109 /// A synchronous task attempted to make a potentially blocking call prior 110 /// to returning. 111 CannotBlockSyncTask, 112 113 /// A component tried to lift a `char` with an invalid bit pattern. 114 InvalidChar, 115 116 /// Debug assertion generated for a fused adapter regarding the expected 117 /// completion of a string encoding operation. 118 DebugAssertStringEncodingFinished, 119 120 /// Debug assertion generated for a fused adapter regarding a string 121 /// encoding operation. 122 DebugAssertEqualCodeUnits, 123 124 /// Debug assertion generated for a fused adapter regarding the alignment of 125 /// a pointer. 126 DebugAssertPointerAligned, 127 128 /// Debug assertion generated for a fused adapter regarding the upper bits 129 /// of a 64-bit value. 130 DebugAssertUpperBitsUnset, 131 132 /// A component tried to lift or lower a string past the end of its memory. 133 StringOutOfBounds, 134 135 /// A component tried to lift or lower a list past the end of its memory. 136 ListOutOfBounds, 137 138 /// A component used an invalid discriminant when lowering a variant value. 139 InvalidDiscriminant, 140 141 /// A component passed an unaligned pointer when lifting or lowering a 142 /// value. 143 UnalignedPointer, 144 // if adding a variant here be sure to update the `check!` macro below, and 145 // remember to update `trap.rs` and `trap.h` as mentioned above 146 } 147 148 impl Trap { 149 /// Converts a byte back into a `Trap` if its in-bounds 150 pub fn from_u8(byte: u8) -> Option<Trap> { 151 // FIXME: this could use some sort of derive-like thing to avoid having to 152 // deduplicate the names here. 153 // 154 // This simply converts from the a `u8`, to the `Trap` enum. 155 macro_rules! check { 156 ($($name:ident)*) => ($(if byte == Trap::$name as u8 { 157 return Some(Trap::$name); 158 })*); 159 } 160 161 check! { 162 StackOverflow 163 MemoryOutOfBounds 164 HeapMisaligned 165 TableOutOfBounds 166 IndirectCallToNull 167 BadSignature 168 IntegerOverflow 169 IntegerDivisionByZero 170 BadConversionToInteger 171 UnreachableCodeReached 172 Interrupt 173 OutOfFuel 174 AtomicWaitNonSharedMemory 175 NullReference 176 ArrayOutOfBounds 177 AllocationTooLarge 178 CastFailure 179 CannotEnterComponent 180 NoAsyncResult 181 UnhandledTag 182 ContinuationAlreadyConsumed 183 DisabledOpcode 184 AsyncDeadlock 185 CannotLeaveComponent 186 CannotBlockSyncTask 187 InvalidChar 188 DebugAssertStringEncodingFinished 189 DebugAssertEqualCodeUnits 190 DebugAssertPointerAligned 191 DebugAssertUpperBitsUnset 192 StringOutOfBounds 193 ListOutOfBounds 194 InvalidDiscriminant 195 UnalignedPointer 196 } 197 198 None 199 } 200 } 201 202 impl fmt::Display for Trap { 203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 204 use Trap::*; 205 206 let desc = match self { 207 StackOverflow => "call stack exhausted", 208 MemoryOutOfBounds => "out of bounds memory access", 209 HeapMisaligned => "unaligned atomic", 210 TableOutOfBounds => "undefined element: out of bounds table access", 211 IndirectCallToNull => "uninitialized element", 212 BadSignature => "indirect call type mismatch", 213 IntegerOverflow => "integer overflow", 214 IntegerDivisionByZero => "integer divide by zero", 215 BadConversionToInteger => "invalid conversion to integer", 216 UnreachableCodeReached => "wasm `unreachable` instruction executed", 217 Interrupt => "interrupt", 218 OutOfFuel => "all fuel consumed by WebAssembly", 219 AtomicWaitNonSharedMemory => "atomic wait on non-shared memory", 220 NullReference => "null reference", 221 ArrayOutOfBounds => "out of bounds array access", 222 AllocationTooLarge => "allocation size too large", 223 CastFailure => "cast failure", 224 CannotEnterComponent => "cannot enter component instance", 225 NoAsyncResult => "async-lifted export failed to produce a result", 226 UnhandledTag => "unhandled tag", 227 ContinuationAlreadyConsumed => "continuation already consumed", 228 DisabledOpcode => "pulley opcode disabled at compile time was executed", 229 AsyncDeadlock => "deadlock detected: event loop cannot make further progress", 230 CannotLeaveComponent => "cannot leave component instance", 231 CannotBlockSyncTask => "cannot block a synchronous task before returning", 232 InvalidChar => "invalid `char` bit pattern", 233 DebugAssertStringEncodingFinished => "should have finished string encoding", 234 DebugAssertEqualCodeUnits => "code units should be equal", 235 DebugAssertPointerAligned => "pointer should be aligned", 236 DebugAssertUpperBitsUnset => "upper bits should be unset", 237 StringOutOfBounds => "string content out-of-bounds", 238 ListOutOfBounds => "list content out-of-bounds", 239 InvalidDiscriminant => "invalid variant discriminant", 240 UnalignedPointer => "unaligned pointer", 241 }; 242 write!(f, "wasm trap: {desc}") 243 } 244 } 245 246 impl core::error::Error for Trap {} 247 248 /// Decodes the provided trap information section and attempts to find the trap 249 /// code corresponding to the `offset` specified. 250 /// 251 /// The `section` provided is expected to have been built by 252 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative 253 /// offset within the text section of the compilation image. 254 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> { 255 let (offsets, traps) = parse(section)?; 256 257 // The `offsets` table is sorted in the trap section so perform a binary 258 // search of the contents of this section to find whether `offset` is an 259 // entry in the section. Note that this is a precise search because trap pcs 260 // should always be precise as well as our metadata about them, which means 261 // we expect an exact match to correspond to a trap opcode. 262 // 263 // Once an index is found within the `offsets` array then that same index is 264 // used to lookup from the `traps` list of bytes to get the trap code byte 265 // corresponding to this offset. 266 let offset = u32::try_from(offset).ok()?; 267 let index = offsets 268 .binary_search_by_key(&offset, |val| val.get(LittleEndian)) 269 .ok()?; 270 debug_assert!(index < traps.len()); 271 let byte = *traps.get(index)?; 272 273 let trap = Trap::from_u8(byte); 274 debug_assert!(trap.is_some(), "missing mapping for {byte}"); 275 trap 276 } 277 278 fn parse(section: &[u8]) -> Option<(&[U32Bytes<LittleEndian>], &[u8])> { 279 let mut section = Bytes(section); 280 // NB: this matches the encoding written by `append_to` above. 281 let count = section.read::<U32Bytes<LittleEndian>>().ok()?; 282 let count = usize::try_from(count.get(LittleEndian)).ok()?; 283 let (offsets, traps) = 284 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?; 285 debug_assert_eq!(traps.len(), count); 286 Some((offsets, traps)) 287 } 288 289 /// Returns an iterator over all of the traps encoded in `section`, which should 290 /// have been produced by `TrapEncodingBuilder`. 291 pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> { 292 let (offsets, traps) = parse(section)?; 293 Some( 294 offsets 295 .iter() 296 .zip(traps) 297 .map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())), 298 ) 299 } 300