use addr2line::gimli::{Dwarf, EndianRcSlice, LittleEndian, SectionId};
use std::{collections::BTreeMap, path::PathBuf, rc::Rc};
use wasmer_types::{LocalFunctionIndex, ModuleInfo, entity::PrimaryMap};
use crate::{
FunctionBodyData, ModuleTranslationState,
wasmparser::{BinaryReader, FunctionBody},
};
#[derive(Clone, Debug)]
pub struct SourceLocation {
pub file: String,
pub directory: String,
pub line: u32,
pub column: u32,
}
#[derive(Default)]
pub struct WasmSourceMap {
locations: BTreeMap<usize, SourceLocation>,
}
impl WasmSourceMap {
pub fn new(
module: &ModuleInfo,
translation: &ModuleTranslationState,
functions: &PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
) -> Result<Self, String> {
let Some(code_base) = translation.code_section_offset() else {
return Ok(Self::default());
};
type Reader = EndianRcSlice<LittleEndian>;
let dwarf = match Dwarf::<Reader>::load(|id: SectionId| {
let data = module.custom_sections(id.name()).next().unwrap_or_default();
Ok::<_, addr2line::gimli::Error>(Reader::new(Rc::from(data), LittleEndian))
}) {
Ok(dwarf) => dwarf,
Err(err) => return Err(format!("cannot parse DWARF file: {err}")),
};
let context = match addr2line::Context::from_dwarf(dwarf) {
Ok(context) => context,
Err(err) => return Err(format!("cannot construct addr2line Context: {err}")),
};
let mut locations = BTreeMap::new();
for (_, input) in functions.iter() {
let body = FunctionBody::new(BinaryReader::new(input.data, input.module_offset));
let Ok(mut operators) = body.get_operators_reader() else {
continue;
};
while !operators.eof() {
let offset = operators.original_position();
if operators.read().is_err() {
return Err("Wasm parsing error".to_string());
}
let address = offset.checked_sub(code_base).ok_or_else(|| {
format!(
"Wasm operator offset {offset} precedes code section offset {code_base}"
)
})? as u64;
let Ok(Some(location)) = context.find_location(address) else {
continue;
};
let (Some(file), Some(line)) = (location.file, location.line) else {
continue;
};
let path = PathBuf::from(file);
let directory = path
.parent()
.and_then(|p| p.to_str().map(|p| p.to_string()))
.unwrap_or_default();
let filename = path
.file_name()
.and_then(|p| p.to_str().map(|p| p.to_string()))
.unwrap_or_default();
locations.insert(
offset,
SourceLocation {
file: filename,
directory,
line,
column: location.column.unwrap_or(0),
},
);
}
}
Ok(Self { locations })
}
pub fn get(&self, wasm_offset: usize) -> Option<&SourceLocation> {
self.locations.get(&wasm_offset)
}
pub fn first_in_function(&self, body: &FunctionBodyData<'_>) -> Option<&SourceLocation> {
self.locations
.range(body.module_offset..body.module_offset.saturating_add(body.data.len()))
.next()
.map(|(_, location)| location)
}
}