#![allow(clippy::too_many_arguments)]
use crate::api::get_defs_and_lib;
use crate::common::error::{CodeLocation, ErrorGen, WhammError as ErrorInternal};
use crate::common::instr;
use crate::emitter::tag_handler::{get_reasons_from_tag, LineCol, Reason};
use log::error;
use std::collections::HashMap;
use std::process::exit;
use wirm::ir::module::module_types::Types;
use wirm::ir::module::side_effects::{InjectType as WirmInjectType, Injection as WirmInjection};
use wirm::ir::types::{DataType as WirmType, FuncInstrMode, InstrumentationMode};
use wirm::wasmparser::{ExternalKind, TypeRef};
use wirm::Module;
pub const MAX_ERRORS: i32 = 15;
pub fn instrument_with_config(
app_wasm_path: String,
script_path: String,
user_lib_paths: Vec<String>,
config: Config,
core_lib_path: Option<String>,
defs_path: Option<String>,
) -> Result<Vec<u8>, Box<ErrorGen>> {
let (def_yamls, core_lib) = get_defs_and_lib(defs_path, core_lib_path);
instr::run_with_path(
&core_lib,
&def_yamls,
app_wasm_path,
script_path,
user_lib_paths,
MAX_ERRORS,
config,
)
}
pub fn instrument_with_rewriting(
app_wasm_path: String,
script_path: String,
user_lib_paths: Vec<String>,
core_lib_path: Option<String>,
defs_path: Option<String>,
) -> Result<Vec<u8>, Box<ErrorGen>> {
instrument_with_config(
app_wasm_path,
script_path,
user_lib_paths,
Config::default_rewriting(),
core_lib_path,
defs_path,
)
}
pub fn instrument_module_with_rewriting(
target_wasm: &mut Module,
script_path: String,
user_lib_paths: Vec<String>,
core_lib_path: Option<String>,
defs_path: Option<String>,
) -> Result<Vec<u8>, Vec<WhammError>> {
let (def_yamls, core_lib) = get_defs_and_lib(defs_path, core_lib_path);
match instr::run_on_module_and_encode(
&core_lib,
&def_yamls,
target_wasm,
script_path,
user_lib_paths,
MAX_ERRORS,
Config::default_rewriting(),
) {
Ok(res) => Ok(res),
Err(e) => Err(WhammError::from_errs(e.pull_errs())),
}
}
pub fn generate_monitor_module(
script_path: String,
user_lib_paths: Vec<String>,
core_lib_path: Option<String>,
defs_path: Option<String>,
) -> Result<Vec<u8>, Vec<WhammError>> {
match instrument_with_config(
"".to_string(),
script_path,
user_lib_paths,
Config::default_monitor_module(),
core_lib_path,
defs_path,
) {
Ok(res) => Ok(res),
Err(e) => Err(WhammError::from_errs(e.pull_errs())),
}
}
pub fn instrument_as_dry_run_rewriting(
app_wasm_path: String,
script_path: String,
user_lib_paths: Vec<String>,
core_lib_path: Option<String>,
defs_path: Option<String>,
) -> Result<HashMap<WirmInjectType, Vec<Injection>>, Vec<WhammError>> {
let buff = std::fs::read(app_wasm_path).unwrap();
let mut target_wasm = Module::parse(&buff, false, true).unwrap();
let (def_yamls, core_lib) = get_defs_and_lib(defs_path, core_lib_path);
let response = instr::dry_run_on_bytes(
&core_lib,
&def_yamls,
&mut target_wasm,
script_path,
user_lib_paths,
MAX_ERRORS,
Config::default_rewriting(),
);
handle_dry_run_response(response)
}
pub fn instrument_as_dry_run_wei(
script_path: String,
user_lib_paths: Vec<String>,
core_lib_path: Option<String>,
defs_path: Option<String>,
) -> Result<HashMap<WirmInjectType, Vec<Injection>>, Vec<WhammError>> {
let mut module = Module::default();
let (def_yamls, core_lib) = get_defs_and_lib(defs_path, core_lib_path);
let response = instr::dry_run_on_bytes(
&core_lib,
&def_yamls,
&mut module,
script_path,
user_lib_paths,
MAX_ERRORS,
Config::default_monitor_module(),
);
handle_dry_run_response(response)
}
fn handle_dry_run_response(
response: Result<HashMap<WirmInjectType, Vec<WirmInjection>>, Vec<ErrorInternal>>,
) -> Result<HashMap<WirmInjectType, Vec<Injection>>, Vec<WhammError>> {
match response {
Ok(mut side_effects) => {
let mut injections = HashMap::new();
for (ty, l) in side_effects.iter_mut() {
let mut list = Vec::new();
for inj in l.iter_mut() {
list.extend(Injection::from(inj));
}
injections.insert(*ty, list);
}
Ok(injections)
}
Err(errs) => Err(WhammError::from_errs(errs)),
}
}
#[derive(Default)]
pub struct Config {
pub as_monitor_module: bool,
pub enable_wei_alt: bool,
pub metrics: bool,
pub no_bundle: bool,
pub no_body: bool,
pub no_pred: bool,
pub no_report: bool,
pub testing: bool,
pub library_strategy: LibraryLinkStrategy,
}
impl Config {
pub fn default_rewriting() -> Self {
Self::default()
}
pub fn default_monitor_module() -> Self {
Self {
as_monitor_module: true,
..Default::default()
}
}
pub fn new(
as_monitor_module: bool,
enable_wei_alt: bool,
metrics: bool,
no_bundle: bool,
no_body: bool,
no_pred: bool,
no_report: bool,
testing: bool,
library_strategy: Option<LibraryLinkStrategy>,
) -> Self {
if testing {
error!("Generating helper methods for testing mode is not yet supported!");
exit(1);
}
if no_bundle && (!no_body || !no_pred) {
panic!("Cannot disable argument bundling without also disabling body and predicate emitting! Otherwise invalid Wasm would be generated.")
}
Self {
as_monitor_module,
enable_wei_alt,
metrics,
no_bundle,
no_body,
no_pred,
no_report,
testing,
library_strategy: library_strategy.unwrap_or_default(),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum LibraryLinkStrategy {
Merged,
#[default]
Imported,
}
#[derive(Debug)]
pub enum Injection {
Import {
module: String,
name: String,
type_ref: TypeRef,
cause: Cause,
},
Export {
name: String,
kind: ExternalKind,
index: u32,
cause: Cause,
},
Type {
ty: Types,
cause: Cause,
},
Memory {
id: u32, initial: u64,
maximum: Option<u64>,
cause: Cause,
},
ActiveData {
memory_index: u32,
offset_expr: Vec<String>,
data: Vec<u8>,
cause: Cause,
},
PassiveData {
data: Vec<u8>,
cause: Cause,
},
Global {
id: u32, ty: WirmType,
shared: bool,
mutable: bool,
init_expr: Vec<String>,
cause: Cause,
},
Func {
id: u32,
fname: Option<String>,
sig: (Vec<WirmType>, Vec<WirmType>),
locals: Vec<WirmType>,
body: Vec<String>,
cause: Cause,
},
Local {
target_fid: u32,
ty: WirmType,
cause: Cause,
},
Table { cause: Cause },
Element { cause: Cause },
OpProbe {
target_fid: u32,
target_opcode_idx: u32,
mode: InstrumentationMode,
body: Vec<String>,
cause: Cause,
},
FuncProbe {
target_fid: u32,
mode: FuncInstrMode,
body: Vec<String>,
cause: Cause,
},
}
impl Injection {
fn from(injection: &mut WirmInjection) -> Vec<Self> {
match injection {
WirmInjection::Import {
module,
name,
type_ref,
tag,
} => {
let reasons = get_reasons_from_tag(tag.data_mut());
assert_eq!(1, reasons.len());
vec![Self::Import {
module: module.to_owned(),
name: name.to_owned(),
type_ref: type_ref.to_owned(),
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::Export {
name,
kind,
index,
tag,
} => {
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::Export {
name: name.to_owned(),
kind: kind.to_owned(),
index: *index,
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::Type { ty, tag, .. } => {
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::Type {
ty: ty.to_owned(),
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::Memory {
id,
initial,
maximum,
tag,
} => {
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::Memory {
id: *id,
initial: *initial,
maximum: maximum.to_owned(),
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::ActiveData {
memory_index,
offset_expr,
data,
tag,
} => {
let offset_expr_wat = format!("{:?}", offset_expr);
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::ActiveData {
memory_index: *memory_index,
offset_expr: vec![offset_expr_wat],
data: data.to_owned(),
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::PassiveData { data, tag } => {
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::PassiveData {
data: data.to_owned(),
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::Global {
id,
ty,
shared,
mutable,
init_expr,
tag,
} => {
let init_expr_wat = format!("{:?}", init_expr);
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::Global {
id: *id,
ty: ty.to_owned(),
shared: *shared,
mutable: *mutable,
init_expr: vec![init_expr_wat],
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::Func {
id,
fname,
sig,
locals,
body,
tag,
} => {
let mut body_ops = vec![];
for op in body.iter() {
body_ops.push(format!("{:?}", op));
}
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::Func {
id: *id,
fname: fname.to_owned(),
sig: sig.to_owned(),
locals: locals.to_owned(),
body: body_ops,
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::Local {
target_fid,
ty,
tag,
} => {
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::Local {
target_fid: *target_fid,
ty: ty.to_owned(),
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::Table { tag } => {
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::Table {
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::Element { tag } => {
let reasons = get_reasons_from_tag(tag.data_mut());
vec![Self::Element {
cause: Cause::from(reasons.first().unwrap()),
}]
}
WirmInjection::FuncProbe {
target_fid,
mode,
body,
tag,
} => {
let mut injections = vec![];
let mut start_idx = 0;
let reasons = get_reasons_from_tag(tag.data_mut());
for reason in reasons.iter() {
if let Reason::UserProbe { op_idx_end, .. }
| Reason::WhammProbe { op_idx_end } = reason
{
let mut body_wat = vec![];
for op in body[start_idx..*op_idx_end as usize].iter() {
body_wat.push(format!("{:?}\n", op));
}
injections.push(Self::FuncProbe {
target_fid: *target_fid,
mode: mode.to_owned(),
body: body_wat,
cause: Cause::from(reason),
});
start_idx = *op_idx_end as usize;
} else {
panic!("Should be a probe reason, but got: {:?}", reason)
}
}
injections
}
WirmInjection::FuncLocProbe {
target_fid,
target_opcode_idx,
mode,
body,
tag,
} => {
let mut injections = vec![];
let mut start_idx = 0;
if tag.is_empty() {
return vec![];
}
let reasons = get_reasons_from_tag(tag.data_mut());
for reason in reasons.iter() {
if let Reason::UserProbe { op_idx_end, .. }
| Reason::WhammProbe { op_idx_end, .. } = reason
{
let mut body_wat = vec![];
for op in body[start_idx..*op_idx_end as usize].iter() {
body_wat.push(format!("{:?}\n", op));
}
injections.push(Self::OpProbe {
target_fid: *target_fid,
target_opcode_idx: *target_opcode_idx,
mode: mode.to_owned(),
body: body_wat,
cause: Cause::from(reason),
});
start_idx = *op_idx_end as usize;
} else {
panic!(
"Should be a probe reason with an op index, but got: {:#?}",
reason
);
}
}
injections
}
}
}
}
#[derive(Clone, Debug)]
pub enum Cause {
UserPos { lc: LineCol },
UserSpan { lc0: LineCol, lc1: LineCol },
UserProbe { lc0: LineCol, lc1: LineCol },
Whamm,
}
impl From<&Reason> for Cause {
fn from(value: &Reason) -> Self {
match value {
Reason::UserPos { lc } => Self::UserPos { lc: *lc },
Reason::UserSpan { lc0, lc1 } => Self::UserSpan {
lc0: *lc0,
lc1: *lc1,
},
Reason::UserProbe { lc0, lc1, .. } => Self::UserProbe {
lc0: *lc0,
lc1: *lc1,
},
Reason::Whamm | Reason::WhammProbe { .. } => Self::Whamm,
}
}
}
#[derive(Clone, Debug)]
pub struct WhammError {
pub err_loc: Option<CodeLocation>,
pub info_loc: Option<CodeLocation>,
pub msg: String,
}
impl From<&ErrorInternal> for WhammError {
fn from(value: &ErrorInternal) -> Self {
Self {
err_loc: value.err_loc.clone(),
info_loc: value.info_loc.clone(),
msg: value.ty.message().to_string(),
}
}
}
impl WhammError {
fn from_errs(values: Vec<ErrorInternal>) -> Vec<Self> {
let mut errs = vec![];
for e in values.iter() {
errs.push(Self::from(e));
}
errs
}
}