use crate::obj::ELF_WASMTIME_TRAPS;
use object::write::{Object, StandardSegment};
use object::{Bytes, LittleEndian, SectionKind, U32Bytes};
use std::convert::TryFrom;
use std::fmt;
use std::ops::Range;
#[derive(Default)]
pub struct TrapEncodingBuilder {
offsets: Vec<U32Bytes<LittleEndian>>,
traps: Vec<u8>,
last_offset: u32,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct TrapInformation {
pub code_offset: u32,
pub trap_code: Trap,
}
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[allow(missing_docs)]
pub enum Trap {
StackOverflow,
MemoryOutOfBounds,
HeapMisaligned,
TableOutOfBounds,
IndirectCallToNull,
BadSignature,
IntegerOverflow,
IntegerDivisionByZero,
BadConversionToInteger,
UnreachableCodeReached,
Interrupt,
AlwaysTrapAdapter,
OutOfFuel,
AtomicWaitNonSharedMemory,
}
impl fmt::Display for Trap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Trap::*;
let desc = match self {
StackOverflow => "call stack exhausted",
MemoryOutOfBounds => "out of bounds memory access",
HeapMisaligned => "unaligned atomic",
TableOutOfBounds => "undefined element: out of bounds table access",
IndirectCallToNull => "uninitialized element",
BadSignature => "indirect call type mismatch",
IntegerOverflow => "integer overflow",
IntegerDivisionByZero => "integer divide by zero",
BadConversionToInteger => "invalid conversion to integer",
UnreachableCodeReached => "wasm `unreachable` instruction executed",
Interrupt => "interrupt",
AlwaysTrapAdapter => "degenerate component adapter called",
OutOfFuel => "all fuel consumed by WebAssembly",
AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
};
write!(f, "wasm trap: {desc}")
}
}
impl std::error::Error for Trap {}
impl TrapEncodingBuilder {
pub fn push(&mut self, func: Range<u64>, traps: &[TrapInformation]) {
let func_start = u32::try_from(func.start).unwrap();
let func_end = u32::try_from(func.end).unwrap();
assert!(func_start >= self.last_offset);
self.offsets.reserve(traps.len());
self.traps.reserve(traps.len());
for info in traps {
let pos = func_start + info.code_offset;
assert!(pos >= self.last_offset);
self.offsets.push(U32Bytes::new(LittleEndian, pos));
self.traps.push(info.trap_code as u8);
self.last_offset = pos;
}
self.last_offset = func_end;
}
pub fn append_to(self, obj: &mut Object) {
let section = obj.add_section(
obj.segment_name(StandardSegment::Data).to_vec(),
ELF_WASMTIME_TRAPS.as_bytes().to_vec(),
SectionKind::ReadOnlyData,
);
let amt = u32::try_from(self.traps.len()).unwrap();
obj.append_section_data(section, &amt.to_le_bytes(), 1);
obj.append_section_data(section, object::bytes_of_slice(&self.offsets), 1);
obj.append_section_data(section, &self.traps, 1);
}
}
pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
let mut section = Bytes(section);
let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
let count = usize::try_from(count.get(LittleEndian)).ok()?;
let (offsets, traps) =
object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
debug_assert_eq!(traps.len(), count);
let offset = u32::try_from(offset).ok()?;
let index = offsets
.binary_search_by_key(&offset, |val| val.get(LittleEndian))
.ok()?;
debug_assert!(index < traps.len());
let trap = *traps.get(index)?;
macro_rules! check {
($($name:ident)*) => ($(if trap == Trap::$name as u8 {
return Some(Trap::$name);
})*);
}
check! {
StackOverflow
MemoryOutOfBounds
HeapMisaligned
TableOutOfBounds
IndirectCallToNull
BadSignature
IntegerOverflow
IntegerDivisionByZero
BadConversionToInteger
UnreachableCodeReached
Interrupt
AlwaysTrapAdapter
OutOfFuel
AtomicWaitNonSharedMemory
}
if cfg!(debug_assertions) {
panic!("missing mapping for {}", trap);
} else {
None
}
}