///|
/// Every failure mode the decoder can report, with the offset that explains it.
pub enum WasmError {
/// The buffer ended before `needed` bytes were available at `offset`.
UnexpectedEnd(Int, Int)
/// The leading four bytes are not `\0asm`.
BadMagic
/// A section id outside the range assigned by the core specification.
InvalidSectionId(Int)
/// A LEB128 integer with invalid high bits or excessive encoded width.
LebOverflow(Int)
/// Requested offset/length and the allowed half-open byte range.
InvalidReadRange(Int, Int, Int, Int)
/// Valid unsigned encoding outside the implementation's Int range.
ValueOutOfRange(Int, UInt)
/// Version-field offset and raw unsigned version value.
UnsupportedVersion(Int, UInt)
/// Second occurrence's id-byte offset and section kind.
DuplicateSection(Int, SectionId)
/// Current id-byte offset, current kind, previous standard section kind.
InvalidSectionOrder(Int, SectionId, SectionId)
/// Related section offset/kind, local declarations and bodies (imports excluded).
FunctionCodeCountMismatch(Int, SectionId, Int, Int)
/// First unconsumed payload byte and section kind.
TrailingSectionBytes(Int, SectionId)
/// Unsupported or malformed table encoding, with its field offset and reason.
InvalidTableEncoding(Int, String)
/// Data mode/count inconsistency, with file offset and reason.
InvalidDataEncoding(Int, String)
/// An import descriptor whose kind byte is outside the assigned range.
InvalidImportKind(Int)
/// A byte where an opcode was expected that no instruction uses.
UnknownOpcode(Int, Int)
/// A prefixed opcode whose sub-opcode is unassigned: the prefix byte, the
/// sub-opcode, and where they sit.
UnknownPrefixedOpcode(Int, Int, Int)
}
///|
/// An absolute cursor restricted to [start, end) within a byte buffer.
///
/// The shape follows `jtenner/starshine`'s `src/binary` decoder, where a decode
/// step takes `(bytes, offset)` and reports how far it advanced, so callers can
/// walk a stream without hidden global state.
pub struct Reader {
data : Bytes
start : Int
end : Int
mut pos : Int
}
///|
pub fn Reader::new(data : Bytes) -> Reader {
{ data, start: 0, end: data.length(), pos: 0, }
}
///|
/// Construct a bounded reader, checking subtraction before any addition.
pub fn Reader::new_range(
data : Bytes,
start : Int,
length : Int,
) -> Result[Reader, WasmError] {
if start < 0 ||
length < 0 ||
start > data.length() ||
length > data.length() - start {
return Err(WasmError::InvalidReadRange(start, length, 0, data.length()))
}
Ok({ data, start, end: start + length, pos: start, })
}
///|
pub fn Reader::position(self : Reader) -> Int {
self.pos
}
///|
pub fn Reader::remaining(self : Reader) -> Int {
self.end - self.pos
}
///|
pub fn Reader::at_end(self : Reader) -> Bool {
self.pos == self.end
}
///|
/// Move to [start, end], including the end. Failure leaves the cursor unchanged.
pub fn Reader::seek(self : Reader, offset : Int) -> Result[Unit, WasmError] {
if offset < self.start || offset > self.end {
return Err(WasmError::InvalidReadRange(offset, 0, self.start, self.end))
}
self.pos = offset
Ok(())
}
///|
/// Read a single byte, or report the offset that ran out of input.
pub fn Reader::read_byte(self : Reader) -> Result[Int, WasmError] {
if self.pos >= self.end {
return Err(WasmError::UnexpectedEnd(self.pos, 1))
}
let value = self.data[self.pos].to_int()
self.pos = self.pos + 1
Ok(value)
}
///|
/// Read `count` raw bytes and advance past them.
///
/// Negative or excessive counts leave the cursor unchanged. A view is enough:
/// names are decoded straight from it
/// and section payloads are skipped by size rather than copied.
pub fn Reader::read_bytes(
self : Reader,
count : Int,
) -> Result[BytesView, WasmError] {
if count < 0 {
return Err(
WasmError::InvalidReadRange(self.pos, count, self.start, self.end),
)
}
if self.remaining() < count {
return Err(WasmError::UnexpectedEnd(self.pos, count))
}
let slice = self.data[self.pos:self.pos + count]
self.pos = self.pos + count
Ok(slice)
}
///|
/// Read an unsigned LEB128 integer of at most 32 significant bits.
///
/// WebAssembly encodes section payload lengths, vector counts and custom
/// section name lengths this way. Five groups of seven bits cover `u32`, so a
/// fifth byte must terminate and may carry only four payload bits. Non-shortest
/// encodings are accepted within these limits.
pub fn Reader::read_u32_leb(self : Reader) -> Result[UInt, WasmError] {
let start = self.pos
let mut result : UInt = 0
let mut shift = 0
while shift <= 28 {
let byte = match self.read_byte() {
Ok(byte) => byte
Err(_) => return Err(WasmError::UnexpectedEnd(start, 1))
}
// Validate before the shift can discard illegal high bits.
if shift == 28 && (byte & 0xF0) != 0 {
return Err(WasmError::LebOverflow(start))
}
result = result | ((byte & 0x7F).reinterpret_as_uint() << shift)
if (byte & 0x80) == 0 {
return Ok(result)
}
shift = shift + 7
}
Err(WasmError::LebOverflow(start))
}
///|
/// Lengths, counts, indices and Array[Int] operands share this checked conversion.
fn read_u32_as_int(reader : Reader) -> Result[Int, WasmError] {
let start = reader.position()
let value = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let maximum : UInt = 2147483647
if value > maximum {
return Err(WasmError::ValueOutOfRange(start, value))
}
Ok(value.reinterpret_as_int())
}
///|
/// Read a signed LEB128 integer of at most `bits` significant bits.
///
/// Block types, heap types and the `i32.const`/`i64.const` immediates are all
/// signed, and the sign lives in the last group's bit 6, so leaving it out would
/// turn a small negative constant into a large positive one. The `bits` bound is
/// what makes a non-terminating encoding malformed: a group may only continue
/// while fewer than `bits` bits have been consumed.
pub fn Reader::read_signed_leb(
self : Reader,
bits : Int,
) -> Result[Int, WasmError] {
let start = self.pos
let mut result = 0
let mut shift = 0
let mut byte = 0
let mut more = true
while more {
if self.pos >= self.end {
return Err(WasmError::UnexpectedEnd(start, 1))
}
byte = self.data[self.pos].to_int()
self.pos = self.pos + 1
result = result | ((byte & 0x7F) << shift)
shift = shift + 7
if (byte & 0x80) == 0 {
more = false
} else if shift >= bits {
return Err(WasmError::LebOverflow(start))
}
}
// The final group's bit 6 is the sign; anything above it has to be filled in.
if (byte & 0x40) != 0 && shift <= 63 {
result = result | (-1 << shift)
}
Ok(result)
}
///|
/// Read a little-endian bit pattern of `count` bytes as a single integer.
///
/// Floating point constants have no structural meaning for a size analyzer, so
/// they are kept as the bits the file holds rather than converted and back.
pub fn Reader::read_bits(self : Reader, count : Int) -> Result[Int, WasmError] {
let bytes = match self.read_bytes(count) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
let mut value = 0
let mut i = 0
while i < count {
value = value | (bytes[i].to_int() << (8 * i))
i = i + 1
}
Ok(value)
}