use crate::error::Error::InstrumentationError;
use crate::ir::types;
use std::collections::HashMap;
use wasmparser::Operator;
macro_rules! refers_to_func_match {
($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
fn refers_to_func(op: &Operator) -> bool {
$( refers_to_func_match!(@stmt op, $op $({ $($arg)* })?); )*
false
}
};
(@stmt $op_expr:ident, $op_name:ident) => {};
(@stmt $op_expr:ident, $op_name:ident { $($args:ident)* }) => {
refers_to_func_match!(@scan $op_expr, $op_name [$($args)*])
};
(@scan $op_expr:ident, $op_name:ident []) => {};
(@scan $op_expr:ident, $op_name:ident [function_index $($_rest:ident)*]) => {
if matches!($op_expr, Operator::$op_name { .. }) { return true; }
};
(@scan $op_expr:ident, $op_name:ident [$_first:ident $($rest:ident)*]) => {
refers_to_func_match!(@scan $op_expr, $op_name [$($rest)*])
};
}
wasmparser::for_each_operator!(refers_to_func_match);
macro_rules! refers_to_global_match {
($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
fn refers_to_global(op: &Operator) -> bool {
$( refers_to_global_match!(@stmt op, $op $({ $($arg)* })?); )*
false
}
};
(@stmt $op_expr:ident, $op_name:ident) => {};
(@stmt $op_expr:ident, $op_name:ident { $($args:ident)* }) => {
refers_to_global_match!(@scan $op_expr, $op_name [$($args)*])
};
(@scan $op_expr:ident, $op_name:ident []) => {};
(@scan $op_expr:ident, $op_name:ident [global_index $($_rest:ident)*]) => {
if matches!($op_expr, Operator::$op_name { .. }) { return true; }
};
(@scan $op_expr:ident, $op_name:ident [$_first:ident $($rest:ident)*]) => {
refers_to_global_match!(@scan $op_expr, $op_name [$($rest)*])
};
}
wasmparser::for_each_operator!(refers_to_global_match);
macro_rules! refers_to_memory_match {
($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
fn refers_to_memory(op: &Operator) -> bool {
$( refers_to_memory_match!(@stmt op, $op $({ $($arg)* })?); )*
false
}
};
(@stmt $op_expr:ident, $op_name:ident) => {};
(@stmt $op_expr:ident, $op_name:ident { $($args:ident)* }) => {
refers_to_memory_match!(@scan $op_expr, $op_name [$($args)*])
};
(@scan $op_expr:ident, $op_name:ident []) => {};
(@scan $op_expr:ident, $op_name:ident [memarg $($_rest:ident)*]) => {
if matches!($op_expr, Operator::$op_name { .. }) { return true; }
};
(@scan $op_expr:ident, $op_name:ident [mem $($_rest:ident)*]) => {
if matches!($op_expr, Operator::$op_name { .. }) { return true; }
};
(@scan $op_expr:ident, $op_name:ident [src_mem $($_rest:ident)*]) => {
if matches!($op_expr, Operator::$op_name { .. }) { return true; }
};
(@scan $op_expr:ident, $op_name:ident [$_first:ident $($rest:ident)*]) => {
refers_to_memory_match!(@scan $op_expr, $op_name [$($rest)*])
};
}
wasmparser::for_each_operator!(refers_to_memory_match);
macro_rules! update_fn_match {
($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
fn update_fn_instr(op: &mut Operator, mapping: &HashMap<u32, u32>) -> types::Result<()> {
$( update_fn_match!(@stmt op, mapping, $op $({ $($arg)* })?); )*
panic!("Internal error: Operation doesn't need to be checked for function IDs!")
}
};
(@stmt $op_expr:ident, $mapping:ident, $op_name:ident) => {};
(@stmt $op_expr:ident, $mapping:ident, $op_name:ident { $($args:ident)* }) => {
update_fn_match!(@scan $op_expr, $mapping, $op_name [$($args)*])
};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident []) => {};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident [function_index $($_rest:ident)*]) => {
if let Operator::$op_name { function_index, .. } = $op_expr {
match $mapping.get(&*function_index) {
Some(new_index) => *function_index = *new_index,
None => return Err(InstrumentationError("Called a deleted function!".to_string())),
}
return Ok(());
}
};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident [$_first:ident $($rest:ident)*]) => {
update_fn_match!(@scan $op_expr, $mapping, $op_name [$($rest)*])
};
}
wasmparser::for_each_operator!(update_fn_match);
macro_rules! update_global_match {
($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
fn update_global_instr(
op: &mut Operator,
mapping: &HashMap<u32, u32>,
) -> types::Result<()> {
$( update_global_match!(@stmt op, mapping, $op $({ $($arg)* })?); )*
panic!("Internal error: Operation doesn't need to be checked for global IDs!")
}
};
(@stmt $op_expr:ident, $mapping:ident, $op_name:ident) => {};
(@stmt $op_expr:ident, $mapping:ident, $op_name:ident { $($args:ident)* }) => {
update_global_match!(@scan $op_expr, $mapping, $op_name [$($args)*])
};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident []) => {};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident [global_index $($_rest:ident)*]) => {
if let Operator::$op_name { global_index, .. } = $op_expr {
match $mapping.get(&*global_index) {
Some(new_index) => *global_index = *new_index,
None => return Err(InstrumentationError("Operation on a deleted global!".to_string())),
}
return Ok(());
}
};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident [$_first:ident $($rest:ident)*]) => {
update_global_match!(@scan $op_expr, $mapping, $op_name [$($rest)*])
};
}
wasmparser::for_each_operator!(update_global_match);
macro_rules! update_memory_match {
($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
fn update_memory_instr(
op: &mut Operator,
mapping: &HashMap<u32, u32>,
) -> types::Result<()> {
$( update_memory_match!(@stmt op, mapping, $op $({ $($arg)* })?); )*
panic!("Internal error: Operation doesn't need to be checked for memory IDs!")
}
};
(@stmt $op_expr:ident, $mapping:ident, $op_name:ident) => {};
(@stmt $op_expr:ident, $mapping:ident, $op_name:ident { $($args:ident)* }) => {
update_memory_match!(@scan $op_expr, $mapping, $op_name [$($args)*])
};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident []) => {};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident [memarg $($_rest:ident)*]) => {
if let Operator::$op_name { memarg, .. } = $op_expr {
match $mapping.get(&memarg.memory) {
Some(new_index) => memarg.memory = *new_index,
None => return Err(InstrumentationError(format!(
"Attempting to reference a deleted memory, ID: {}", memarg.memory
))),
}
return Ok(());
}
};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident [mem $($_rest:ident)*]) => {
if let Operator::$op_name { mem, .. } = $op_expr {
match $mapping.get(&*mem) {
Some(new_index) => *mem = *new_index,
None => return Err(InstrumentationError(format!(
"Attempting to reference a deleted memory, ID: {}", mem
))),
}
return Ok(());
}
};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident [src_mem $($_rest:ident)*]) => {
if let Operator::$op_name { src_mem, dst_mem, .. } = $op_expr {
match $mapping.get(&*src_mem) {
Some(new_index) => *src_mem = *new_index,
None => return Err(InstrumentationError(format!(
"Attempting to reference a deleted memory, ID: {}", src_mem
))),
}
match $mapping.get(&*dst_mem) {
Some(new_index) => *dst_mem = *new_index,
None => return Err(InstrumentationError(format!(
"Attempting to reference a deleted memory, ID: {}", dst_mem
))),
}
return Ok(());
}
};
(@scan $op_expr:ident, $mapping:ident, $op_name:ident [$_first:ident $($rest:ident)*]) => {
update_memory_match!(@scan $op_expr, $mapping, $op_name [$($rest)*])
};
}
wasmparser::for_each_operator!(update_memory_match);
pub(crate) fn fix_op_id_mapping(
op: &mut Operator,
func_mapping: &HashMap<u32, u32>,
global_mapping: &HashMap<u32, u32>,
memory_mapping: &HashMap<u32, u32>,
) -> types::Result<()> {
if refers_to_func(op) {
update_fn_instr(op, func_mapping)?;
}
if refers_to_global(op) {
update_global_instr(op, global_mapping)?;
}
if refers_to_memory(op) {
update_memory_instr(op, memory_mapping)?;
}
Ok(())
}