use anyhow::Result;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Write;
use wasm_encoder::reencode::{Reencode, RoundtripReencoder};
use wasm_encoder::{ImportSection, RawSection};
use wasmparser::{Parser, Payload::*};
#[derive(Default)]
pub struct ModuleImportMap {
renamed_to_original: HashMap<String, HashMap<String, Option<String>>>,
}
impl ModuleImportMap {
pub fn new<'a>(wasm: Cow<'a, [u8]>) -> Result<(Cow<'a, [u8]>, Option<ModuleImportMap>)> {
let mut module = wasm_encoder::Module::new();
let mut ret = ModuleImportMap::default();
let mut found_duplicate_imports = false;
for payload in Parser::new(0).parse_all(&wasm) {
let payload = payload?;
match &payload {
Version { encoding, .. } if *encoding == wasmparser::Encoding::Component => {
assert!(!found_duplicate_imports);
break;
}
ImportSection(i) => {
let mut new_import_section = ImportSection::new();
for import in i.clone().into_imports() {
found_duplicate_imports = ret
.push_import(&mut new_import_section, import?)?
|| found_duplicate_imports;
}
module.section(&new_import_section);
}
_ => {
if let Some((id, range)) = payload.as_section() {
module.section(&RawSection {
id,
data: &wasm[range],
});
}
}
}
}
if found_duplicate_imports {
Ok((module.finish().into(), Some(ret)))
} else {
Ok((wasm, None))
}
}
fn push_import(
&mut self,
new_import_section: &mut ImportSection,
import: wasmparser::Import<'_>,
) -> Result<bool> {
let module_map = self
.renamed_to_original
.entry(import.module.to_string())
.or_insert(HashMap::new());
if !module_map.contains_key(import.name) {
let prev = module_map.insert(import.name.to_string(), None);
assert!(prev.is_none());
RoundtripReencoder.parse_import(new_import_section, import)?;
return Ok(false);
}
let mut new_name = import.name.to_string();
for i in 2.. {
new_name.truncate(import.name.len());
write!(new_name, " [v{i}]").unwrap();
if !module_map.contains_key(&new_name) {
break;
}
}
let mut new_import = import;
new_import.name = &new_name;
RoundtripReencoder.parse_import(new_import_section, new_import)?;
let prev = module_map.insert(new_name, Some(import.name.to_string()));
assert!(prev.is_none());
Ok(true)
}
pub fn original_name(&self, import: &wasmparser::Import<'_>) -> Option<&str> {
self.renamed_to_original
.get(import.module)?
.get(import.name)?
.as_deref()
}
}