#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
use failure::ResultExt;
use rayon::prelude::*;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path;
use walrus::ir::VisitorMut;
#[derive(Clone, Debug)]
pub enum Input {
File(path::PathBuf),
Buffer(Vec<u8>),
}
impl Default for Input {
fn default() -> Self {
Input::File(path::PathBuf::default())
}
}
#[derive(Clone, Debug, Default)]
pub struct Options {
pub functions: Vec<String>,
pub patterns: Vec<String>,
pub snip_rust_fmt_code: bool,
pub snip_rust_panicking_code: bool,
pub skip_producers_section: bool,
}
pub fn snip(module: &mut walrus::Module, options: Options) -> Result<(), failure::Error> {
if !options.skip_producers_section {
module
.producers
.add_processed_by("wasm-snip", env!("CARGO_PKG_VERSION"));
}
let names: HashSet<String> = options.functions.iter().cloned().collect();
let re_set = build_regex_set(options).context("failed to compile regex")?;
let to_snip = find_functions_to_snip(&module, &names, &re_set);
replace_calls_with_unreachable(module, &to_snip);
unexport_snipped_functions(module, &to_snip);
unimport_snipped_functions(module, &to_snip);
snip_table_elements(module, &to_snip);
delete_functions_to_snip(module, &to_snip);
walrus::passes::gc::run(module);
Ok(())
}
fn build_regex_set(mut options: Options) -> Result<regex::RegexSet, failure::Error> {
if options.snip_rust_fmt_code {
options.patterns.push(".*4core3fmt.*".into());
options.patterns.push(".*3std3fmt.*".into());
options.patterns.push(r#".*core\.\.fmt\.\..*"#.into());
options.patterns.push(r#".*std\.\.fmt\.\..*"#.into());
options.patterns.push(".*core::fmt::.*".into());
options.patterns.push(".*std::fmt::.*".into());
}
if options.snip_rust_panicking_code {
options.patterns.push(".*4core9panicking.*".into());
options.patterns.push(".*3std9panicking.*".into());
options.patterns.push(r#".*core\.\.panicking\.\..*"#.into());
options.patterns.push(r#".*std\.\.panicking\.\..*"#.into());
options.patterns.push(".*core::panicking::.*".into());
options.patterns.push(".*std::panicking::.*".into());
}
Ok(regex::RegexSet::new(options.patterns)?)
}
fn find_functions_to_snip(
module: &walrus::Module,
names: &HashSet<String>,
re_set: ®ex::RegexSet,
) -> HashSet<walrus::FunctionId> {
module
.funcs
.par_iter()
.filter_map(|f| {
f.name.as_ref().and_then(|name| {
if names.contains(name) || re_set.is_match(name) {
Some(f.id())
} else {
None
}
})
})
.collect()
}
fn delete_functions_to_snip(module: &mut walrus::Module, to_snip: &HashSet<walrus::FunctionId>) {
for f in to_snip.iter().cloned() {
module.funcs.delete(f);
}
}
fn replace_calls_with_unreachable(
module: &mut walrus::Module,
to_snip: &HashSet<walrus::FunctionId>,
) {
struct Replacer<'a> {
to_snip: &'a HashSet<walrus::FunctionId>,
}
impl Replacer<'_> {
fn should_snip_call(&self, instr: &walrus::ir::Instr) -> bool {
if let walrus::ir::Instr::Call(walrus::ir::Call { func }) = instr {
if self.to_snip.contains(func) {
return true;
}
}
false
}
}
impl VisitorMut for Replacer<'_> {
fn visit_instr_mut(&mut self, instr: &mut walrus::ir::Instr) {
if self.should_snip_call(instr) {
*instr = walrus::ir::Unreachable {}.into();
}
}
}
module.funcs.par_iter_local_mut().for_each(|(id, func)| {
if to_snip.contains(&id) {
return;
}
let entry = func.entry_block();
walrus::ir::dfs_pre_order_mut(&mut Replacer { to_snip }, func, entry);
});
}
fn unexport_snipped_functions(module: &mut walrus::Module, to_snip: &HashSet<walrus::FunctionId>) {
let exports_to_snip: HashSet<walrus::ExportId> = module
.exports
.iter()
.filter_map(|e| match e.item {
walrus::ExportItem::Function(f) if to_snip.contains(&f) => Some(e.id()),
_ => None,
})
.collect();
for e in exports_to_snip {
module.exports.delete(e);
}
}
fn unimport_snipped_functions(module: &mut walrus::Module, to_snip: &HashSet<walrus::FunctionId>) {
let imports_to_snip: HashSet<walrus::ImportId> = module
.imports
.iter()
.filter_map(|i| match i.kind {
walrus::ImportKind::Function(f) if to_snip.contains(&f) => Some(i.id()),
_ => None,
})
.collect();
for i in imports_to_snip {
module.imports.delete(i);
}
}
fn snip_table_elements(module: &mut walrus::Module, to_snip: &HashSet<walrus::FunctionId>) {
let mut unreachable_funcs: HashMap<walrus::TypeId, walrus::FunctionId> = Default::default();
let make_unreachable_func = |ty: walrus::TypeId,
types: &mut walrus::ModuleTypes,
locals: &mut walrus::ModuleLocals,
funcs: &mut walrus::ModuleFunctions|
-> walrus::FunctionId {
let ty = types.get(ty);
let params = ty.params().to_vec();
let locals: Vec<_> = params.iter().map(|ty| locals.add(*ty)).collect();
let results = ty.results().to_vec();
let mut builder = walrus::FunctionBuilder::new(types, ¶ms, &results);
builder.func_body().unreachable();
builder.finish(locals, funcs)
};
for t in module.tables.iter_mut() {
if let walrus::TableKind::Function(ref mut ft) = t.kind {
let types = &mut module.types;
let locals = &mut module.locals;
let funcs = &mut module.funcs;
ft.elements
.iter_mut()
.flat_map(|el| el)
.filter(|f| to_snip.contains(f))
.for_each(|el| {
let ty = funcs.get(*el).ty();
*el = *unreachable_funcs
.entry(ty)
.or_insert_with(|| make_unreachable_func(ty, types, locals, funcs));
});
ft.relative_elements
.iter_mut()
.flat_map(|(_, elems)| elems.iter_mut().filter(|f| to_snip.contains(f)))
.for_each(|el| {
let ty = funcs.get(*el).ty();
*el = *unreachable_funcs
.entry(ty)
.or_insert_with(|| make_unreachable_func(ty, types, locals, funcs));
});
}
}
}