diff --git a/crates/fuzz-utils/src/lib.rs b/crates/fuzz-utils/src/lib.rs index c595f25c..40711489 100755 --- a/crates/fuzz-utils/src/lib.rs +++ b/crates/fuzz-utils/src/lib.rs @@ -131,6 +131,30 @@ where .into()) } + fn test_merge(&self, wata: &str, watb: &str) -> Result<()> { + let wasma = self.wat2wasm(&wata)?; + let wasmb = self.wat2wasm(&watb)?; + let mut module = walrus::Module::from_buffer(&wasma) + .context("walrus failed to parse the first wasm buffer")?; + walrus::ModuleConfig::default() + .generate_dwarf(false) + .parse_into(&wasmb, &mut module) + .context("walrus failed to merge the second wasm buffer")?; + + module.emit_wasm(); + return Ok(()); + } + /// Generate two wasm files and confirm that merging them is valid in `walrus` + /// /// + /// Does not attempt to reduce any failing test cases. + pub fn run_merge(&mut self) -> Result<()> { + let wata = self.gen_wat(); + let watb = self.gen_wat(); + self.test_merge(&wata, &watb) + .with_context(|| format!("wata = {}, watb = {}", wata, watb))?; + return Ok(()); + } + /// Generate a single wasm file and then compare its output in the reference /// interpreter before and after round tripping it through `walrus`. /// diff --git a/fuzz/fuzz_targets/watmerge.rs b/fuzz/fuzz_targets/watmerge.rs new file mode 100644 index 00000000..e00a0375 --- /dev/null +++ b/fuzz/fuzz_targets/watmerge.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[macro_use] +extern crate libfuzzer_sys; + +use bufrng::BufRng; +use walrus_fuzz_utils::{Config, WatGen}; + +fuzz_target!(|data: &[u8]| { + let data = if data.is_empty() { &[0] } else { data }; + let fuel = data.len(); + let rng = BufRng::new(data); + let mut config = Config::, BufRng>::new(rng).set_fuel(fuel); + if let Err(e) = config.run_merge() { + walrus_fuzz_utils::print_err(&e); + panic!("Found an error! {}", e); + } +}); diff --git a/src/module/config.rs b/src/module/config.rs index 66470a65..90cbe758 100644 --- a/src/module/config.rs +++ b/src/module/config.rs @@ -18,6 +18,7 @@ pub struct ModuleConfig { pub(crate) on_parse: Option Result<()> + Sync + Send + 'static>>, pub(crate) on_instr_loc: Option InstrLocId + Sync + Send + 'static>>, + pub(crate) skip_debug_sections: bool, } impl Clone for ModuleConfig { @@ -32,6 +33,7 @@ impl Clone for ModuleConfig { skip_producers_section: self.skip_producers_section, skip_name_section: self.skip_name_section, preserve_code_transform: self.preserve_code_transform, + skip_debug_sections: self.skip_debug_sections, // ... and this is left empty. on_parse: None, @@ -54,6 +56,7 @@ impl fmt::Debug for ModuleConfig { ref preserve_code_transform, ref on_parse, ref on_instr_loc, + ref skip_debug_sections, } = self; f.debug_struct("ModuleConfig") @@ -69,6 +72,7 @@ impl fmt::Debug for ModuleConfig { .field("preserve_code_transform", preserve_code_transform) .field("on_parse", &on_parse.as_ref().map(|_| "..")) .field("on_instr_loc", &on_instr_loc.as_ref().map(|_| "..")) + .field("skip_debug_sections", skip_debug_sections) .finish() } } @@ -79,6 +83,11 @@ impl ModuleConfig { ModuleConfig::default() } + /// Sets a flag to whether DWARF debug sections are skipped for this module. + pub fn skip_dwarf(&mut self, dw: bool) -> &mut ModuleConfig { + self.skip_debug_sections = dw; + return self; + } /// Sets a flag to whether DWARF debug sections are generated for this /// module. /// @@ -208,6 +217,12 @@ impl ModuleConfig { Module::parse(wasm, self) } + /// Parses an in-memory WebAssembly file into an existing `Module` using this + /// configuration. + pub fn parse_into(&self, wasm: &[u8], pre: &mut Module) -> Result<()> { + pre.parse_in(wasm, self) + } + /// Parses a WebAssembly file into a `Module` using this configuration. pub fn parse_file

(&self, path: P) -> Result where diff --git a/src/module/functions/mod.rs b/src/module/functions/mod.rs index 530ce720..d86ad6d1 100644 --- a/src/module/functions/mod.rs +++ b/src/module/functions/mod.rs @@ -350,7 +350,7 @@ impl Module { // necessary and it's a bottleneck! let mut bodies = Vec::with_capacity(functions.len()); for (i, (body, mut validator)) in functions.into_iter().enumerate() { - let index = (num_imports + i) as u32; + let index = (indices.num_fun_imports + i) as u32; let id = indices.get_func(index)?; let ty = match self.funcs.arena[id].kind { FunctionKind::Uninitialized(ty) => ty, diff --git a/src/module/imports.rs b/src/module/imports.rs index e7e02289..bc5e0909 100644 --- a/src/module/imports.rs +++ b/src/module/imports.rs @@ -160,6 +160,7 @@ impl Module { entry.field.expect("module linking not supported"), ty, ); + ids.num_fun_imports += 1; ids.push_func(id.0); } wasmparser::ImportSectionEntryType::Table(t) => { @@ -171,6 +172,7 @@ impl Module { t.maximum, ty, ); + ids.num_table_imports += 1; ids.push_table(id.0); } wasmparser::ImportSectionEntryType::Memory(m) => { @@ -184,6 +186,7 @@ impl Module { m.initial as u32, m.maximum.map(|m| m as u32), ); + ids.num_mem_imports += 1; ids.push_memory(id.0); } wasmparser::ImportSectionEntryType::Global(g) => { @@ -193,6 +196,7 @@ impl Module { ValType::parse(&g.content_type)?, g.mutable, ); + ids.num_global_imports += 1; ids.push_global(id.0); } wasmparser::ImportSectionEntryType::Module(_) diff --git a/src/module/mod.rs b/src/module/mod.rs index 881b30f9..defd721a 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -38,6 +38,7 @@ pub use crate::module::producers::ModuleProducers; pub use crate::module::tables::{ModuleTables, Table, TableId}; pub use crate::module::types::ModuleTypes; use crate::parse::IndicesToIds; +use crate::FunctionBuilder; use anyhow::{bail, Context}; use id_arena::Id; use log::warn; @@ -128,9 +129,21 @@ impl Module { ModuleConfig::new().parse(wasm) } + /// Merge a module from the in-memory wasm buffer with the default configuration + pub fn merge_buffer(&mut self, wasm: &[u8]) -> Result<()> { + return self.parse_in(wasm, &ModuleConfig::default()); + } + fn parse(wasm: &[u8], config: &ModuleConfig) -> Result { let mut ret = Module::default(); ret.config = config.clone(); + ret.parse_in(wasm, config)?; + return Ok(ret); + } + fn parse_in(&mut self, wasm: &[u8], config: &ModuleConfig) -> Result<()> { + // let mut ret = Module::default(); + // ret.config = config.clone(); + let old_start = self.start.take(); let mut indices = IndicesToIds::default(); let mut validator = Validator::new(); validator.wasm_features(WasmFeatures { @@ -155,67 +168,67 @@ impl Module { validator .data_section(&s) .context("failed to parse data section")?; - ret.parse_data(s, &mut indices)?; + self.parse_data(s, &mut indices)?; } Payload::TypeSection(s) => { validator .type_section(&s) .context("failed to parse type section")?; - ret.parse_types(s, &mut indices)?; + self.parse_types(s, &mut indices)?; } Payload::ImportSection(s) => { validator .import_section(&s) .context("failed to parse import section")?; - ret.parse_imports(s, &mut indices)?; + self.parse_imports(s, &mut indices)?; } Payload::TableSection(s) => { validator .table_section(&s) .context("failed to parse table section")?; - ret.parse_tables(s, &mut indices)?; + self.parse_tables(s, &mut indices)?; } Payload::MemorySection(s) => { validator .memory_section(&s) .context("failed to parse memory section")?; - ret.parse_memories(s, &mut indices)?; + self.parse_memories(s, &mut indices)?; } Payload::GlobalSection(s) => { validator .global_section(&s) .context("failed to parse global section")?; - ret.parse_globals(s, &mut indices)?; + self.parse_globals(s, &mut indices)?; } Payload::ExportSection(s) => { validator .export_section(&s) .context("failed to parse export section")?; - ret.parse_exports(s, &mut indices)?; + self.parse_exports(s, &mut indices)?; } Payload::ElementSection(s) => { validator .element_section(&s) .context("failed to parse element section")?; - ret.parse_elements(s, &mut indices)?; + self.parse_elements(s, &mut indices)?; } Payload::StartSection { func, range, .. } => { validator.start_section(func, &range)?; - ret.start = Some(indices.get_func(func)?); + self.start = Some(indices.get_func(func)?); } Payload::FunctionSection(s) => { validator .function_section(&s) .context("failed to parse function section")?; - ret.declare_local_functions(s, &mut indices)?; + self.declare_local_functions(s, &mut indices)?; } Payload::DataCountSection { count, range } => { validator.data_count_section(count, &range)?; - ret.reserve_data(count, &mut indices); + self.reserve_data(count, &mut indices); } Payload::CodeSectionStart { count, range, .. } => { validator.code_section_start(count, &range)?; - ret.funcs.code_section_offset = range.start; + self.funcs.code_section_offset = range.start; } Payload::CodeSectionEntry(body) => { let validator = validator.code_section_entry()?; @@ -230,10 +243,10 @@ impl Module { let result = match name { "producers" => wasmparser::ProducersSectionReader::new(data, data_offset) .map_err(anyhow::Error::from) - .and_then(|s| ret.parse_producers_section(s)), + .and_then(|s| self.parse_producers_section(s)), "name" => wasmparser::NameSectionReader::new(data, data_offset) .map_err(anyhow::Error::from) - .and_then(|r| ret.parse_name_section(r, &indices)), + .and_then(|r| self.parse_name_section(r, &indices)), _ => { log::debug!("parsing custom section `{}`", name); if name.starts_with(".debug") { @@ -242,7 +255,7 @@ impl Module { data: data.to_vec(), }); } else { - ret.customs.add(RawCustomSection { + self.customs.add(RawCustomSection { name: name.to_string(), data: data.to_vec(), }); @@ -293,25 +306,37 @@ impl Module { } } - ret.parse_local_functions( + self.parse_local_functions( local_functions, &mut indices, config.on_instr_loc.as_ref().map(|f| f.as_ref()), ) .context("failed to parse code section")?; - - ret.parse_debug_sections(debug_sections) - .context("failed to parse debug data section")?; - - ret.producers + if !config.skip_debug_sections { + self.parse_debug_sections(debug_sections) + .context("failed to parse debug data section")?; + } + self.producers .add_processed_by("walrus", env!("CARGO_PKG_VERSION")); + match self.start { + Some(a) => { + if let Some(r) = old_start { + let mut new_builder = FunctionBuilder::new(&mut self.types, &[], &[]); + new_builder.func_body().call(r).call(a).return_(); + let new = new_builder.local_func(vec![]); + let new = self.funcs.add_local(new); + self.start = Some(new); + } + } + None => self.start = old_start, + } if let Some(on_parse) = &config.on_parse { - on_parse(&mut ret, &indices)?; + on_parse(self, &indices)?; } log::debug!("parse complete"); - Ok(ret) + Ok(()) } /// Emit this module into a `.wasm` file at the given path. diff --git a/src/parse.rs b/src/parse.rs index 77195e38..6f224db2 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -26,6 +26,10 @@ pub struct IndicesToIds { elements: Vec, data: Vec, locals: IdHashMap>, + pub(crate) num_fun_imports: usize, + pub(crate) num_mem_imports: usize, + pub(crate) num_global_imports: usize, + pub(crate) num_table_imports: usize, } macro_rules! define_push_get {