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 macro_rules! generate_trap_type { 17 (pub enum Trap { 18 $( 19 $(#[$doc:meta])* 20 $name:ident = $msg:tt, 21 )* 22 }) => { 23 #[non_exhaustive] 24 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 25 #[expect(missing_docs, reason = "self-describing variants")] 26 pub enum Trap { 27 $( 28 $(#[$doc])* 29 $name, 30 )* 31 } 32 33 impl Trap { 34 /// Converts a byte back into a `Trap` if its in-bounds 35 pub fn from_u8(byte: u8) -> Option<Trap> { 36 $( 37 if byte == Trap::$name as u8 { 38 return Some(Trap::$name); 39 } 40 )* 41 None 42 } 43 } 44 45 impl fmt::Display for Trap { 46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 let desc = match self { 48 $(Self::$name => $msg,)* 49 }; 50 write!(f, "wasm trap: {desc}") 51 } 52 } 53 } 54 } 55 56 // The code can be accessed from the c-api, where the possible values are 57 // translated into enum values defined there: 58 // 59 // * the const assertions in c-api/src/trap.rs, and 60 // * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h. 61 // 62 // These need to be kept in sync. 63 generate_trap_type! { 64 pub enum Trap { 65 /// The current stack space was exhausted. 66 StackOverflow = "call stack exhausted", 67 68 /// An out-of-bounds memory access. 69 MemoryOutOfBounds = "out of bounds memory access", 70 71 /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address. 72 HeapMisaligned = "unaligned atomic", 73 74 /// An out-of-bounds access to a table. 75 TableOutOfBounds = "undefined element: out of bounds table access", 76 77 /// Indirect call to a null table entry. 78 IndirectCallToNull = "uninitialized element", 79 80 /// Signature mismatch on indirect call. 81 BadSignature = "indirect call type mismatch", 82 83 /// An integer arithmetic operation caused an overflow. 84 IntegerOverflow = "integer overflow", 85 86 /// An integer division by zero. 87 IntegerDivisionByZero = "integer divide by zero", 88 89 /// Failed float-to-int conversion. 90 BadConversionToInteger = "invalid conversion to integer", 91 92 /// Code that was supposed to have been unreachable was reached. 93 UnreachableCodeReached = "wasm `unreachable` instruction executed", 94 95 /// Execution has potentially run too long and may be interrupted. 96 Interrupt = "interrupt", 97 98 /// When wasm code is configured to consume fuel and it runs out of fuel 99 /// then this trap will be raised. 100 OutOfFuel = "all fuel consumed by WebAssembly", 101 102 /// Used to indicate that a trap was raised by atomic wait operations on non shared memory. 103 AtomicWaitNonSharedMemory = "atomic wait on non-shared memory", 104 105 /// Call to a null reference. 106 NullReference = "null reference", 107 108 /// Attempt to access beyond the bounds of an array. 109 ArrayOutOfBounds = "out of bounds array access", 110 111 /// Attempted an allocation that was too large to succeed. 112 AllocationTooLarge = "allocation size too large", 113 114 /// Attempted to cast a reference to a type that it is not an instance of. 115 CastFailure = "cast failure", 116 117 /// When the `component-model` feature is enabled this trap represents a 118 /// scenario where one component tried to call another component but it 119 /// would have violated the reentrance rules of the component model, 120 /// triggering a trap instead. 121 CannotEnterComponent = "cannot enter component instance", 122 123 /// Async-lifted export failed to produce a result by calling `task.return` 124 /// before returning `STATUS_DONE` and/or after all host tasks completed. 125 NoAsyncResult = "async-lifted export failed to produce a result", 126 127 /// We are suspending to a tag for which there is no active handler. 128 UnhandledTag = "unhandled tag", 129 130 /// Attempt to resume a continuation twice. 131 ContinuationAlreadyConsumed = "continuation already consumed", 132 133 /// A Pulley opcode was executed at runtime when the opcode was disabled at 134 /// compile time. 135 DisabledOpcode = "pulley opcode disabled at compile time was executed", 136 137 /// Async event loop deadlocked; i.e. it cannot make further progress given 138 /// that all host tasks have completed and any/all host-owned stream/future 139 /// handles have been dropped. 140 AsyncDeadlock = "deadlock detected: event loop cannot make further progress", 141 142 /// When the `component-model` feature is enabled this trap represents a 143 /// scenario where a component instance tried to call an import or intrinsic 144 /// when it wasn't allowed to, e.g. from a post-return function. 145 CannotLeaveComponent = "cannot leave component instance", 146 147 /// A synchronous task attempted to make a potentially blocking call prior 148 /// to returning. 149 CannotBlockSyncTask = "cannot block a synchronous task before returning", 150 151 /// A component tried to lift a `char` with an invalid bit pattern. 152 InvalidChar = "invalid `char` bit pattern", 153 154 /// Debug assertion generated for a fused adapter regarding the expected 155 /// completion of a string encoding operation. 156 DebugAssertStringEncodingFinished = "should have finished string encoding", 157 158 /// Debug assertion generated for a fused adapter regarding a string 159 /// encoding operation. 160 DebugAssertEqualCodeUnits = "code units should be equal", 161 162 /// Debug assertion generated for a fused adapter regarding the alignment of 163 /// a pointer. 164 DebugAssertPointerAligned = "pointer should be aligned", 165 166 /// Debug assertion generated for a fused adapter regarding the upper bits 167 /// of a 64-bit value. 168 DebugAssertUpperBitsUnset = "upper bits should be unset", 169 170 /// A component tried to lift or lower a string past the end of its memory. 171 StringOutOfBounds = "string content out-of-bounds", 172 173 /// A component tried to lift or lower a list past the end of its memory. 174 ListOutOfBounds = "list content out-of-bounds", 175 176 /// A component used an invalid discriminant when lowering a variant value. 177 InvalidDiscriminant = "invalid variant discriminant", 178 179 /// A component passed an unaligned pointer when lifting or lowering a 180 /// value. 181 UnalignedPointer = "unaligned pointer", 182 183 /// `task.cancel` was called by a task which has not been cancelled. 184 TaskCancelNotCancelled = "`task.cancel` called by task which has not been cancelled", 185 186 /// `task.return` or `task.cancel` was called more than once for the 187 /// current task. 188 TaskCancelOrReturnTwice = "`task.return` or `task.cancel` called more than once for current task", 189 190 /// `subtask.cancel` was called after terminal status was already 191 /// delivered. 192 SubtaskCancelAfterTerminal = "`subtask.cancel` called after terminal status delivered", 193 194 /// Invalid `task.return` signature and/or options for the current task. 195 TaskReturnInvalid = "invalid `task.return` signature and/or options for current task", 196 197 /// Cannot drop waitable set with waiters in it. 198 WaitableSetDropHasWaiters = "cannot drop waitable set with waiters", 199 200 /// Cannot drop a subtask which has not yet resolved. 201 SubtaskDropNotResolved = "cannot drop a subtask which has not yet resolved", 202 203 /// Start function does not match the expected type. 204 ThreadNewIndirectInvalidType = "start function does not match expected type (currently only `(i32) -> ()` is supported)", 205 206 /// The start function index points to an uninitialized function. 207 ThreadNewIndirectUninitialized = "the start function index points to an uninitialized function", 208 209 /// Backpressure-related intrinsics overflowed the built-in 16-bit 210 /// counter. 211 BackpressureOverflow = "backpressure counter overflow", 212 213 /// Invalid code returned from `callback` of `async`-lifted function. 214 UnsupportedCallbackCode = "unsupported callback code", 215 216 /// Cannot resume a thread which is not suspended. 217 CannotResumeThread = "cannot resume thread which is not suspended", 218 219 // if adding a variant here be sure to update `trap.rs` and `trap.h` as 220 // mentioned above 221 } 222 } 223 224 impl core::error::Error for Trap {} 225 226 /// Decodes the provided trap information section and attempts to find the trap 227 /// code corresponding to the `offset` specified. 228 /// 229 /// The `section` provided is expected to have been built by 230 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative 231 /// offset within the text section of the compilation image. 232 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> { 233 let (offsets, traps) = parse(section)?; 234 235 // The `offsets` table is sorted in the trap section so perform a binary 236 // search of the contents of this section to find whether `offset` is an 237 // entry in the section. Note that this is a precise search because trap pcs 238 // should always be precise as well as our metadata about them, which means 239 // we expect an exact match to correspond to a trap opcode. 240 // 241 // Once an index is found within the `offsets` array then that same index is 242 // used to lookup from the `traps` list of bytes to get the trap code byte 243 // corresponding to this offset. 244 let offset = u32::try_from(offset).ok()?; 245 let index = offsets 246 .binary_search_by_key(&offset, |val| val.get(LittleEndian)) 247 .ok()?; 248 debug_assert!(index < traps.len()); 249 let byte = *traps.get(index)?; 250 251 let trap = Trap::from_u8(byte); 252 debug_assert!(trap.is_some(), "missing mapping for {byte}"); 253 trap 254 } 255 256 fn parse(section: &[u8]) -> Option<(&[U32Bytes<LittleEndian>], &[u8])> { 257 let mut section = Bytes(section); 258 // NB: this matches the encoding written by `append_to` above. 259 let count = section.read::<U32Bytes<LittleEndian>>().ok()?; 260 let count = usize::try_from(count.get(LittleEndian)).ok()?; 261 let (offsets, traps) = 262 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?; 263 debug_assert_eq!(traps.len(), count); 264 Some((offsets, traps)) 265 } 266 267 /// Returns an iterator over all of the traps encoded in `section`, which should 268 /// have been produced by `TrapEncodingBuilder`. 269 pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> { 270 let (offsets, traps) = parse(section)?; 271 Some( 272 offsets 273 .iter() 274 .zip(traps) 275 .map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())), 276 ) 277 } 278