///|
/// The section identifiers defined by the WebAssembly core specification.
pub enum SectionId {
Custom
Type
Import
Function
Table
Memory
Global
Export
Start
Element
Code
Data
DataCount
Tag
}
///|
/// Map a raw section id byte to its meaning, or `None` when unassigned.
pub fn SectionId::from_id(id : Int) -> SectionId? {
match id {
0 => Some(Custom)
1 => Some(Type)
2 => Some(Import)
3 => Some(Function)
4 => Some(Table)
5 => Some(Memory)
6 => Some(Global)
7 => Some(Export)
8 => Some(Start)
9 => Some(Element)
10 => Some(Code)
11 => Some(Data)
12 => Some(DataCount)
13 => Some(Tag)
_ => None
}
}
///|
/// Custom sections are the only ones that carry a name, and they are worth
/// telling apart from the numbered sections.
pub fn SectionId::is_custom(self : SectionId) -> Bool {
match self {
Custom => true
_ => false
}
}
///|
/// The lower-case name used by `wasm-objdump` and friends.
pub fn SectionId::name(self : SectionId) -> String {
match self {
Custom => "custom"
Type => "type"
Import => "import"
Function => "function"
Table => "table"
Memory => "memory"
Global => "global"
Export => "export"
Start => "start"
Element => "element"
Code => "code"
Data => "data"
DataCount => "datacount"
Tag => "tag"
}
}
///|
/// One decoded section header, with the byte budget it accounts for.
pub struct Section {
/// Raw id byte as it appears in the file.
id : Int
kind : SectionId
/// Offset of the id byte from the start of the file.
offset : Int
/// Number of payload bytes, i.e. the value of the section's size field.
payload_size : Int
/// Header bytes (id plus encoded size) plus the payload.
total_size : Int
/// Name declared inside a custom section, when it is decodable.
custom_name : String?
}
///|
/// True when two section ids are the same variant.
///
/// Section ids carry no payload and this toolchain derives no `Eq` for them, so
/// the comparison is written out once here rather than spelled at each call site
/// that has to pick a section out of the table.
pub fn same_kind(a : SectionId, b : SectionId) -> Bool {
match (a, b) {
(Custom, Custom)
| (Type, Type)
| (Import, Import)
| (Function, Function)
| (Table, Table)
| (Memory, Memory)
| (Global, Global)
| (Export, Export)
| (Start, Start)
| (Element, Element)
| (Code, Code)
| (Data, Data)
| (DataCount, DataCount)
| (Tag, Tag) => true
_ => false
}
}
///|
/// A decoded module preamble plus its section table.
pub struct WasmModule {
version : Int
file_size : Int
sections : Array[Section]
}
///|
/// WebAssembly magic number, `\0asm`.
const MAGIC_0 : Int = 0x00
///|
const MAGIC_1 : Int = 0x61
///|
const MAGIC_2 : Int = 0x73
///|
const MAGIC_3 : Int = 0x6D
///|
/// Decode the module preamble and walk every section header.
///
/// Payloads are measured but not interpreted, which keeps this step a pure
/// structural pass over the file.
pub fn parse_module(data : Bytes) -> Result[WasmModule, WasmError] {
if data.length() < 8 {
return Err(WasmError::UnexpectedEnd(0, 8))
}
if data[0].to_int() != MAGIC_0 ||
data[1].to_int() != MAGIC_1 ||
data[2].to_int() != MAGIC_2 ||
data[3].to_int() != MAGIC_3 {
return Err(WasmError::BadMagic)
}
let version = data[4].to_int() |
(data[5].to_int() << 8) |
(data[6].to_int() << 16) |
(data[7].to_int() << 24)
let reader = Reader::new(data)
reader.seek(8)
let sections : Array[Section] = []
while !reader.at_end() {
let offset = reader.position()
let id = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
let kind = match SectionId::from_id(id) {
Some(kind) => kind
None => return Err(WasmError::InvalidSectionId(id))
}
let payload_size = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let header_size = reader.position() - offset
if reader.remaining() < payload_size {
return Err(WasmError::UnexpectedEnd(reader.position(), payload_size))
}
let payload_start = reader.position()
let custom_name = if kind.is_custom() {
read_custom_name(reader)
} else {
None
}
// The name is read for display only, so rewind to the payload end rather
// than depending on how far the name happened to advance.
reader.seek(payload_start + payload_size)
sections.push({
id,
kind,
offset,
payload_size,
total_size: header_size + payload_size,
custom_name,
})
}
Ok({ version, file_size: data.length(), sections, })
}
///|
/// Custom sections begin with a name, which tells producer metadata, debug info
/// and `name` sections apart. A malformed name never fails the parse: the size
/// of the section is the fact this tool is after.
fn read_custom_name(reader : Reader) -> String? {
let name_length = match reader.read_u32_leb() {
Ok(length) => length
Err(_) => return None
}
if reader.remaining() < name_length {
return None
}
match reader.read_bytes(name_length) {
Ok(name) => Some(@utf8.decode_lossy(name))
Err(_) => None
}
}
///|
/// A one-line explanation for a failed parse.
pub fn describe_error(error : WasmError) -> String {
match error {
UnexpectedEnd(offset, needed) =>
"unexpected end of input at offset " +
offset.to_string() +
" (needed " +
needed.to_string() +
" bytes)"
BadMagic => "not a WebAssembly binary: missing \\0asm magic number"
InvalidSectionId(id) => "unknown section id " + id.to_string()
LebOverflow(offset) =>
"malformed LEB128 integer at offset " + offset.to_string()
InvalidImportKind(kind) =>
"unknown import kind " + kind.to_string() + " (expected 0..4)"
UnknownOpcode(offset, byte) =>
"unknown opcode 0x" +
byte.to_string(radix=16) +
" at offset " +
offset.to_string()
UnknownPrefixedOpcode(offset, prefix, sub) =>
"unknown opcode 0x" +
prefix.to_string(radix=16) +
" 0x" +
sub.to_string(radix=16) +
" at offset " +
offset.to_string()
}
}
///|
/// One function body decoded out of the code section.
///
/// The spec stores a body as a size-prefixed blob, so an entry costs its own
/// length prefix on top of the body itself. Both numbers are kept: the body is
/// what a compiler can shrink, the total is what the file actually spends.
pub struct FunctionBody {
/// Index in the function index space, where imported functions come first.
index : Int
/// Length declared by the body's size prefix: locals plus instructions.
body_size : Int
/// The size prefix plus the body, i.e. the bytes this entry costs.
total_size : Int
/// Offset of the size prefix from the start of the file.
offset : Int
}
///|
/// Subsection id `1` of the `name` custom section: the function name map.
const NAME_SUBSECTION_FUNCTION : Int = 1
///|
/// Byte offset at which a section's payload begins, derived from the header
/// size the section table already recorded.
fn payload_start(section : Section) -> Int {
section.offset + section.total_size - section.payload_size
}
///|
/// Create a cursor parked on the first payload byte of `section`.
fn reader_at_payload(data : Bytes, section : Section) -> Reader {
let reader = Reader::new(data)
reader.seek(payload_start(section))
reader
}
///|
/// Advance past a WebAssembly name: a LEB128 byte count and that many bytes.
fn skip_name(reader : Reader) -> Result[Unit, WasmError] {
let length = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
match reader.read_bytes(length) {
Ok(_) => Ok(())
Err(error) => Err(error)
}
}
///|
/// Advance past a `limits` record: a flag byte, a minimum, and a maximum only
/// when the flag says one is present.
///
/// The shared and 64-bit flags change the value type but not the shape, so
/// skipping only has to read the low bit.
fn skip_limits(reader : Reader) -> Result[Unit, WasmError] {
let flags = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
match reader.read_u32_leb() {
Ok(_) => ()
Err(error) => return Err(error)
}
if (flags & 0x01) != 0 {
match reader.read_u32_leb() {
Ok(_) => ()
Err(error) => return Err(error)
}
}
Ok(())
}
///|
/// Read a `limits` record: a flag byte, a minimum, and a maximum when the flag
/// says one is present.
///
/// The shared and 64-bit flags change the value type but not the shape, so only
/// the low bit decides whether a maximum follows.
fn parse_limits(reader : Reader) -> Result[(Int, Int?), WasmError] {
let flags = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
let minimum = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
if (flags & 0x01) != 0 {
let maximum = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
Ok((minimum, Some(maximum)))
} else {
Ok((minimum, None))
}
}
///|
/// Advance past one import descriptor and report whether it is a function.
///
/// The kind byte picks the shape that follows: a type index for functions, a
/// table type, limits or a global type for everything else.
fn skip_import_desc(reader : Reader) -> Result[Bool, WasmError] {
let kind = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
match kind {
// func: typeidx
0 =>
match reader.read_u32_leb() {
Ok(_) => Ok(true)
Err(error) => Err(error)
}
// table: reftype limits
1 => {
match reader.read_byte() {
Ok(_) => ()
Err(error) => return Err(error)
}
match skip_limits(reader) {
Ok(_) => Ok(false)
Err(error) => Err(error)
}
}
// memory: limits
2 =>
match skip_limits(reader) {
Ok(_) => Ok(false)
Err(error) => Err(error)
}
// global: valtype mut
3 => {
match reader.read_byte() {
Ok(_) => ()
Err(error) => return Err(error)
}
match reader.read_byte() {
Ok(_) => Ok(false)
Err(error) => Err(error)
}
}
// tag: attribute typeidx
4 => {
match reader.read_byte() {
Ok(_) => ()
Err(error) => return Err(error)
}
match reader.read_u32_leb() {
Ok(_) => Ok(false)
Err(error) => Err(error)
}
}
_ => Err(WasmError::InvalidImportKind(kind))
}
}
///|
/// Count the function imports of a module.
///
/// Imported functions occupy the first slots of the function index space, so
/// this count is what turns a code-section position into the index that the
/// `name` section and every call instruction actually use.
pub fn count_imported_functions(
data : Bytes,
section : Section,
) -> Result[Int, WasmError] {
let reader = reader_at_payload(data, section)
let end = payload_start(section) + section.payload_size
let count = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let mut functions = 0
for i = 0; i < count; i = i + 1 {
if reader.position() >= end {
return Err(WasmError::UnexpectedEnd(reader.position(), 1))
}
// Each import is "module name" then "field name" then a descriptor.
match skip_name(reader) {
Ok(_) => ()
Err(error) => return Err(error)
}
match skip_name(reader) {
Ok(_) => ()
Err(error) => return Err(error)
}
match skip_import_desc(reader) {
Ok(is_function) => if is_function { functions = functions + 1 }
Err(error) => return Err(error)
}
}
Ok(functions)
}
///|
/// Decode the code section into one record per function body.
///
/// `base_index` is the number of imported functions, so the returned `index`
/// values line up with the function index space. Bodies are measured and
/// skipped, never interpreted, which keeps this a structural pass like
/// `parse_module`.
pub fn parse_code_section(
data : Bytes,
section : Section,
base_index? : Int = 0,
) -> Result[Array[FunctionBody], WasmError] {
let reader = reader_at_payload(data, section)
let end = payload_start(section) + section.payload_size
let count = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let bodies : Array[FunctionBody] = []
for i = 0; i < count; i = i + 1 {
let offset = reader.position()
let body_size = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let prefix_size = reader.position() - offset
if reader.position() + body_size > end {
return Err(WasmError::UnexpectedEnd(reader.position(), body_size))
}
reader.seek(reader.position() + body_size)
bodies.push({
index: base_index + i,
body_size,
total_size: prefix_size + body_size,
offset,
})
}
Ok(bodies)
}
///|
/// Read one function name map (`namemap`) into `names`, stopping early on a
/// malformed entry so a damaged suffix cannot discard the names before it.
fn read_function_names(
reader : Reader,
sub_end : Int,
names : Map[Int, String],
) -> Unit {
let count = match reader.read_u32_leb() {
Ok(value) => value
Err(_) => return
}
for i = 0; i < count; i = i + 1 {
if reader.position() >= sub_end {
return
}
let index = match reader.read_u32_leb() {
Ok(value) => value
Err(_) => return
}
let length = match reader.read_u32_leb() {
Ok(value) => value
Err(_) => return
}
let text = match reader.read_bytes(length) {
Ok(bytes) => @utf8.decode_lossy(bytes)
Err(_) => return
}
names.set(index, text)
}
}
///|
/// Decode the function names out of a `name` custom section.
///
/// The result is keyed by function index. `None` means the section is not a
/// `name` section at all; `Some` of an empty map is a real answer, because
/// names are optional producer metadata and a stripped binary simply has none.
/// Subsection ids other than `1` (locals, labels, types) are skipped whole,
/// and a malformed subsection ends the walk without discarding earlier names.
pub fn parse_name_section(data : Bytes, section : Section) -> Map[Int, String]? {
let is_name_section = match section.custom_name {
Some(name) => name == "name"
None => false
}
if !is_name_section {
return None
}
let names : Map[Int, String] = Map([])
let reader = reader_at_payload(data, section)
let end = payload_start(section) + section.payload_size
// A custom section opens with its own name, which is "name" here.
match skip_name(reader) {
Ok(_) => ()
Err(_) => return Some(names)
}
while reader.position() < end {
let sub_id = match reader.read_byte() {
Ok(value) => value
Err(_) => break
}
let sub_size = match reader.read_u32_leb() {
Ok(value) => value
Err(_) => break
}
let sub_end = reader.position() + sub_size
if sub_end > end {
break
}
if sub_id == NAME_SUBSECTION_FUNCTION {
read_function_names(reader, sub_end, names)
}
reader.seek(sub_end)
}
Some(names)
}
///|
/// The sections the analysis passes need, picked out of the section table once.
///
/// A well-formed module holds at most one of each numbered section, so the last
/// one seen wins. Several custom sections are legal, and `name` is the only one
/// this tool reads.
pub struct Sections {
mut code : Section?
mut imports : Section?
mut names : Section?
mut exports : Section?
mut elements : Section?
mut tables : Section?
mut start : Section?
}
///|
/// Pick the interesting sections out of a parsed section table.
pub fn find_sections(parsed : WasmModule) -> Sections {
let found : Sections = {
code: None,
imports: None,
names: None,
exports: None,
elements: None,
tables: None,
start: None,
}
for section in parsed.sections {
match section.kind {
Code => found.code = Some(section)
Import => found.imports = Some(section)
Export => found.exports = Some(section)
Element => found.elements = Some(section)
Table => found.tables = Some(section)
Start => found.start = Some(section)
Custom =>
match section.custom_name {
Some(name) => if name == "name" { found.names = Some(section) }
None => ()
}
_ => ()
}
}
found
}
///|
/// How a call site reaches its target.
pub enum CallKind {
/// `call x`: the target is named outright.
Direct(Int)
/// `call_indirect`: the target is read from table `x` at run time.
Indirect(Int)
}
///|
/// One call found in a function body.
pub struct CallSite {
kind : CallKind
/// The signature `call_indirect` selects on; `None` for a direct call.
type_index : Int?
/// Offset of the instruction from the start of the file.
offset : Int
}
///|
/// Skip a function body's local declaration and decode the expression.
///
/// `body_offset` is the first byte after the body's size prefix, so the locals
/// vector comes first. The declared `body_size` is a hard bound: an instruction
/// that would run past it is reported rather than read out of the next body,
/// which is what keeps a wrong immediate width from quietly becoming a wrong
/// call graph.
pub fn decode_instructions(
bytes : Bytes,
body_offset : Int,
body_size : Int,
) -> Result[Array[Instruction], WasmError] {
let reader = Reader::new(bytes)
reader.seek(body_offset)
let end = body_offset + body_size
let local_groups = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let mut group = 0
while group < local_groups {
match reader.read_u32_leb() {
Ok(_) => ()
Err(error) => return Err(error)
}
match reader.read_valtype() {
Ok(_) => ()
Err(error) => return Err(error)
}
group = group + 1
}
if reader.position() > end {
return Err(WasmError::UnexpectedEnd(body_offset, body_size))
}
let instructions : Array[Instruction] = []
while reader.position() < end {
let offset = reader.position()
let instruction = match decode_instruction(bytes, offset) {
Ok(instruction) => instruction
Err(error) => return Err(error)
}
if offset + instruction.size > end {
return Err(WasmError::UnexpectedEnd(offset, instruction.size))
}
reader.seek(offset + instruction.size)
instructions.push(instruction)
}
Ok(instructions)
}
///|
/// Collect every call a function body makes.
///
/// Only `call` and `call_indirect` count here. `ref.func` is a reference rather
/// than a call, and the call graph picks those up separately.
pub fn scan_calls(
bytes : Bytes,
body_offset : Int,
body_size : Int,
) -> Result[Array[CallSite], WasmError] {
let instructions = match decode_instructions(bytes, body_offset, body_size) {
Ok(instructions) => instructions
Err(error) => return Err(error)
}
let sites : Array[CallSite] = []
for instruction in instructions {
match instruction.opcode {
Call =>
sites.push({
kind: Direct(instruction.operands[0]),
type_index: None,
offset: instruction.offset,
})
CallIndirect =>
sites.push({
kind: Indirect(instruction.operands[1]),
type_index: Some(instruction.operands[0]),
offset: instruction.offset,
})
_ => ()
}
}
Ok(sites)
}
///|
/// One entry of the export section.
pub struct Export {
name : String
/// 0 function, 1 table, 2 memory, 3 global, 4 tag.
kind : Int
/// Index into that kind's own index space.
index : Int
}
///|
/// Decode the export section.
pub fn parse_export_section(
data : Bytes,
section : Section,
) -> Result[Array[Export], WasmError] {
let reader = reader_at_payload(data, section)
let end = payload_start(section) + section.payload_size
let count = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let exports : Array[Export] = []
let mut i = 0
while i < count {
let length = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let name = match reader.read_bytes(length) {
Ok(bytes) => @utf8.decode_lossy(bytes)
Err(error) => return Err(error)
}
let kind = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
let index = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
if reader.position() > end {
return Err(WasmError::UnexpectedEnd(reader.position(), 1))
}
exports.push({ name, kind, index, })
i = i + 1
}
Ok(exports)
}
///|
/// The function the module runs at instantiation, from the start section.
pub fn parse_start_section(
data : Bytes,
section : Section,
) -> Result[Int, WasmError] {
let reader = reader_at_payload(data, section)
reader.read_u32_leb()
}
///|
/// A table declared by the table section.
pub struct TableType {
/// Reference type the slots hold: `0x70` is `funcref`.
element_type : Int
/// Declared minimum size, in slots.
minimum : Int
/// Declared maximum, when the limits carry one.
maximum : Int?
}
///|
/// Decode the table section.
pub fn parse_table_section(
data : Bytes,
section : Section,
) -> Result[Array[TableType], WasmError] {
let reader = reader_at_payload(data, section)
let count = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let tables : Array[TableType] = []
let mut i = 0
while i < count {
// A table with an explicit initialiser leads with `0x40 0x00`; everything
// else starts straight at the reference type.
let mut first = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
if first == 0x40 {
match reader.read_byte() {
Ok(_) => ()
Err(error) => return Err(error)
}
first = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
}
let element_type = if first == REF_NULL_TYPE || first == REF_TYPE {
match reader.read_signed_leb(33) {
Ok(_) => 0
Err(error) => return Err(error)
}
} else {
first
}
let limits = match parse_limits(reader) {
Ok(limits) => limits
Err(error) => return Err(error)
}
tables.push({ element_type, minimum: limits.0, maximum: limits.1, })
i = i + 1
}
Ok(tables)
}
///|
/// One entry of the element section.
pub struct ElementSegment {
/// Table the segment initialises, or `-1` when it initialises none.
table_index : Int
/// True when the segment is passive or declarative: it fills no table at
/// instantiation, but the functions it names are still referenced.
passive : Bool
/// Function indices the segment places, in slot order. Entries written as
/// `ref.null` contribute nothing.
functions : Array[Int]
}
///|
/// Decode the element section.
///
/// The eight encodings differ only in whether a table index, an offset
/// expression, an element kind or a reference type is present, so the shared
/// tail — a vector of function indices or of constant expressions — is read once
/// after the mode byte has been handled.
pub fn parse_element_section(
data : Bytes,
section : Section,
) -> Result[Array[ElementSegment], WasmError] {
let reader = reader_at_payload(data, section)
let end = payload_start(section) + section.payload_size
let count = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let segments : Array[ElementSegment] = []
let mut i = 0
while i < count {
let mode = match reader.read_byte() {
Ok(value) => value
Err(error) => return Err(error)
}
let mut table_index = -1
let mut passive = false
// Modes 2 and 6 name their table; 0 and 4 always mean table 0.
if mode == 0x02 || mode == 0x06 {
table_index = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
} else if mode == 0x00 || mode == 0x04 {
table_index = 0
}
// Active modes carry an offset expression that has to be stepped over.
if mode == 0x00 || mode == 0x02 || mode == 0x04 || mode == 0x06 {
match skip_constant_expression(data, reader) {
Ok(_) => ()
Err(error) => return Err(error)
}
}
if mode == 0x01 || mode == 0x03 || mode == 0x05 || mode == 0x07 {
passive = true
}
if mode == 0x01 || mode == 0x03 {
// An element kind byte, which is always `funcref`.
match reader.read_byte() {
Ok(_) => ()
Err(error) => return Err(error)
}
}
if mode == 0x05 || mode == 0x06 || mode == 0x07 {
match reader.read_reftype() {
Ok(_) => ()
Err(error) => return Err(error)
}
} else if mode == 0x02 {
// Mode 2 carries an element kind after its offset.
match reader.read_byte() {
Ok(_) => ()
Err(error) => return Err(error)
}
}
let functions = if mode >= 0x04 {
match read_element_expressions(data, reader) {
Ok(functions) => functions
Err(error) => return Err(error)
}
} else {
let length = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let functions : Array[Int] = []
let mut slot = 0
while slot < length {
match reader.read_u32_leb() {
Ok(value) => functions.push(value)
Err(error) => return Err(error)
}
slot = slot + 1
}
functions
}
if reader.position() > end {
return Err(WasmError::UnexpectedEnd(reader.position(), 1))
}
segments.push({ table_index, passive, functions, })
i = i + 1
}
Ok(segments)
}
///|
/// How an instruction changes the nesting depth of a constant expression: `1`
/// opens a block, `-1` closes one, and `0` leaves the depth alone.
fn nesting_delta(opcode : Opcode) -> Int {
match opcode {
Block | Loop | If => 1
End => -1
_ => 0
}
}
///|
/// True when an instruction ends the expression it sits in.
fn closes_expression(opcode : Opcode, depth : Int) -> Bool {
match opcode {
End => depth == 0
_ => false
}
}
///|
/// Step over a constant expression, which ends at its matching `end`.
fn skip_constant_expression(
data : Bytes,
reader : Reader,
) -> Result[Unit, WasmError] {
let mut depth = 0
let mut done = false
while !done {
let instruction = match decode_instruction(data, reader.position()) {
Ok(instruction) => instruction
Err(error) => return Err(error)
}
reader.seek(instruction.offset + instruction.size)
if closes_expression(instruction.opcode, depth) {
done = true
} else {
depth = depth + nesting_delta(instruction.opcode)
}
}
Ok(())
}
///|
/// Read a vector of element expressions and keep the functions they reference.
fn read_element_expressions(
data : Bytes,
reader : Reader,
) -> Result[Array[Int], WasmError] {
let length = match reader.read_u32_leb() {
Ok(value) => value
Err(error) => return Err(error)
}
let functions : Array[Int] = []
let mut slot = 0
while slot < length {
let mut depth = 0
let mut done = false
while !done {
let instruction = match decode_instruction(data, reader.position()) {
Ok(instruction) => instruction
Err(error) => return Err(error)
}
reader.seek(instruction.offset + instruction.size)
match instruction.opcode {
RefFunc => functions.push(instruction.operands[0])
_ => ()
}
if closes_expression(instruction.opcode, depth) {
done = true
} else {
depth = depth + nesting_delta(instruction.opcode)
}
}
slot = slot + 1
}
Ok(functions)
}