blob: d38dbfb9dfcd62619117ee7957f283f38402e1d5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[macro_use]
mod log;
mod location;
mod path;
mod wasm;
use fallible_iterator::FallibleIterator;
use gimli::{ColumnType, Dwarf, EndianSlice, LittleEndian, Reader, ReaderOffset};
use indexmap::IndexMap;
use js_sys::JsString;
use location::{LocationEntry, Pos, SourceMapEntry};
use path::Path;
use std::collections::HashMap;
use std::convert::TryInto;
use std::rc::Rc;
use wasm::{parse_sections, Error, SectionKind};
use wasm_bindgen::prelude::*;
pub struct FileEntries {
pub filename: Rc<JsString>,
pub entries: Vec<LocationEntry>,
}
enum FuncState {
Start,
Ignored,
Normal,
}
#[wasm_bindgen]
#[derive(Default)]
pub struct Resolver {
locations: Vec<LocationEntry>,
reverse_locations: IndexMap<String, FileEntries>,
}
impl Resolver {
pub fn new<R: Reader + Clone + Default>(src: R) -> Result<Resolver, Error> {
#[cfg(feature = "log")]
console_error_panic_hook::set_once();
let mut code_section_offset = None;
let mut sections = HashMap::new();
for section in parse_sections(src.clone())?.iterator() {
let section = section?;
match section.kind {
SectionKind::Custom { name } => {
if name.starts_with(".debug_") {
sections.insert(name, section.payload);
}
}
SectionKind::Standard { id } if id.get() == 10 => {
code_section_offset = Some(section.payload.offset_from(&src));
}
_ => {}
}
}
let code_section_offset: u32 = code_section_offset
.ok_or_else(|| Error::MissingCodeSection)?
.into_u64()
.try_into()
.unwrap();
let dwarf = Dwarf::load::<_, _, Error>(
|id| Ok(sections.get(id.name()).cloned().unwrap_or_default()),
|_| Ok(Default::default()),
)?;
let mut res = Self::default();
let mut iter = dwarf.units();
while let Some(unit) = iter.next()? {
let mut unit = dwarf.unit(unit)?;
let line_program = match unit.line_program.take() {
Some(line_program) => line_program,
None => continue,
};
let is_rust = {
let mut entries = unit.entries();
entries.next_entry()?;
match entries
.current()
.unwrap()
.attr_value(gimli::DW_AT_language)?
{
Some(gimli::AttributeValue::Language(gimli::constants::DW_LANG_Rust)) => true,
_ => false,
}
};
let unit_dir = Path::new(
unit
.comp_dir
.as_ref()
.map(|comp_dir| comp_dir.to_string())
.transpose()?
.unwrap_or_default(),
);
let mut rows = line_program.rows();
let mut func_state = FuncState::Start;
while let Some((header, row)) = rows.next_row()? {
if let FuncState::Start = func_state {
func_state = if row.address() == 0 {
FuncState::Ignored
} else {
FuncState::Normal
};
}
if let FuncState::Ignored = func_state {
if row.end_sequence() {
func_state = FuncState::Start;
}
continue;
}
let file = match row.file(header) {
Some(file) => file,
None => continue,
};
let pos = {
let line = match row.line() {
Some(line) => line.checked_sub(1).unwrap().try_into().unwrap(),
None => continue, // couldn't attribute instruction to any line
};
let column = match row.column() {
ColumnType::Column(mut column) => {
// DWARF columns are 1-based, Source Map are 0-based.
column -= 1;
// ...but Rust doesn't implement DWARF columns correctly
// (see https://github.com/rust-lang/rust/issues/65437)
if is_rust {
column += 1;
}
column.try_into().unwrap()
}
ColumnType::LeftEdge => 0,
};
Pos::new(line, column)
};
let addr: u32 = row.address().try_into().unwrap();
let mut path = unit_dir.borrow();
let dir_value;
if let Some(dir) = file.directory(header) {
dir_value = dwarf.attr_string(&unit, dir)?;
path.push(dir_value.to_string()?);
}
let path_name_value = dwarf.attr_string(&unit, file.path_name())?;
path.push(path_name_value.to_string()?);
let dest = path.to_uri();
let file_entries = match res.reverse_locations.entry(dest) {
indexmap::map::Entry::Occupied(entry) => entry.into_mut(),
indexmap::map::Entry::Vacant(entry) => {
let filename = Rc::new(JsString::from(entry.key().as_str()));
entry.insert(FileEntries {
filename,
entries: Vec::new(),
})
}
};
let loc = LocationEntry::new(code_section_offset + addr, &file_entries.filename, pos);
res.locations.push(loc.clone());
file_entries.entries.push(loc);
if row.end_sequence() {
func_state = FuncState::Start;
}
}
}
res.locations.sort_by_key(|loc| loc.addr());
res.locations.dedup_by_key(|loc| loc.addr());
for file_entries in res.reverse_locations.values_mut() {
let entries = &mut file_entries.entries;
entries.sort_by_key(|loc| loc.pos());
entries.dedup_by_key(|loc| loc.pos());
}
Ok(res)
}
}
#[wasm_bindgen]
impl Resolver {
#[wasm_bindgen(constructor)]
pub fn from_slice(src: &[u8]) -> Result<Resolver, JsValue> {
Self::new(EndianSlice::new(src, LittleEndian))
.map_err(|err| js_sys::Error::new(&err.to_string()).into())
}
#[wasm_bindgen(js_name = listFiles)]
pub fn list_files(&self) -> js_sys::Array {
let array = js_sys::Array::new();
for file_entries in self.reverse_locations.values() {
array.push(&file_entries.filename);
}
array
}
#[wasm_bindgen(js_name = listMappings)]
pub fn list_mappings(&self) -> js_sys::Array {
// This method will convert all mappings, which sort of
// goes against the idea of doing that lazily as an optimisation,
// but it's used only by blackboxing implementation in DevTools,
// and so shouldn't be called in most common scenarios.
let array = js_sys::Array::new();
for loc in &self.locations {
array.push(loc.as_js());
}
array
}
pub fn resolve(&self, addr: u32) -> Option<SourceMapEntry> {
let idx = match self.locations.binary_search_by_key(&addr, |loc| loc.addr()) {
Ok(idx) => idx,
// Check that we're not going before the beginning.
Err(idx) => idx.checked_sub(1)?,
};
Some(self.locations[idx].as_js().clone())
}
#[wasm_bindgen(js_name = resolveReverse)]
pub fn resolve_reverse(&self, file: &str, line: u32, column: u32) -> Option<SourceMapEntry> {
let entries = &self.reverse_locations.get(file)?.entries;
// Find arbitrary position that is >= than requested.
let idx = entries
.binary_search_by_key(&Pos::new(line, column), |loc| loc.pos())
.unwrap_or_else(|idx| idx);
// Check that we're not past the end.
let loc = entries.get(idx)?;
// Make sure we found a position on the same line.
if loc.pos().line() != line {
return None;
}
Some(loc.as_js().clone())
}
}