use crate::binary_reader::WASM_MAGIC_NUMBER;
use crate::CoreTypeSectionReader;
use crate::{
limits::MAX_WASM_MODULE_SIZE, BinaryReader, BinaryReaderError, ComponentCanonicalSectionReader,
ComponentExportSectionReader, ComponentImportSectionReader, ComponentInstanceSectionReader,
ComponentStartFunction, ComponentTypeSectionReader, CustomSectionReader, DataSectionReader,
ElementSectionReader, ExportSectionReader, FromReader, FunctionBody, FunctionSectionReader,
GlobalSectionReader, ImportSectionReader, InstanceSectionReader, MemorySectionReader, Result,
SectionLimited, TableSectionReader, TagSectionReader, TypeSectionReader,
};
use std::convert::TryInto;
use std::fmt;
use std::iter;
use std::ops::Range;
pub(crate) const WASM_MODULE_VERSION: u16 = 0x1;
pub(crate) const WASM_COMPONENT_VERSION: u16 = 0xd;
const KIND_MODULE: u16 = 0x00;
const KIND_COMPONENT: u16 = 0x01;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Encoding {
Module,
Component,
}
#[derive(Debug, Clone)]
pub struct Parser {
state: State,
offset: u64,
max_size: u64,
encoding: Encoding,
}
#[derive(Debug, Clone)]
enum State {
Header,
SectionStart,
FunctionBody { remaining: u32, len: u32 },
}
#[derive(Debug)]
pub enum Chunk<'a> {
NeedMoreData(u64),
Parsed {
consumed: usize,
payload: Payload<'a>,
},
}
pub enum Payload<'a> {
Version {
num: u16,
encoding: Encoding,
range: Range<usize>,
},
TypeSection(TypeSectionReader<'a>),
ImportSection(ImportSectionReader<'a>),
FunctionSection(FunctionSectionReader<'a>),
TableSection(TableSectionReader<'a>),
MemorySection(MemorySectionReader<'a>),
TagSection(TagSectionReader<'a>),
GlobalSection(GlobalSectionReader<'a>),
ExportSection(ExportSectionReader<'a>),
StartSection {
func: u32,
range: Range<usize>,
},
ElementSection(ElementSectionReader<'a>),
DataCountSection {
count: u32,
range: Range<usize>,
},
DataSection(DataSectionReader<'a>),
CodeSectionStart {
count: u32,
range: Range<usize>,
size: u32,
},
CodeSectionEntry(FunctionBody<'a>),
ModuleSection {
parser: Parser,
range: Range<usize>,
},
InstanceSection(InstanceSectionReader<'a>),
CoreTypeSection(CoreTypeSectionReader<'a>),
ComponentSection {
parser: Parser,
range: Range<usize>,
},
ComponentInstanceSection(ComponentInstanceSectionReader<'a>),
ComponentAliasSection(SectionLimited<'a, crate::ComponentAlias<'a>>),
ComponentTypeSection(ComponentTypeSectionReader<'a>),
ComponentCanonicalSection(ComponentCanonicalSectionReader<'a>),
ComponentStartSection {
start: ComponentStartFunction,
range: Range<usize>,
},
ComponentImportSection(ComponentImportSectionReader<'a>),
ComponentExportSection(ComponentExportSectionReader<'a>),
CustomSection(CustomSectionReader<'a>),
UnknownSection {
id: u8,
contents: &'a [u8],
range: Range<usize>,
},
End(usize),
}
const CUSTOM_SECTION: u8 = 0;
const TYPE_SECTION: u8 = 1;
const IMPORT_SECTION: u8 = 2;
const FUNCTION_SECTION: u8 = 3;
const TABLE_SECTION: u8 = 4;
const MEMORY_SECTION: u8 = 5;
const GLOBAL_SECTION: u8 = 6;
const EXPORT_SECTION: u8 = 7;
const START_SECTION: u8 = 8;
const ELEMENT_SECTION: u8 = 9;
const CODE_SECTION: u8 = 10;
const DATA_SECTION: u8 = 11;
const DATA_COUNT_SECTION: u8 = 12;
const TAG_SECTION: u8 = 13;
const COMPONENT_MODULE_SECTION: u8 = 1;
const COMPONENT_CORE_INSTANCE_SECTION: u8 = 2;
const COMPONENT_CORE_TYPE_SECTION: u8 = 3;
const COMPONENT_SECTION: u8 = 4;
const COMPONENT_INSTANCE_SECTION: u8 = 5;
const COMPONENT_ALIAS_SECTION: u8 = 6;
const COMPONENT_TYPE_SECTION: u8 = 7;
const COMPONENT_CANONICAL_SECTION: u8 = 8;
const COMPONENT_START_SECTION: u8 = 9;
const COMPONENT_IMPORT_SECTION: u8 = 10;
const COMPONENT_EXPORT_SECTION: u8 = 11;
impl Parser {
pub fn new(offset: u64) -> Parser {
Parser {
state: State::Header,
offset,
max_size: u64::MAX,
encoding: Encoding::Module,
}
}
pub fn is_core_wasm(bytes: &[u8]) -> bool {
const HEADER: [u8; 8] = [
WASM_MAGIC_NUMBER[0],
WASM_MAGIC_NUMBER[1],
WASM_MAGIC_NUMBER[2],
WASM_MAGIC_NUMBER[3],
WASM_MODULE_VERSION.to_le_bytes()[0],
WASM_MODULE_VERSION.to_le_bytes()[1],
KIND_MODULE.to_le_bytes()[0],
KIND_MODULE.to_le_bytes()[1],
];
bytes.starts_with(&HEADER)
}
pub fn is_component(bytes: &[u8]) -> bool {
const HEADER: [u8; 8] = [
WASM_MAGIC_NUMBER[0],
WASM_MAGIC_NUMBER[1],
WASM_MAGIC_NUMBER[2],
WASM_MAGIC_NUMBER[3],
WASM_COMPONENT_VERSION.to_le_bytes()[0],
WASM_COMPONENT_VERSION.to_le_bytes()[1],
KIND_COMPONENT.to_le_bytes()[0],
KIND_COMPONENT.to_le_bytes()[1],
];
bytes.starts_with(&HEADER)
}
pub fn parse<'a>(&mut self, data: &'a [u8], eof: bool) -> Result<Chunk<'a>> {
let (data, eof) = if usize_to_u64(data.len()) > self.max_size {
(&data[..(self.max_size as usize)], true)
} else {
(data, eof)
};
let mut reader = BinaryReader::new_with_offset(data, self.offset as usize);
match self.parse_reader(&mut reader, eof) {
Ok(payload) => {
self.offset += usize_to_u64(reader.position);
self.max_size -= usize_to_u64(reader.position);
Ok(Chunk::Parsed {
consumed: reader.position,
payload,
})
}
Err(e) => {
if eof {
return Err(e);
}
match e.inner.needed_hint {
Some(hint) => Ok(Chunk::NeedMoreData(usize_to_u64(hint))),
None => Err(e),
}
}
}
}
fn parse_reader<'a>(
&mut self,
reader: &mut BinaryReader<'a>,
eof: bool,
) -> Result<Payload<'a>> {
use Payload::*;
match self.state {
State::Header => {
let start = reader.original_position();
let header_version = reader.read_header_version()?;
self.encoding = match (header_version >> 16) as u16 {
KIND_MODULE => Encoding::Module,
KIND_COMPONENT => Encoding::Component,
_ => bail!(start + 4, "unknown binary version: {header_version:#10x}"),
};
let num = header_version as u16;
self.state = State::SectionStart;
Ok(Version {
num,
encoding: self.encoding,
range: start..reader.original_position(),
})
}
State::SectionStart => {
if eof && reader.bytes_remaining() == 0 {
return Ok(Payload::End(reader.original_position()));
}
let id_pos = reader.position;
let id = reader.read_u8()?;
if id & 0x80 != 0 {
return Err(BinaryReaderError::new("malformed section id", id_pos));
}
let len_pos = reader.original_position();
let mut len = reader.read_var_u32()?;
let section_overflow = self
.max_size
.checked_sub(usize_to_u64(reader.position))
.and_then(|s| s.checked_sub(len.into()))
.is_none();
if section_overflow {
return Err(BinaryReaderError::new("section too large", len_pos));
}
match (self.encoding, id) {
(_, 0) => section(reader, len, CustomSectionReader::new, CustomSection),
(Encoding::Module, TYPE_SECTION) => {
section(reader, len, TypeSectionReader::new, TypeSection)
}
(Encoding::Module, IMPORT_SECTION) => {
section(reader, len, ImportSectionReader::new, ImportSection)
}
(Encoding::Module, FUNCTION_SECTION) => {
section(reader, len, FunctionSectionReader::new, FunctionSection)
}
(Encoding::Module, TABLE_SECTION) => {
section(reader, len, TableSectionReader::new, TableSection)
}
(Encoding::Module, MEMORY_SECTION) => {
section(reader, len, MemorySectionReader::new, MemorySection)
}
(Encoding::Module, GLOBAL_SECTION) => {
section(reader, len, GlobalSectionReader::new, GlobalSection)
}
(Encoding::Module, EXPORT_SECTION) => {
section(reader, len, ExportSectionReader::new, ExportSection)
}
(Encoding::Module, START_SECTION) => {
let (func, range) = single_item(reader, len, "start")?;
Ok(StartSection { func, range })
}
(Encoding::Module, ELEMENT_SECTION) => {
section(reader, len, ElementSectionReader::new, ElementSection)
}
(Encoding::Module, CODE_SECTION) => {
let start = reader.original_position();
let count = delimited(reader, &mut len, |r| r.read_var_u32())?;
let range = start..reader.original_position() + len as usize;
self.state = State::FunctionBody {
remaining: count,
len,
};
Ok(CodeSectionStart {
count,
range,
size: len,
})
}
(Encoding::Module, DATA_SECTION) => {
section(reader, len, DataSectionReader::new, DataSection)
}
(Encoding::Module, DATA_COUNT_SECTION) => {
let (count, range) = single_item(reader, len, "data count")?;
Ok(DataCountSection { count, range })
}
(Encoding::Module, TAG_SECTION) => {
section(reader, len, TagSectionReader::new, TagSection)
}
(Encoding::Component, COMPONENT_MODULE_SECTION)
| (Encoding::Component, COMPONENT_SECTION) => {
if len as usize > MAX_WASM_MODULE_SIZE {
bail!(
len_pos,
"{} section is too large",
if id == 1 { "module" } else { "component " }
);
}
let range =
reader.original_position()..reader.original_position() + len as usize;
self.max_size -= u64::from(len);
self.offset += u64::from(len);
let mut parser = Parser::new(usize_to_u64(reader.original_position()));
parser.max_size = len.into();
Ok(match id {
1 => ModuleSection { parser, range },
4 => ComponentSection { parser, range },
_ => unreachable!(),
})
}
(Encoding::Component, COMPONENT_CORE_INSTANCE_SECTION) => {
section(reader, len, InstanceSectionReader::new, InstanceSection)
}
(Encoding::Component, COMPONENT_CORE_TYPE_SECTION) => {
section(reader, len, CoreTypeSectionReader::new, CoreTypeSection)
}
(Encoding::Component, COMPONENT_INSTANCE_SECTION) => section(
reader,
len,
ComponentInstanceSectionReader::new,
ComponentInstanceSection,
),
(Encoding::Component, COMPONENT_ALIAS_SECTION) => {
section(reader, len, SectionLimited::new, ComponentAliasSection)
}
(Encoding::Component, COMPONENT_TYPE_SECTION) => section(
reader,
len,
ComponentTypeSectionReader::new,
ComponentTypeSection,
),
(Encoding::Component, COMPONENT_CANONICAL_SECTION) => section(
reader,
len,
ComponentCanonicalSectionReader::new,
ComponentCanonicalSection,
),
(Encoding::Component, COMPONENT_START_SECTION) => {
let (start, range) = single_item(reader, len, "component start")?;
Ok(ComponentStartSection { start, range })
}
(Encoding::Component, COMPONENT_IMPORT_SECTION) => section(
reader,
len,
ComponentImportSectionReader::new,
ComponentImportSection,
),
(Encoding::Component, COMPONENT_EXPORT_SECTION) => section(
reader,
len,
ComponentExportSectionReader::new,
ComponentExportSection,
),
(_, id) => {
let offset = reader.original_position();
let contents = reader.read_bytes(len as usize)?;
let range = offset..offset + len as usize;
Ok(UnknownSection {
id,
contents,
range,
})
}
}
}
State::FunctionBody {
remaining: 0,
len: 0,
} => {
self.state = State::SectionStart;
self.parse_reader(reader, eof)
}
State::FunctionBody { remaining: 0, len } => {
debug_assert!(len > 0);
let offset = reader.original_position();
Err(BinaryReaderError::new(
"trailing bytes at end of section",
offset,
))
}
State::FunctionBody { remaining, mut len } => {
let body = delimited(reader, &mut len, |r| {
let size = r.read_var_u32()?;
let offset = r.original_position();
Ok(FunctionBody::new(offset, r.read_bytes(size as usize)?))
})?;
self.state = State::FunctionBody {
remaining: remaining - 1,
len,
};
Ok(CodeSectionEntry(body))
}
}
}
pub fn parse_all(self, mut data: &[u8]) -> impl Iterator<Item = Result<Payload>> {
let mut stack = Vec::new();
let mut cur = self;
let mut done = false;
iter::from_fn(move || {
if done {
return None;
}
let payload = match cur.parse(data, true) {
Err(e) => {
done = true;
return Some(Err(e));
}
Ok(Chunk::NeedMoreData(_)) => unreachable!(),
Ok(Chunk::Parsed { payload, consumed }) => {
data = &data[consumed..];
payload
}
};
match &payload {
Payload::ModuleSection { parser, .. }
| Payload::ComponentSection { parser, .. } => {
stack.push(cur.clone());
cur = parser.clone();
}
Payload::End(_) => match stack.pop() {
Some(p) => cur = p,
None => done = true,
},
_ => {}
}
Some(Ok(payload))
})
}
pub fn skip_section(&mut self) {
let skip = match self.state {
State::FunctionBody { remaining: _, len } => len,
_ => panic!("wrong state to call `skip_section`"),
};
self.offset += u64::from(skip);
self.max_size -= u64::from(skip);
self.state = State::SectionStart;
}
}
fn usize_to_u64(a: usize) -> u64 {
a.try_into().unwrap()
}
fn section<'a, T>(
reader: &mut BinaryReader<'a>,
len: u32,
ctor: fn(&'a [u8], usize) -> Result<T>,
variant: fn(T) -> Payload<'a>,
) -> Result<Payload<'a>> {
let offset = reader.original_position();
let payload = reader.read_bytes(len as usize)?;
let reader = ctor(payload, offset).map_err(clear_hint)?;
Ok(variant(reader))
}
fn single_item<'a, T>(
reader: &mut BinaryReader<'a>,
len: u32,
desc: &str,
) -> Result<(T, Range<usize>)>
where
T: FromReader<'a>,
{
let range = reader.original_position()..reader.original_position() + len as usize;
let mut content = BinaryReader::new_with_offset(reader.read_bytes(len as usize)?, range.start);
let ret = content.read().map_err(clear_hint)?;
if !content.eof() {
bail!(
content.original_position(),
"unexpected content in the {desc} section",
);
}
Ok((ret, range))
}
fn delimited<'a, T>(
reader: &mut BinaryReader<'a>,
len: &mut u32,
f: impl FnOnce(&mut BinaryReader<'a>) -> Result<T>,
) -> Result<T> {
let start = reader.position;
let ret = f(reader)?;
*len = match (reader.position - start)
.try_into()
.ok()
.and_then(|i| len.checked_sub(i))
{
Some(i) => i,
None => return Err(BinaryReaderError::new("unexpected end-of-file", start)),
};
Ok(ret)
}
impl Default for Parser {
fn default() -> Parser {
Parser::new(0)
}
}
impl Payload<'_> {
pub fn as_section(&self) -> Option<(u8, Range<usize>)> {
use Payload::*;
match self {
Version { .. } => None,
TypeSection(s) => Some((TYPE_SECTION, s.range())),
ImportSection(s) => Some((IMPORT_SECTION, s.range())),
FunctionSection(s) => Some((FUNCTION_SECTION, s.range())),
TableSection(s) => Some((TABLE_SECTION, s.range())),
MemorySection(s) => Some((MEMORY_SECTION, s.range())),
TagSection(s) => Some((TAG_SECTION, s.range())),
GlobalSection(s) => Some((GLOBAL_SECTION, s.range())),
ExportSection(s) => Some((EXPORT_SECTION, s.range())),
ElementSection(s) => Some((ELEMENT_SECTION, s.range())),
DataSection(s) => Some((DATA_SECTION, s.range())),
StartSection { range, .. } => Some((START_SECTION, range.clone())),
DataCountSection { range, .. } => Some((DATA_COUNT_SECTION, range.clone())),
CodeSectionStart { range, .. } => Some((CODE_SECTION, range.clone())),
CodeSectionEntry(_) => None,
ModuleSection { range, .. } => Some((COMPONENT_MODULE_SECTION, range.clone())),
InstanceSection(s) => Some((COMPONENT_CORE_INSTANCE_SECTION, s.range())),
CoreTypeSection(s) => Some((COMPONENT_CORE_TYPE_SECTION, s.range())),
ComponentSection { range, .. } => Some((COMPONENT_SECTION, range.clone())),
ComponentInstanceSection(s) => Some((COMPONENT_INSTANCE_SECTION, s.range())),
ComponentAliasSection(s) => Some((COMPONENT_ALIAS_SECTION, s.range())),
ComponentTypeSection(s) => Some((COMPONENT_TYPE_SECTION, s.range())),
ComponentCanonicalSection(s) => Some((COMPONENT_CANONICAL_SECTION, s.range())),
ComponentStartSection { range, .. } => Some((COMPONENT_START_SECTION, range.clone())),
ComponentImportSection(s) => Some((COMPONENT_IMPORT_SECTION, s.range())),
ComponentExportSection(s) => Some((COMPONENT_EXPORT_SECTION, s.range())),
CustomSection(c) => Some((CUSTOM_SECTION, c.range())),
UnknownSection { id, range, .. } => Some((*id, range.clone())),
End(_) => None,
}
}
}
impl fmt::Debug for Payload<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Payload::*;
match self {
Version {
num,
encoding,
range,
} => f
.debug_struct("Version")
.field("num", num)
.field("encoding", encoding)
.field("range", range)
.finish(),
TypeSection(_) => f.debug_tuple("TypeSection").field(&"...").finish(),
ImportSection(_) => f.debug_tuple("ImportSection").field(&"...").finish(),
FunctionSection(_) => f.debug_tuple("FunctionSection").field(&"...").finish(),
TableSection(_) => f.debug_tuple("TableSection").field(&"...").finish(),
MemorySection(_) => f.debug_tuple("MemorySection").field(&"...").finish(),
TagSection(_) => f.debug_tuple("TagSection").field(&"...").finish(),
GlobalSection(_) => f.debug_tuple("GlobalSection").field(&"...").finish(),
ExportSection(_) => f.debug_tuple("ExportSection").field(&"...").finish(),
ElementSection(_) => f.debug_tuple("ElementSection").field(&"...").finish(),
DataSection(_) => f.debug_tuple("DataSection").field(&"...").finish(),
StartSection { func, range } => f
.debug_struct("StartSection")
.field("func", func)
.field("range", range)
.finish(),
DataCountSection { count, range } => f
.debug_struct("DataCountSection")
.field("count", count)
.field("range", range)
.finish(),
CodeSectionStart { count, range, size } => f
.debug_struct("CodeSectionStart")
.field("count", count)
.field("range", range)
.field("size", size)
.finish(),
CodeSectionEntry(_) => f.debug_tuple("CodeSectionEntry").field(&"...").finish(),
ModuleSection { parser: _, range } => f
.debug_struct("ModuleSection")
.field("range", range)
.finish(),
InstanceSection(_) => f.debug_tuple("InstanceSection").field(&"...").finish(),
CoreTypeSection(_) => f.debug_tuple("CoreTypeSection").field(&"...").finish(),
ComponentSection { parser: _, range } => f
.debug_struct("ComponentSection")
.field("range", range)
.finish(),
ComponentInstanceSection(_) => f
.debug_tuple("ComponentInstanceSection")
.field(&"...")
.finish(),
ComponentAliasSection(_) => f
.debug_tuple("ComponentAliasSection")
.field(&"...")
.finish(),
ComponentTypeSection(_) => f.debug_tuple("ComponentTypeSection").field(&"...").finish(),
ComponentCanonicalSection(_) => f
.debug_tuple("ComponentCanonicalSection")
.field(&"...")
.finish(),
ComponentStartSection { .. } => f
.debug_tuple("ComponentStartSection")
.field(&"...")
.finish(),
ComponentImportSection(_) => f
.debug_tuple("ComponentImportSection")
.field(&"...")
.finish(),
ComponentExportSection(_) => f
.debug_tuple("ComponentExportSection")
.field(&"...")
.finish(),
CustomSection(c) => f.debug_tuple("CustomSection").field(c).finish(),
UnknownSection { id, range, .. } => f
.debug_struct("UnknownSection")
.field("id", id)
.field("range", range)
.finish(),
End(offset) => f.debug_tuple("End").field(offset).finish(),
}
}
}
fn clear_hint(mut err: BinaryReaderError) -> BinaryReaderError {
err.inner.needed_hint = None;
err
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_matches {
($a:expr, $b:pat $(,)?) => {
match $a {
$b => {}
a => panic!("`{:?}` doesn't match `{}`", a, stringify!($b)),
}
};
}
#[test]
fn header() {
assert!(Parser::default().parse(&[], true).is_err());
assert_matches!(
Parser::default().parse(&[], false),
Ok(Chunk::NeedMoreData(4)),
);
assert_matches!(
Parser::default().parse(b"\0", false),
Ok(Chunk::NeedMoreData(3)),
);
assert_matches!(
Parser::default().parse(b"\0asm", false),
Ok(Chunk::NeedMoreData(4)),
);
assert_matches!(
Parser::default().parse(b"\0asm\x01\0\0\0", false),
Ok(Chunk::Parsed {
consumed: 8,
payload: Payload::Version { num: 1, .. },
}),
);
}
#[test]
fn header_iter() {
for _ in Parser::default().parse_all(&[]) {}
for _ in Parser::default().parse_all(b"\0") {}
for _ in Parser::default().parse_all(b"\0asm") {}
for _ in Parser::default().parse_all(b"\0asm\x01\x01\x01\x01") {}
}
fn parser_after_header() -> Parser {
let mut p = Parser::default();
assert_matches!(
p.parse(b"\0asm\x01\0\0\0", false),
Ok(Chunk::Parsed {
consumed: 8,
payload: Payload::Version {
num: WASM_MODULE_VERSION,
encoding: Encoding::Module,
..
},
}),
);
p
}
fn parser_after_component_header() -> Parser {
let mut p = Parser::default();
assert_matches!(
p.parse(b"\0asm\x0d\0\x01\0", false),
Ok(Chunk::Parsed {
consumed: 8,
payload: Payload::Version {
num: WASM_COMPONENT_VERSION,
encoding: Encoding::Component,
..
},
}),
);
p
}
#[test]
fn start_section() {
assert_matches!(
parser_after_header().parse(&[], false),
Ok(Chunk::NeedMoreData(1)),
);
assert!(parser_after_header().parse(&[8], true).is_err());
assert!(parser_after_header().parse(&[8, 1], true).is_err());
assert!(parser_after_header().parse(&[8, 2], true).is_err());
assert_matches!(
parser_after_header().parse(&[8], false),
Ok(Chunk::NeedMoreData(1)),
);
assert_matches!(
parser_after_header().parse(&[8, 1], false),
Ok(Chunk::NeedMoreData(1)),
);
assert_matches!(
parser_after_header().parse(&[8, 2], false),
Ok(Chunk::NeedMoreData(2)),
);
assert_matches!(
parser_after_header().parse(&[8, 1, 1], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::StartSection { func: 1, .. },
}),
);
assert!(parser_after_header().parse(&[8, 2, 1, 1], false).is_err());
assert!(parser_after_header().parse(&[8, 0], false).is_err());
}
#[test]
fn end_works() {
assert_matches!(
parser_after_header().parse(&[], true),
Ok(Chunk::Parsed {
consumed: 0,
payload: Payload::End(8),
}),
);
}
#[test]
fn type_section() {
assert!(parser_after_header().parse(&[1], true).is_err());
assert!(parser_after_header().parse(&[1, 0], false).is_err());
assert!(parser_after_header().parse(&[8, 2], true).is_err());
assert_matches!(
parser_after_header().parse(&[1], false),
Ok(Chunk::NeedMoreData(1)),
);
assert_matches!(
parser_after_header().parse(&[1, 1], false),
Ok(Chunk::NeedMoreData(1)),
);
assert_matches!(
parser_after_header().parse(&[1, 1, 1], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::TypeSection(_),
}),
);
assert_matches!(
parser_after_header().parse(&[1, 1, 1, 2, 3, 4], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::TypeSection(_),
}),
);
}
#[test]
fn custom_section() {
assert!(parser_after_header().parse(&[0], true).is_err());
assert!(parser_after_header().parse(&[0, 0], false).is_err());
assert!(parser_after_header().parse(&[0, 1, 1], false).is_err());
assert_matches!(
parser_after_header().parse(&[0, 2, 1], false),
Ok(Chunk::NeedMoreData(1)),
);
assert_matches!(
parser_after_header().parse(&[0, 1, 0], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::CustomSection(CustomSectionReader {
name: "",
data_offset: 11,
data: b"",
range: Range { start: 10, end: 11 },
}),
}),
);
assert_matches!(
parser_after_header().parse(&[0, 2, 1, b'a'], false),
Ok(Chunk::Parsed {
consumed: 4,
payload: Payload::CustomSection(CustomSectionReader {
name: "a",
data_offset: 12,
data: b"",
range: Range { start: 10, end: 12 },
}),
}),
);
assert_matches!(
parser_after_header().parse(&[0, 2, 0, b'a'], false),
Ok(Chunk::Parsed {
consumed: 4,
payload: Payload::CustomSection(CustomSectionReader {
name: "",
data_offset: 11,
data: b"a",
range: Range { start: 10, end: 12 },
}),
}),
);
}
#[test]
fn function_section() {
assert!(parser_after_header().parse(&[10], true).is_err());
assert!(parser_after_header().parse(&[10, 0], true).is_err());
assert!(parser_after_header().parse(&[10, 1], true).is_err());
assert_matches!(
parser_after_header().parse(&[10], false),
Ok(Chunk::NeedMoreData(1))
);
assert_matches!(
parser_after_header().parse(&[10, 1], false),
Ok(Chunk::NeedMoreData(1))
);
let mut p = parser_after_header();
assert_matches!(
p.parse(&[10, 1, 0], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::CodeSectionStart { count: 0, .. },
}),
);
assert_matches!(
p.parse(&[], true),
Ok(Chunk::Parsed {
consumed: 0,
payload: Payload::End(11),
}),
);
let mut p = parser_after_header();
assert_matches!(
p.parse(&[10, 2, 1, 0], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::CodeSectionStart { count: 1, .. },
}),
);
assert_matches!(
p.parse(&[0], false),
Ok(Chunk::Parsed {
consumed: 1,
payload: Payload::CodeSectionEntry(_),
}),
);
assert_matches!(
p.parse(&[], true),
Ok(Chunk::Parsed {
consumed: 0,
payload: Payload::End(12),
}),
);
let mut p = parser_after_header();
assert_matches!(
p.parse(&[10, 1, 1], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::CodeSectionStart { count: 1, .. },
}),
);
assert_eq!(
p.parse(&[0], false).unwrap_err().message(),
"unexpected end-of-file"
);
let mut p = parser_after_header();
assert_matches!(
p.parse(&[10, 2, 2], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::CodeSectionStart { count: 2, .. },
}),
);
assert_matches!(
p.parse(&[0], false),
Ok(Chunk::Parsed {
consumed: 1,
payload: Payload::CodeSectionEntry(_),
}),
);
assert_matches!(p.parse(&[], false), Ok(Chunk::NeedMoreData(1)));
assert_eq!(
p.parse(&[0], false).unwrap_err().message(),
"unexpected end-of-file",
);
let mut p = parser_after_header();
assert_matches!(
p.parse(&[10, 3, 1], false),
Ok(Chunk::Parsed {
consumed: 3,
payload: Payload::CodeSectionStart { count: 1, .. },
}),
);
assert_matches!(
p.parse(&[0], false),
Ok(Chunk::Parsed {
consumed: 1,
payload: Payload::CodeSectionEntry(_),
}),
);
assert_eq!(
p.parse(&[0], false).unwrap_err().message(),
"trailing bytes at end of section",
);
}
#[test]
fn single_module() {
let mut p = parser_after_component_header();
assert_matches!(p.parse(&[4], false), Ok(Chunk::NeedMoreData(1)));
let mut sub = match p.parse(&[1, 8], false) {
Ok(Chunk::Parsed {
consumed: 2,
payload: Payload::ModuleSection { parser, .. },
}) => parser,
other => panic!("bad parse {:?}", other),
};
assert_matches!(sub.parse(&[], false), Ok(Chunk::NeedMoreData(4)));
assert_matches!(sub.parse(b"\0asm", false), Ok(Chunk::NeedMoreData(4)));
assert_matches!(
sub.parse(b"\0asm\x01\0\0\0", false),
Ok(Chunk::Parsed {
consumed: 8,
payload: Payload::Version {
num: 1,
encoding: Encoding::Module,
..
},
}),
);
assert_matches!(
sub.parse(&[10], false),
Ok(Chunk::Parsed {
consumed: 0,
payload: Payload::End(18),
}),
);
assert_matches!(p.parse(&[], false), Ok(Chunk::NeedMoreData(1)));
assert_matches!(
p.parse(&[], true),
Ok(Chunk::Parsed {
consumed: 0,
payload: Payload::End(18),
}),
);
}
#[test]
fn nested_section_too_big() {
let mut p = parser_after_component_header();
let mut sub = match p.parse(&[1, 10], false) {
Ok(Chunk::Parsed {
consumed: 2,
payload: Payload::ModuleSection { parser, .. },
}) => parser,
other => panic!("bad parse {:?}", other),
};
assert_matches!(
sub.parse(b"\0asm\x01\0\0\0", false),
Ok(Chunk::Parsed {
consumed: 8,
payload: Payload::Version { num: 1, .. },
}),
);
assert_eq!(
sub.parse(&[0, 1, 0], false).unwrap_err().message(),
"section too large",
);
}
}