1#![deny(missing_docs)]
106#![deny(missing_debug_implementations)]
107
108use failure::ResultExt;
109use rayon::prelude::*;
110use std::collections::HashMap;
111use std::collections::HashSet;
112use std::path;
113use walrus::ir::VisitorMut;
114
115#[derive(Clone, Debug)]
117pub enum Input {
118 File(path::PathBuf),
120 Buffer(Vec<u8>),
122 }
125
126impl Default for Input {
127 fn default() -> Self {
128 Input::File(path::PathBuf::default())
129 }
130}
131
132#[derive(Clone, Debug, Default)]
135pub struct Options {
136 pub functions: Vec<String>,
138
139 pub patterns: Vec<String>,
142
143 pub snip_rust_fmt_code: bool,
145
146 pub snip_rust_panicking_code: bool,
148
149 pub skip_producers_section: bool,
152}
153
154pub fn snip(module: &mut walrus::Module, options: Options) -> Result<(), failure::Error> {
156 if !options.skip_producers_section {
157 module
158 .producers
159 .add_processed_by("wasm-snip", env!("CARGO_PKG_VERSION"));
160 }
161
162 let names: HashSet<String> = options.functions.iter().cloned().collect();
163 let re_set = build_regex_set(options).context("failed to compile regex")?;
164 let to_snip = find_functions_to_snip(&module, &names, &re_set);
165
166 replace_calls_with_unreachable(module, &to_snip);
167 unexport_snipped_functions(module, &to_snip);
168 unimport_snipped_functions(module, &to_snip);
169 snip_table_elements(module, &to_snip);
170 delete_functions_to_snip(module, &to_snip);
171 walrus::passes::gc::run(module);
172
173 Ok(())
174}
175
176fn build_regex_set(mut options: Options) -> Result<regex::RegexSet, failure::Error> {
177 if options.snip_rust_fmt_code {
179 options.patterns.push(".*4core3fmt.*".into());
181 options.patterns.push(".*3std3fmt.*".into());
182
183 options.patterns.push(r#".*core\.\.fmt\.\..*"#.into());
185 options.patterns.push(r#".*std\.\.fmt\.\..*"#.into());
186
187 options.patterns.push(".*core::fmt::.*".into());
189 options.patterns.push(".*std::fmt::.*".into());
190 }
191
192 if options.snip_rust_panicking_code {
194 options.patterns.push(".*4core9panicking.*".into());
196 options.patterns.push(".*3std9panicking.*".into());
197
198 options.patterns.push(r#".*core\.\.panicking\.\..*"#.into());
200 options.patterns.push(r#".*std\.\.panicking\.\..*"#.into());
201
202 options.patterns.push(".*core::panicking::.*".into());
204 options.patterns.push(".*std::panicking::.*".into());
205 }
206
207 Ok(regex::RegexSet::new(options.patterns)?)
208}
209
210fn find_functions_to_snip(
211 module: &walrus::Module,
212 names: &HashSet<String>,
213 re_set: ®ex::RegexSet,
214) -> HashSet<walrus::FunctionId> {
215 module
216 .funcs
217 .par_iter()
218 .filter_map(|f| {
219 f.name.as_ref().and_then(|name| {
220 if names.contains(name) || re_set.is_match(name) {
221 Some(f.id())
222 } else {
223 None
224 }
225 })
226 })
227 .collect()
228}
229
230fn delete_functions_to_snip(module: &mut walrus::Module, to_snip: &HashSet<walrus::FunctionId>) {
231 for f in to_snip.iter().cloned() {
232 module.funcs.delete(f);
233 }
234}
235
236fn replace_calls_with_unreachable(
237 module: &mut walrus::Module,
238 to_snip: &HashSet<walrus::FunctionId>,
239) {
240 struct Replacer<'a> {
241 to_snip: &'a HashSet<walrus::FunctionId>,
242 }
243
244 impl Replacer<'_> {
245 fn should_snip_call(&self, instr: &walrus::ir::Instr) -> bool {
246 if let walrus::ir::Instr::Call(walrus::ir::Call { func }) = instr {
247 if self.to_snip.contains(func) {
248 return true;
249 }
250 }
251 false
252 }
253 }
254
255 impl VisitorMut for Replacer<'_> {
256 fn visit_instr_mut(&mut self, instr: &mut walrus::ir::Instr) {
257 if self.should_snip_call(instr) {
258 *instr = walrus::ir::Unreachable {}.into();
259 }
260 }
261 }
262
263 module.funcs.par_iter_local_mut().for_each(|(id, func)| {
264 if to_snip.contains(&id) {
266 return;
267 }
268
269 let entry = func.entry_block();
270 walrus::ir::dfs_pre_order_mut(&mut Replacer { to_snip }, func, entry);
271 });
272}
273
274fn unexport_snipped_functions(module: &mut walrus::Module, to_snip: &HashSet<walrus::FunctionId>) {
275 let exports_to_snip: HashSet<walrus::ExportId> = module
276 .exports
277 .iter()
278 .filter_map(|e| match e.item {
279 walrus::ExportItem::Function(f) if to_snip.contains(&f) => Some(e.id()),
280 _ => None,
281 })
282 .collect();
283
284 for e in exports_to_snip {
285 module.exports.delete(e);
286 }
287}
288
289fn unimport_snipped_functions(module: &mut walrus::Module, to_snip: &HashSet<walrus::FunctionId>) {
290 let imports_to_snip: HashSet<walrus::ImportId> = module
291 .imports
292 .iter()
293 .filter_map(|i| match i.kind {
294 walrus::ImportKind::Function(f) if to_snip.contains(&f) => Some(i.id()),
295 _ => None,
296 })
297 .collect();
298
299 for i in imports_to_snip {
300 module.imports.delete(i);
301 }
302}
303
304fn snip_table_elements(module: &mut walrus::Module, to_snip: &HashSet<walrus::FunctionId>) {
305 let mut unreachable_funcs: HashMap<walrus::TypeId, walrus::FunctionId> = Default::default();
306
307 let make_unreachable_func = |ty: walrus::TypeId,
308 types: &mut walrus::ModuleTypes,
309 locals: &mut walrus::ModuleLocals,
310 funcs: &mut walrus::ModuleFunctions|
311 -> walrus::FunctionId {
312 let ty = types.get(ty);
313 let params = ty.params().to_vec();
314 let locals: Vec<_> = params.iter().map(|ty| locals.add(*ty)).collect();
315 let results = ty.results().to_vec();
316 let mut builder = walrus::FunctionBuilder::new(types, ¶ms, &results);
317 builder.func_body().unreachable();
318 builder.finish(locals, funcs)
319 };
320
321 for t in module.tables.iter_mut() {
322 if let walrus::TableKind::Function(ref mut ft) = t.kind {
323 let types = &mut module.types;
324 let locals = &mut module.locals;
325 let funcs = &mut module.funcs;
326
327 ft.elements
328 .iter_mut()
329 .flat_map(|el| el)
330 .filter(|f| to_snip.contains(f))
331 .for_each(|el| {
332 let ty = funcs.get(*el).ty();
333 *el = *unreachable_funcs
334 .entry(ty)
335 .or_insert_with(|| make_unreachable_func(ty, types, locals, funcs));
336 });
337
338 ft.relative_elements
339 .iter_mut()
340 .flat_map(|(_, elems)| elems.iter_mut().filter(|f| to_snip.contains(f)))
341 .for_each(|el| {
342 let ty = funcs.get(*el).ty();
343 *el = *unreachable_funcs
344 .entry(ty)
345 .or_insert_with(|| make_unreachable_func(ty, types, locals, funcs));
346 });
347 }
348 }
349}