diff --git a/crates/dump/src/lib.rs b/crates/dump/src/lib.rs index 86ed861cba..98d64aa71c 100644 --- a/crates/dump/src/lib.rs +++ b/crates/dump/src/lib.rs @@ -228,18 +228,13 @@ impl<'a> Dump<'a> { Payload::ComponentStartSection { .. } => todo!("component-model"), Payload::AliasSection(_) => todo!("component-model"), - Payload::CustomSection { - name, - data_offset, - data, - range, - } => { + Payload::CustomSection(c) => { write!(self.state, "custom section")?; - self.print(range.start)?; - write!(self.state, "name: {:?}", name)?; - self.print(data_offset)?; - if name == "name" { - let mut iter = NameSectionReader::new(data, data_offset)?; + self.print(c.range().start)?; + write!(self.state, "name: {:?}", c.name())?; + self.print(c.data_offset())?; + if c.name() == "name" { + let mut iter = NameSectionReader::new(c.data(), c.data_offset())?; while !iter.eof() { self.print_custom_name_section(iter.read()?, iter.original_position())?; } @@ -248,8 +243,8 @@ impl<'a> Dump<'a> { for _ in 0..NBYTES { write!(self.dst, "---")?; } - write!(self.dst, "-| ... {} bytes of data\n", data.len())?; - self.cur += data.len(); + write!(self.dst, "-| ... {} bytes of data\n", c.data().len())?; + self.cur += c.data().len(); } } Payload::UnknownSection { diff --git a/crates/wasm-mutate/src/info.rs b/crates/wasm-mutate/src/info.rs index b590b22d68..669810cde9 100644 --- a/crates/wasm-mutate/src/info.rs +++ b/crates/wasm-mutate/src/info.rs @@ -200,13 +200,8 @@ impl<'a> ModuleInfo<'a> { info.data_segments_count = reader.get_count(); info.section(SectionId::Data.into(), reader.range(), input_wasm); } - Payload::CustomSection { - name: _, - data_offset: _, - data: _, - range, - } => { - info.section(SectionId::Custom.into(), range, input_wasm); + Payload::CustomSection(c) => { + info.section(SectionId::Custom.into(), c.range(), input_wasm); } Payload::UnknownSection { id, @@ -246,6 +241,13 @@ impl<'a> ModuleInfo<'a> { self.code != None } + /// Does this module have any custom sections? + pub fn has_custom_section(&self) -> bool { + self.raw_sections + .iter() + .any(|s| s.id == SectionId::Custom as u8) + } + /// Registers a new raw_section in the ModuleInfo pub fn section(&mut self, id: u8, range: wasmparser::Range, full_wasm: &'a [u8]) { self.raw_sections.push(RawSection { diff --git a/crates/wasm-mutate/src/mutators.rs b/crates/wasm-mutate/src/mutators.rs index e0dddbd87a..861831618e 100644 --- a/crates/wasm-mutate/src/mutators.rs +++ b/crates/wasm-mutate/src/mutators.rs @@ -23,6 +23,7 @@ pub mod add_function; pub mod add_type; pub mod codemotion; +pub mod custom; pub mod function_body_unreachable; pub mod modify_data; pub mod modify_init_exprs; diff --git a/crates/wasm-mutate/src/mutators/custom.rs b/crates/wasm-mutate/src/mutators/custom.rs new file mode 100644 index 0000000000..d32e870a1f --- /dev/null +++ b/crates/wasm-mutate/src/mutators/custom.rs @@ -0,0 +1,146 @@ +//! Mutate custom sections. + +use super::Mutator; +use rand::{seq::SliceRandom, Rng}; + +#[derive(Clone, Copy)] +pub struct CustomSectionMutator; + +impl Mutator for CustomSectionMutator { + fn can_mutate(&self, config: &crate::WasmMutate) -> bool { + config.info().has_custom_section() + } + + fn mutate<'a>( + self, + config: &'a mut crate::WasmMutate, + ) -> crate::Result> + 'a>> { + let custom_section_indices: Vec<_> = config + .info() + .raw_sections + .iter() + .enumerate() + .filter(|(_i, s)| s.id == wasm_encoder::SectionId::Custom as u8) + .map(|(i, _s)| i) + .collect(); + assert!(!custom_section_indices.is_empty()); + + let custom_section_index = *custom_section_indices.choose(config.rng()).unwrap(); + let old_custom_section = &config.info().raw_sections[custom_section_index]; + let old_custom_section = + wasmparser::CustomSectionReader::new(old_custom_section.data, 0).unwrap(); + + let name_string; + let data_vec; + let mut name = old_custom_section.name(); + let mut data = old_custom_section.data(); + + if config.rng().gen_ratio(1, 20) { + // Mutate the custom section's name. + let mut new_name = name.to_string().into_bytes(); + config.raw_mutate( + &mut new_name, + if config.reduce { + name.len().saturating_sub(1) + } else { + std::cmp::max(name.len() * 2, 32) + }, + )?; + name_string = String::from_utf8_lossy(&new_name).to_string(); + name = &name_string; + } else { + // Mutate the custom section's data. + let mut new_data = data.to_vec(); + config.raw_mutate( + &mut new_data, + if config.reduce { + data.len().saturating_sub(1) + } else { + std::cmp::max(data.len() * 2, 32) + }, + )?; + data_vec = new_data; + data = &data_vec; + }; + + Ok(Box::new(std::iter::once(Ok(config + .info() + .replace_section( + custom_section_index, + &wasm_encoder::CustomSection { name, data }, + ))))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_grow_custom_section() { + crate::mutators::match_mutation( + r#" + (module + (@custom "name" "data") + ) + "#, + CustomSectionMutator, + r#" + (module + (@custom "name" "datadata") + ) + "#, + ); + } + + #[test] + fn test_shrink_custom_section() { + crate::mutators::match_mutation( + r#" + (module + (@custom "name" "data") + ) + "#, + CustomSectionMutator, + r#" + (module + (@custom "name" "d") + ) + "#, + ); + } + + #[test] + fn test_mutate_custom_section() { + crate::mutators::match_mutation( + r#" + (module + (@custom "name" "data") + ) + "#, + CustomSectionMutator, + r#" + (module + (@custom "name" "aaaa") + ) + "#, + ); + } + + #[test] + fn test_mutate_custom_section_name() { + crate::mutators::match_mutation( + r#" + (module + (@custom "name" "data") + ) + "#, + CustomSectionMutator, + r#" + (module + (@custom "n" "data") + ) + "#, + ); + } +} diff --git a/crates/wasm-mutate/src/mutators/remove_section.rs b/crates/wasm-mutate/src/mutators/remove_section.rs index ad55e11c03..578dce2b6b 100644 --- a/crates/wasm-mutate/src/mutators/remove_section.rs +++ b/crates/wasm-mutate/src/mutators/remove_section.rs @@ -40,11 +40,7 @@ fn is_empty_section(section: &wasm_encoder::RawSection) -> bool { impl Mutator for RemoveSection { fn can_mutate(&self, config: &WasmMutate) -> bool { match self { - &Self::Custom => config - .info() - .raw_sections - .iter() - .any(|s| s.id == wasm_encoder::SectionId::Custom as u8), + &Self::Custom => config.info().has_custom_section(), &Self::Empty => config .info() .raw_sections diff --git a/crates/wasmparser/src/parser.rs b/crates/wasmparser/src/parser.rs index 85f72387a8..73044a197a 100644 --- a/crates/wasmparser/src/parser.rs +++ b/crates/wasmparser/src/parser.rs @@ -5,9 +5,10 @@ use crate::{ InstanceSectionReader, }; use crate::{ - BinaryReader, BinaryReaderError, DataSectionReader, ElementSectionReader, ExportSectionReader, - FunctionBody, FunctionSectionReader, GlobalSectionReader, ImportSectionReader, - MemorySectionReader, Range, Result, TableSectionReader, TagSectionReader, TypeSectionReader, + BinaryReader, BinaryReaderError, CustomSectionReader, DataSectionReader, ElementSectionReader, + ExportSectionReader, FunctionBody, FunctionSectionReader, GlobalSectionReader, + ImportSectionReader, MemorySectionReader, Range, Result, TableSectionReader, TagSectionReader, + TypeSectionReader, }; use std::convert::TryInto; use std::fmt; @@ -247,19 +248,7 @@ pub enum Payload<'a> { CodeSectionEntry(crate::FunctionBody<'a>), /// A module or component custom section was received. - CustomSection { - /// The name of the custom section. - name: &'a str, - /// The offset, relative to the start of the original module or component, - /// that the `data` payload for this custom section starts at. - data_offset: usize, - /// The actual contents of the custom section. - data: &'a [u8], - /// The range of bytes that specify this whole custom section (including - /// both the name of this custom section and its data) specified in - /// offsets relative to the start of the byte stream. - range: Range, - }, + CustomSection(crate::CustomSectionReader<'a>), /// An unknown section was found. /// @@ -411,7 +400,7 @@ impl Parser { /// ComponentStartSection { .. } => { /* ... */ } /// AliasSection(_) => { /* ... */ } /// - /// CustomSection { name, .. } => { /* ... */ } + /// CustomSection(_) => { /* ... */ } /// /// // most likely you'd return an error here /// UnknownSection { id, .. } => { /* ... */ } @@ -538,26 +527,12 @@ impl Parser { } // Check for custom sections (supported by all encodings) - if id == 0 { - let start = reader.original_position(); - let range = Range { - start, - end: reader.original_position() + len as usize, - }; - let mut content = subreader(reader, len)?; - // Note that if this fails we can't read any more bytes, - // so clear the "we'd succeed if we got this many more - // bytes" because we can't recover from "eof" at this point. - let name = content.read_string().map_err(clear_hint)?; - return Ok(Payload::CustomSection { - name, - data_offset: content.original_position(), - data: content.remaining_buffer(), - range, - }); - } + if id == 0 {} match (self.encoding, id) { + // Sections for both modules and components. + (_, 0) => section(reader, len, CustomSectionReader::new, CustomSection), + // Module sections (Encoding::Module, 1) => { section(reader, len, TypeSectionReader::new, TypeSection) @@ -1015,18 +990,7 @@ impl fmt::Debug for Payload<'_> { .finish(), AliasSection(_) => f.debug_tuple("AliasSection").field(&"...").finish(), - CustomSection { - name, - data_offset, - data: _, - range, - } => f - .debug_struct("CustomSection") - .field("name", name) - .field("data_offset", data_offset) - .field("range", range) - .field("data", &"...") - .finish(), + CustomSection(c) => f.debug_tuple("CustomSection").field(c).finish(), UnknownSection { id, range, .. } => f .debug_struct("UnknownSection") @@ -1206,36 +1170,36 @@ mod tests { parser_after_header().parse(&[0, 1, 0], false), Ok(Chunk::Parsed { consumed: 3, - payload: Payload::CustomSection { + payload: Payload::CustomSection(CustomSectionReader { name: "", data_offset: 11, data: b"", range: Range { start: 10, end: 11 }, - }, + }), }), ); assert_matches!( parser_after_header().parse(&[0, 2, 1, b'a'], false), Ok(Chunk::Parsed { consumed: 4, - payload: Payload::CustomSection { + payload: Payload::CustomSection(CustomSectionReader { name: "a", data_offset: 12, data: b"", range: Range { start: 10, end: 12 }, - }, + }), }), ); assert_matches!( parser_after_header().parse(&[0, 2, 0, b'a'], false), Ok(Chunk::Parsed { consumed: 4, - payload: Payload::CustomSection { + payload: Payload::CustomSection(CustomSectionReader { name: "", data_offset: 11, data: b"a", range: Range { start: 10, end: 12 }, - }, + }), }), ); } diff --git a/crates/wasmparser/src/readers/core.rs b/crates/wasmparser/src/readers/core.rs index 934a6a4e40..941f208548 100644 --- a/crates/wasmparser/src/readers/core.rs +++ b/crates/wasmparser/src/readers/core.rs @@ -1,4 +1,5 @@ mod code; +mod custom; mod data; mod elements; mod exports; @@ -17,6 +18,7 @@ mod tags; mod types; pub use self::code::*; +pub use self::custom::*; pub use self::data::*; pub use self::elements::*; pub use self::exports::*; diff --git a/crates/wasmparser/src/readers/core/custom.rs b/crates/wasmparser/src/readers/core/custom.rs new file mode 100644 index 0000000000..b231357ce7 --- /dev/null +++ b/crates/wasmparser/src/readers/core/custom.rs @@ -0,0 +1,62 @@ +use crate::{BinaryReader, Range, Result}; + +/// A reader for custom sections of a WebAssembly module. +#[derive(Clone)] +pub struct CustomSectionReader<'a> { + // NB: these fields are public to the crate to make testing easier. + pub(crate) name: &'a str, + pub(crate) data_offset: usize, + pub(crate) data: &'a [u8], + pub(crate) range: Range, +} + +impl<'a> CustomSectionReader<'a> { + /// Constructs a new `CustomSectionReader` for the given data and offset. + pub fn new(data: &'a [u8], offset: usize) -> Result> { + let mut reader = BinaryReader::new_with_offset(data, offset); + let name = reader.read_string()?; + let data_offset = reader.original_position(); + let data = reader.remaining_buffer(); + let range = reader.range(); + return Ok(CustomSectionReader { + name, + data_offset, + data, + range, + }); + } + + /// The name of the custom section. + pub fn name(&self) -> &'a str { + self.name + } + + /// The offset, relative to the start of the original module or component, + /// that the `data` payload for this custom section starts at. + pub fn data_offset(&self) -> usize { + self.data_offset + } + + /// The actual contents of the custom section. + pub fn data(&self) -> &'a [u8] { + self.data + } + + /// The range of bytes that specify this whole custom section (including + /// both the name of this custom section and its data) specified in + /// offsets relative to the start of the byte stream. + pub fn range(&self) -> Range { + self.range + } +} + +impl<'a> std::fmt::Debug for CustomSectionReader<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CustomSectionReader") + .field("name", &self.name) + .field("data_offset", &self.data_offset) + .field("data", &"...") + .field("range", &self.range) + .finish() + } +} diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index f215e6fb01..c80b532a2b 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -292,13 +292,8 @@ impl Printer { } bytes = &bytes[offset..]; } - Payload::CustomSection { - name: "name", - data_offset, - data, - range: _, - } => { - let reader = NameSectionReader::new(data, data_offset)?; + Payload::CustomSection(c) if c.name() == "name" => { + let reader = NameSectionReader::new(c.data(), c.data_offset())?; // Ignore any error associated with the name section. drop(self.register_names(state, reader)); @@ -395,15 +390,10 @@ impl Printer { name.write(&mut self.result); } } - Payload::CustomSection { - name, - data, - data_offset, - range: _, - } => { + Payload::CustomSection(c) => { let mut printers = mem::take(&mut self.printers); - if let Some(printer) = printers.get_mut(name) { - printer(self, data_offset, data)?; + if let Some(printer) = printers.get_mut(c.name()) { + printer(self, c.data_offset(), c.data())?; } self.printers = printers; } diff --git a/crates/wast/tests/annotations.rs b/crates/wast/tests/annotations.rs index b9809b0f08..b5ec894707 100644 --- a/crates/wast/tests/annotations.rs +++ b/crates/wast/tests/annotations.rs @@ -95,14 +95,10 @@ fn assert_local_name(name: &str, wat: &str) -> anyhow::Result<()> { fn get_name_section(wasm: &[u8]) -> anyhow::Result> { for payload in Parser::new(0).parse_all(&wasm) { - if let Payload::CustomSection { - name: "name", - data, - data_offset, - range: _, - } = payload? - { - return Ok(NameSectionReader::new(data, data_offset)?); + if let Payload::CustomSection(c) = payload? { + if c.name() == "name" { + return Ok(NameSectionReader::new(c.data(), c.data_offset())?); + } } } panic!("no name section found"); @@ -131,9 +127,9 @@ fn custom_section_order() -> anyhow::Result<()> { "#, )?; macro_rules! assert_matches { - ($a:expr, $b:pat $(,)?) => { + ($a:expr, $b:pat $(if $cond:expr)? $(,)?) => { match &$a { - $b => {} + $b $(if $cond)? => {} a => panic!("`{:?}` doesn't match `{}`", a, stringify!($b)), } }; @@ -142,22 +138,55 @@ fn custom_section_order() -> anyhow::Result<()> { .parse_all(&bytes) .collect::>>()?; assert_matches!(wasm[0], Payload::Version { .. }); - assert_matches!(wasm[1], Payload::CustomSection { name: "K", .. }); - assert_matches!(wasm[2], Payload::CustomSection { name: "F", .. }); + assert_matches!( + wasm[1], + Payload::CustomSection(c) if c.name() == "K" + ); + assert_matches!( + wasm[2], + Payload::CustomSection(c) if c.name() == "F" + ); assert_matches!(wasm[3], Payload::TypeSection(_)); - assert_matches!(wasm[4], Payload::CustomSection { name: "E", .. }); - assert_matches!(wasm[5], Payload::CustomSection { name: "C", .. }); - assert_matches!(wasm[6], Payload::CustomSection { name: "J", .. }); + assert_matches!( + wasm[4], + Payload::CustomSection(c) if c.name() == "E" + ); + assert_matches!( + wasm[5], + Payload::CustomSection(c) if c.name() == "C" + ); + assert_matches!( + wasm[6], + Payload::CustomSection(c) if c.name() == "J" + ); assert_matches!(wasm[7], Payload::FunctionSection(_)); - assert_matches!(wasm[8], Payload::CustomSection { name: "B", .. }); - assert_matches!(wasm[9], Payload::CustomSection { name: "I", .. }); + assert_matches!( + wasm[8], + Payload::CustomSection(c) if c.name() == "B" + ); + assert_matches!( + wasm[9], + Payload::CustomSection(c) if c.name() == "I" + ); assert_matches!(wasm[10], Payload::TableSection(_)); assert_matches!(wasm[11], Payload::CodeSectionStart { .. }); assert_matches!(wasm[12], Payload::CodeSectionEntry { .. }); - assert_matches!(wasm[13], Payload::CustomSection { name: "H", .. }); - assert_matches!(wasm[14], Payload::CustomSection { name: "G", .. }); - assert_matches!(wasm[15], Payload::CustomSection { name: "A", .. }); - assert_matches!(wasm[16], Payload::CustomSection { name: "D", .. }); + assert_matches!( + wasm[13], + Payload::CustomSection(c) if c.name() == "H" + ); + assert_matches!( + wasm[14], + Payload::CustomSection(c) if c.name() == "G" + ); + assert_matches!( + wasm[15], + Payload::CustomSection(c) if c.name() == "A" + ); + assert_matches!( + wasm[16], + Payload::CustomSection(c) if c.name() == "D" + ); match &wasm[17] { Payload::End(x) if *x == bytes.len() => {} diff --git a/fuzz/fuzz_targets/incremental-parse.rs b/fuzz/fuzz_targets/incremental-parse.rs index bcc3176113..0c0e8adaa0 100644 --- a/fuzz/fuzz_targets/incremental-parse.rs +++ b/fuzz/fuzz_targets/incremental-parse.rs @@ -118,24 +118,11 @@ fuzz_target!(|data: Vec>| { assert_eq!(ar, br); } (DataSection(a), DataSection(b)) => assert_eq!(a.range(), b.range()), - ( - CustomSection { - name: a, - data_offset: ado, - data: ad, - range: ar, - }, - CustomSection { - name: b, - data_offset: bdo, - data: bd, - range: br, - }, - ) => { - assert_eq!(a, b); - assert_eq!(ad, bd); - assert_eq!(ado, bdo); - assert_eq!(ar, br); + (CustomSection(ca), CustomSection(cb)) => { + assert_eq!(ca.name(), cb.name()); + assert_eq!(ca.data_offset(), cb.data_offset()); + assert_eq!(ca.data(), cb.data()); + assert_eq!(ca.range(), cb.range()); } ( CodeSectionStart { diff --git a/fuzz/fuzz_targets/mutate.rs b/fuzz/fuzz_targets/mutate.rs old mode 100755 new mode 100644 diff --git a/fuzz/fuzz_targets/roundtrip.rs b/fuzz/fuzz_targets/roundtrip.rs index 7a084e979e..a25e6daf20 100644 --- a/fuzz/fuzz_targets/roundtrip.rs +++ b/fuzz/fuzz_targets/roundtrip.rs @@ -61,12 +61,9 @@ fn validate_name_section(wasm: &[u8]) -> wasmparser::Result<()> { use wasmparser::*; for payload in Parser::new(0).parse_all(wasm) { let reader = match payload? { - Payload::CustomSection { - name: "name", - data_offset, - data, - range: _, - } => NameSectionReader::new(data, data_offset)?, + Payload::CustomSection(c) if c.name() == "name" => { + NameSectionReader::new(c.data(), c.data_offset())? + } _ => continue, }; for section in reader { diff --git a/src/bin/wasm-tools/objdump.rs b/src/bin/wasm-tools/objdump.rs index 375dc3d4dc..a98b966221 100644 --- a/src/bin/wasm-tools/objdump.rs +++ b/src/bin/wasm-tools/objdump.rs @@ -1,6 +1,6 @@ use anyhow::Result; use std::path::PathBuf; -use wasmparser::{Parser, Payload::*}; +use wasmparser::{CustomSectionReader, Parser, Payload::*}; /// Dumps information about sections in a WebAssembly file. /// @@ -46,18 +46,13 @@ impl Opts { ComponentStartSection { .. } => todo!("component-model"), AliasSection(_) => todo!("component-model"), - CustomSection { - name, - data_offset, - data, - range: _, - } => printer.section_raw( + CustomSection(c) => printer.section_raw( wasmparser::Range { - start: data_offset, - end: data_offset + data.len(), + start: c.data_offset(), + end: c.data_offset() + c.data().len(), }, 1, - &format!("custom {:?}", name), + &format!("custom {:?}", c.name()), ), UnknownSection { .. } => {}