use crate::limits::{MAX_WASM_CATCHES, MAX_WASM_HANDLERS};
use crate::prelude::*;
use crate::{BinaryReader, BinaryReaderError, FromReader, Result, ValType};
use core::{fmt, mem};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BlockType {
Empty,
Type(ValType),
FuncType(u32),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FrameKind {
Block,
If,
Else,
Loop,
TryTable,
LegacyTry,
LegacyCatch,
LegacyCatchAll,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct MemArg {
pub align: u8,
pub max_align: u8,
pub offset: u64,
pub memory: u32,
}
#[derive(Clone)]
pub struct BrTable<'a> {
pub(crate) reader: crate::BinaryReader<'a>,
pub(crate) cnt: u32,
pub(crate) default: u32,
}
impl PartialEq<Self> for BrTable<'_> {
fn eq(&self, other: &Self) -> bool {
self.cnt == other.cnt
&& self.default == other.default
&& self.reader.remaining_buffer() == other.reader.remaining_buffer()
}
}
impl Eq for BrTable<'_> {}
impl<'a> BrTable<'a> {
pub fn len(&self) -> u32 {
self.cnt
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn default(&self) -> u32 {
self.default
}
pub fn targets(&self) -> BrTableTargets<'_> {
BrTableTargets {
reader: self.reader.clone(),
remaining: self.cnt,
}
}
}
pub struct BrTableTargets<'a> {
reader: crate::BinaryReader<'a>,
remaining: u32,
}
impl<'a> Iterator for BrTableTargets<'a> {
type Item = Result<u32>;
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = usize::try_from(self.remaining).unwrap_or_else(|error| {
panic!("could not convert remaining `u32` into `usize`: {error}")
});
(remaining, Some(remaining))
}
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
if !self.reader.eof() {
return Some(Err(BinaryReaderError::new(
"trailing data in br_table",
self.reader.original_position(),
)));
}
return None;
}
self.remaining -= 1;
Some(self.reader.read_var_u32())
}
}
impl fmt::Debug for BrTable<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("BrTable");
f.field("count", &self.cnt);
f.field("default", &self.default);
match self.targets().collect::<Result<Vec<_>>>() {
Ok(targets) => {
f.field("targets", &targets);
}
Err(_) => {
f.field("reader", &self.reader);
}
}
f.finish()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Ieee32(pub(crate) u32);
impl Ieee32 {
pub fn bits(self) -> u32 {
self.0
}
}
impl From<f32> for Ieee32 {
fn from(value: f32) -> Self {
Ieee32 {
0: u32::from_le_bytes(value.to_le_bytes()),
}
}
}
impl From<Ieee32> for f32 {
fn from(bits: Ieee32) -> f32 {
f32::from_bits(bits.bits())
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Ieee64(pub(crate) u64);
impl Ieee64 {
pub fn bits(self) -> u64 {
self.0
}
}
impl From<f64> for Ieee64 {
fn from(value: f64) -> Self {
Ieee64 {
0: u64::from_le_bytes(value.to_le_bytes()),
}
}
}
impl From<Ieee64> for f64 {
fn from(bits: Ieee64) -> f64 {
f64::from_bits(bits.bits())
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct V128(pub(crate) [u8; 16]);
impl V128 {
pub fn bytes(&self) -> &[u8; 16] {
&self.0
}
pub fn i128(&self) -> i128 {
i128::from_le_bytes(self.0)
}
}
impl From<V128> for i128 {
fn from(bits: V128) -> i128 {
bits.i128()
}
}
impl From<V128> for u128 {
fn from(bits: V128) -> u128 {
u128::from_le_bytes(bits.0)
}
}
impl From<i128> for V128 {
fn from(value: i128) -> Self {
V128(i128::to_le_bytes(value))
}
}
impl From<u128> for V128 {
fn from(value: u128) -> Self {
V128(u128::to_le_bytes(value))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Ordering {
AcqRel,
SeqCst,
}
macro_rules! define_operator {
($(@$proposal:ident $op:ident $({ $($payload:tt)* })? => $visit:ident ($($ann:tt)*))*) => {
#[derive(Debug, Clone, Eq, PartialEq)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum Operator<'a> {
$(
$op $({ $($payload)* })?,
)*
}
}
}
crate::for_each_operator!(define_operator);
pub trait FrameStack {
fn current_frame(&self) -> Option<FrameKind>;
}
#[derive(Debug, Default, Clone)]
pub struct ControlStack {
frames: Vec<FrameKind>,
top: Option<FrameKind>,
}
impl ControlStack {
pub fn clear(&mut self) {
self.frames.clear();
self.top = None;
}
#[inline]
pub fn is_empty(&self) -> bool {
self.top.is_none()
}
#[inline]
pub fn push(&mut self, frame: FrameKind) {
if let Some(old_top) = self.top.replace(frame) {
self.frames.push(old_top);
}
}
pub fn pop(&mut self) -> Option<FrameKind> {
mem::replace(&mut self.top, self.frames.pop())
}
#[inline]
pub fn last(&self) -> Option<FrameKind> {
self.top
}
}
struct FrameStackAdapter<'a, T> {
stack: &'a mut ControlStack,
visitor: &'a mut T,
}
impl<T> FrameStack for FrameStackAdapter<'_, T> {
fn current_frame(&self) -> Option<FrameKind> {
self.stack.last()
}
}
struct SingleFrameAdapter<'a, T> {
current_frame: FrameKind,
visitor: &'a mut T,
}
impl<T> FrameStack for SingleFrameAdapter<'_, T> {
fn current_frame(&self) -> Option<FrameKind> {
Some(self.current_frame)
}
}
#[derive(Clone)]
pub struct OperatorsReader<'a> {
reader: BinaryReader<'a>,
stack: ControlStack,
}
#[derive(Default)]
pub struct OperatorsReaderAllocations(ControlStack);
impl<'a> OperatorsReader<'a> {
pub fn new(reader: BinaryReader<'a>) -> Self {
Self::new_with_allocs(reader, Default::default())
}
pub fn new_with_allocs(
reader: BinaryReader<'a>,
mut allocs: OperatorsReaderAllocations,
) -> Self {
allocs.0.clear();
allocs.0.push(FrameKind::Block);
Self {
reader,
stack: allocs.0,
}
}
pub fn get_binary_reader(&self) -> BinaryReader<'a> {
self.reader.clone()
}
pub fn eof(&self) -> bool {
self.reader.eof()
}
pub fn original_position(&self) -> usize {
self.reader.original_position()
}
pub fn is_end_then_eof(&self) -> bool {
self.reader.is_end_then_eof()
}
pub fn into_allocations(self) -> OperatorsReaderAllocations {
OperatorsReaderAllocations(self.stack)
}
pub fn read(&mut self) -> Result<Operator<'a>> {
self.visit_operator(&mut OperatorFactory)
}
pub fn visit_operator<T>(&mut self, visitor: &mut T) -> Result<<T as VisitOperator<'a>>::Output>
where
T: VisitOperator<'a>,
{
self.reader.visit_operator(&mut FrameStackAdapter {
stack: &mut self.stack,
visitor,
})
}
pub fn read_with_offset(&mut self) -> Result<(Operator<'a>, usize)> {
let pos = self.reader.original_position();
Ok((self.read()?, pos))
}
pub fn into_iter_with_offsets(self) -> OperatorsIteratorWithOffsets<'a> {
OperatorsIteratorWithOffsets {
reader: self,
err: false,
}
}
pub(crate) fn skip_const_expr(&mut self) -> Result<()> {
loop {
if let Operator::End = self.read()? {
if self.current_frame().is_some() {
bail!(
self.original_position(),
"control frames remain at end of expression"
);
}
return Ok(());
}
}
}
pub fn finish(&self) -> Result<()> {
self.reader.finish_expression(self)
}
}
impl<'a> FrameStack for OperatorsReader<'a> {
fn current_frame(&self) -> Option<FrameKind> {
self.stack.last()
}
}
impl<'a> IntoIterator for OperatorsReader<'a> {
type Item = Result<Operator<'a>>;
type IntoIter = OperatorsIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
OperatorsIterator {
reader: self,
err: false,
}
}
}
pub struct OperatorsIterator<'a> {
reader: OperatorsReader<'a>,
err: bool,
}
impl<'a> OperatorsIterator<'a> {
pub fn into_allocations(self) -> OperatorsReaderAllocations {
self.reader.into_allocations()
}
}
impl<'a> Iterator for OperatorsIterator<'a> {
type Item = Result<Operator<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.err || self.reader.eof() {
return None;
}
let result = self.reader.read();
self.err = result.is_err();
Some(result)
}
}
pub struct OperatorsIteratorWithOffsets<'a> {
reader: OperatorsReader<'a>,
err: bool,
}
impl<'a> OperatorsIteratorWithOffsets<'a> {
pub fn into_allocations(self) -> OperatorsReaderAllocations {
self.reader.into_allocations()
}
}
impl<'a> Iterator for OperatorsIteratorWithOffsets<'a> {
type Item = Result<(Operator<'a>, usize)>;
fn next(&mut self) -> Option<Self::Item> {
if self.err || self.reader.eof() {
return None;
}
let result = self.reader.read_with_offset();
self.err = result.is_err();
Some(result)
}
}
macro_rules! define_visit_operator {
($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
$(
fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output;
)*
}
}
#[allow(missing_docs)]
pub trait VisitOperator<'a> {
type Output: 'a;
fn visit_operator(&mut self, op: &Operator<'a>) -> Self::Output {
macro_rules! visit_operator {
($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {{
match op {
$( Operator::$op $({ $($arg),* })? => self.$visit($($($arg.clone()),*)?), )*
#[cfg(feature = "simd")]
other => visit_simd_operator(self, other),
}
}};
}
crate::for_each_visit_operator!(visit_operator)
}
#[cfg(feature = "simd")]
fn simd_visitor(&mut self) -> Option<&mut dyn VisitSimdOperator<'a, Output = Self::Output>> {
None
}
crate::for_each_visit_operator!(define_visit_operator);
}
#[cfg(feature = "simd")]
fn visit_simd_operator<'a, V>(visitor: &mut V, op: &Operator<'a>) -> V::Output
where
V: VisitOperator<'a> + ?Sized,
{
let Some(simd_visitor) = visitor.simd_visitor() else {
panic!("missing SIMD visitor to visit operator: {op:?}")
};
macro_rules! visit_simd_operator {
($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {{
match op {
$( Operator::$op $({ $($arg),* })? => simd_visitor.$visit($($($arg.clone()),*)?), )*
unexpected => unreachable!("unexpected non-SIMD operator: {unexpected:?}"),
}
}};
}
crate::for_each_visit_simd_operator!(visit_simd_operator)
}
#[cfg(feature = "simd")]
#[allow(missing_docs)]
pub trait VisitSimdOperator<'a>: VisitOperator<'a> {
crate::for_each_visit_simd_operator!(define_visit_operator);
}
macro_rules! define_visit_operator_delegate {
($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
$(
fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output {
V::$visit(&mut *self, $($($arg),*)?)
}
)*
}
}
impl<'a, 'b, V: VisitOperator<'a> + ?Sized> VisitOperator<'a> for &'b mut V {
type Output = V::Output;
fn visit_operator(&mut self, op: &Operator<'a>) -> Self::Output {
V::visit_operator(*self, op)
}
#[cfg(feature = "simd")]
fn simd_visitor(&mut self) -> Option<&mut dyn VisitSimdOperator<'a, Output = V::Output>> {
V::simd_visitor(*self)
}
crate::for_each_visit_operator!(define_visit_operator_delegate);
}
#[cfg(feature = "simd")]
impl<'a, 'b, V: VisitSimdOperator<'a> + ?Sized> VisitSimdOperator<'a> for &'b mut V {
crate::for_each_visit_simd_operator!(define_visit_operator_delegate);
}
impl<'a, V: VisitOperator<'a> + ?Sized> VisitOperator<'a> for Box<V> {
type Output = V::Output;
fn visit_operator(&mut self, op: &Operator<'a>) -> Self::Output {
V::visit_operator(&mut *self, op)
}
#[cfg(feature = "simd")]
fn simd_visitor(&mut self) -> Option<&mut dyn VisitSimdOperator<'a, Output = V::Output>> {
V::simd_visitor(&mut *self)
}
crate::for_each_visit_operator!(define_visit_operator_delegate);
}
#[cfg(feature = "simd")]
impl<'a, V: VisitSimdOperator<'a> + ?Sized> VisitSimdOperator<'a> for Box<V> {
crate::for_each_visit_simd_operator!(define_visit_operator_delegate);
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TryTable {
pub ty: BlockType,
pub catches: Vec<Catch>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum Catch {
One { tag: u32, label: u32 },
OneRef { tag: u32, label: u32 },
All { label: u32 },
AllRef { label: u32 },
}
impl<'a> FromReader<'a> for TryTable {
fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
let ty = reader.read_block_type()?;
let catches = reader
.read_iter(MAX_WASM_CATCHES, "catches")?
.collect::<Result<_>>()?;
Ok(TryTable { ty, catches })
}
}
impl<'a> FromReader<'a> for Catch {
fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
Ok(match reader.read_u8()? {
0x00 => Catch::One {
tag: reader.read_var_u32()?,
label: reader.read_var_u32()?,
},
0x01 => Catch::OneRef {
tag: reader.read_var_u32()?,
label: reader.read_var_u32()?,
},
0x02 => Catch::All {
label: reader.read_var_u32()?,
},
0x03 => Catch::AllRef {
label: reader.read_var_u32()?,
},
x => return reader.invalid_leading_byte(x, "catch"),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResumeTable {
pub handlers: Vec<Handle>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum Handle {
OnLabel { tag: u32, label: u32 },
OnSwitch { tag: u32 },
}
impl ResumeTable {
pub fn len(&self) -> usize {
self.handlers.len()
}
}
impl<'a> FromReader<'a> for ResumeTable {
fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
let handlers = reader
.read_iter(MAX_WASM_HANDLERS, "resume table")?
.collect::<Result<_>>()?;
let table = ResumeTable { handlers };
Ok(table)
}
}
impl<'a> FromReader<'a> for Handle {
fn from_reader(reader: &mut BinaryReader<'a>) -> Result<Self> {
Ok(match reader.read_u8()? {
0x00 => Handle::OnLabel {
tag: reader.read_var_u32()?,
label: reader.read_var_u32()?,
},
0x01 => Handle::OnSwitch {
tag: reader.read_var_u32()?,
},
x => return reader.invalid_leading_byte(x, "on clause"),
})
}
}
struct OperatorFactory;
macro_rules! define_visit_operator {
($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
$(
fn $visit(&mut self $($(,$arg: $argty)*)?) -> Operator<'a> {
Operator::$op $({ $($arg),* })?
}
)*
}
}
impl<'a> VisitOperator<'a> for OperatorFactory {
type Output = Operator<'a>;
#[cfg(feature = "simd")]
fn simd_visitor(&mut self) -> Option<&mut dyn VisitSimdOperator<'a, Output = Self::Output>> {
Some(self)
}
crate::for_each_visit_operator!(define_visit_operator);
}
#[cfg(feature = "simd")]
impl<'a> VisitSimdOperator<'a> for OperatorFactory {
crate::for_each_visit_simd_operator!(define_visit_operator);
}
macro_rules! define_visit_operator_stack_adapter {
($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
$(
fn $visit(&mut self $($(,$arg: $argty)*)?) -> T::Output {
define_visit_operator_stack_adapter!(@visit self $visit $($($arg,)*)?)
}
)*
};
(@visit $self:ident visit_block $($rest:tt)*) => {{
$self.stack.push(FrameKind::Block);
$self.visitor.visit_block( $($rest)* )
}};
(@visit $self:ident visit_loop $($rest:tt)*) => {{
$self.stack.push(FrameKind::Loop);
$self.visitor.visit_loop( $($rest)* )
}};
(@visit $self:ident visit_if $($rest:tt)*) => {{
$self.stack.push(FrameKind::If);
$self.visitor.visit_if( $($rest)* )
}};
(@visit $self:ident visit_else $($rest:tt)*) => {{
$self.stack.pop();
$self.stack.push(FrameKind::Else);
$self.visitor.visit_else( $($rest)* )
}};
(@visit $self:ident visit_try $($rest:tt)*) => {{
$self.stack.push(FrameKind::LegacyTry);
$self.visitor.visit_try( $($rest)* )
}};
(@visit $self:ident visit_catch $($rest:tt)*) => {{
$self.stack.pop();
$self.stack.push(FrameKind::LegacyCatch);
$self.visitor.visit_catch( $($rest)* )
}};
(@visit $self:ident visit_catch_all $($rest:tt)*) => {{
$self.stack.pop();
$self.stack.push(FrameKind::LegacyCatchAll);
$self.visitor.visit_catch_all( $($rest)* )
}};
(@visit $self:ident visit_try_table $($rest:tt)*) => {{
$self.stack.push(FrameKind::TryTable);
$self.visitor.visit_try_table( $($rest)* )
}};
(@visit $self:ident visit_delegate $($rest:tt)*) => {{
$self.stack.pop();
$self.visitor.visit_delegate( $($rest)* )
}};
(@visit $self:ident visit_end $($rest:tt)*) => {{
$self.stack.pop();
$self.visitor.visit_end( $($rest)* )
}};
(@visit $self:ident $visit:ident $($rest:tt)*) => {
$self.visitor.$visit( $($rest)* )
};
}
impl<'a, T: VisitOperator<'a>> VisitOperator<'a> for FrameStackAdapter<'_, T> {
type Output = T::Output;
#[cfg(feature = "simd")]
fn simd_visitor(&mut self) -> Option<&mut dyn VisitSimdOperator<'a, Output = Self::Output>> {
self.visitor.simd_visitor()
}
crate::for_each_visit_operator!(define_visit_operator_stack_adapter);
}
macro_rules! define_passthrough_visit_operator {
($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
$(
fn $visit(&mut self $($(,$arg: $argty)*)?) -> T::Output {
self.visitor.$visit( $($($arg,)*)? )
}
)*
};
}
impl<'a, T: VisitOperator<'a>> VisitOperator<'a> for SingleFrameAdapter<'_, T> {
type Output = T::Output;
#[cfg(feature = "simd")]
fn simd_visitor(&mut self) -> Option<&mut dyn VisitSimdOperator<'a, Output = Self::Output>> {
self.visitor.simd_visitor()
}
crate::for_each_visit_operator!(define_passthrough_visit_operator);
}
impl<'a> BinaryReader<'a> {
pub fn peek_operator<T: FrameStack>(&self, stack: &T) -> Result<Operator<'a>> {
self.clone().visit_operator(&mut SingleFrameAdapter {
current_frame: stack.current_frame().ok_or_else(|| {
format_err!(
self.original_position(),
"operators remaining after end of function body or expression"
)
})?,
visitor: &mut OperatorFactory,
})
}
}