use super::Mutator;
use crate::{Result, WasmMutate};
use rand::RngExt;
use wasm_encoder::{ExportKind, ExportSection, Module};
use wasmparser::ExportSectionReader;
#[derive(Clone, Copy)]
pub struct RenameExportMutator {
pub max_name_size: usize,
}
impl RenameExportMutator {
fn limited_string(&self, config: &mut WasmMutate, original: &str) -> crate::Result<String> {
loop {
config.consume_fuel(1)?;
let mut bytes = original.as_bytes().to_vec();
config.raw_mutate(&mut bytes, self.max_name_size)?;
match std::str::from_utf8(&bytes) {
Ok(_) => {}
Err(e) => {
let i = e.valid_up_to();
bytes.drain(i..);
}
};
if bytes.len() > self.max_name_size {
continue;
}
let ret = String::from_utf8(bytes).unwrap();
if ret != original && config.info().export_names.contains(&ret) {
continue;
}
return Ok(ret);
}
}
}
impl Mutator for RenameExportMutator {
fn mutate<'a>(
&self,
config: &'a mut WasmMutate,
) -> Result<Box<dyn Iterator<Item = Result<Module>> + 'a>> {
let mut exports = ExportSection::new();
let exports_idx = config.info().exports.unwrap();
let reader = config.info().get_binary_reader(exports_idx);
let reader = ExportSectionReader::new(reader)?;
let max_exports = u64::from(reader.count());
let skip_at = config.rng().random_range(0..max_exports);
for (i, export) in reader.into_iter().enumerate() {
let export = export?;
config.consume_fuel(1)?;
let new_name = if skip_at != i as u64 {
String::from(export.name)
} else {
let new_name = self.limited_string(config, export.name)?;
log::debug!("Renaming export {export:?} by {new_name:?}");
new_name
};
match export.kind {
wasmparser::ExternalKind::Func => {
exports.export(new_name.as_str(), ExportKind::Func, export.index);
}
wasmparser::ExternalKind::Table => {
exports.export(new_name.as_str(), ExportKind::Table, export.index);
}
wasmparser::ExternalKind::Memory => {
exports.export(new_name.as_str(), ExportKind::Memory, export.index);
}
wasmparser::ExternalKind::Global => {
exports.export(new_name.as_str(), ExportKind::Global, export.index);
}
_ => {
panic!("Unknown export {export:?}")
}
}
}
Ok(Box::new(std::iter::once(Ok(config
.info()
.replace_section(
config.info().exports.unwrap(),
&exports,
)))))
}
fn can_mutate<'a>(&self, config: &'a WasmMutate) -> bool {
!config.preserve_semantics && config.info().has_exports() && config.info().exports_count > 0
}
}
#[cfg(test)]
mod tests {
use super::RenameExportMutator;
use crate::WasmMutate;
use std::sync::Arc;
#[test]
fn test_rename_export_mutator() {
let mut config = WasmMutate::default();
config.raw_mutate_func(Some(Arc::new(|data, _| {
assert_eq!(data, b"exported_func");
*data = Vec::new();
Ok(())
})));
config.match_mutation(
r#"
(module
(func (export "exported_func") (result i32)
i32.const 42
)
)
"#,
RenameExportMutator { max_name_size: 2 }, r#"(module
(type (;0;) (func (result i32)))
(func (;0;) (type 0) (result i32)
i32.const 42)
(export "" (func 0)))"#,
);
}
}