use crate::dwarf::DwarfResolver;
use crate::util::std_crates;
use object::{Object, ObjectSymbol};
use std::collections::{HashMap, HashSet};
use std::path::Path;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Confidence {
Exact,
Heuristic,
Unknown,
}
impl Confidence {
pub fn as_str(self) -> &'static str {
match self {
Confidence::Exact => "exact",
Confidence::Heuristic => "heuristic",
Confidence::Unknown => "unknown",
}
}
}
pub struct RlibMap {
map: HashMap<String, String>,
}
impl RlibMap {
pub fn build(rlibs: &[(String, std::path::PathBuf)]) -> Self {
let mut map = HashMap::new();
for (crate_name, path) in rlibs {
if let Err(e) = index_rlib(&mut map, crate_name, path) {
eprintln!(
"cargo-treemap: warning: could not index {}: {e:#}",
path.display()
);
}
}
RlibMap { map }
}
fn get(&self, sym: &str) -> Option<&str> {
self.map.get(sym).map(String::as_str)
}
}
fn index_rlib(
map: &mut HashMap<String, String>,
crate_name: &str,
path: &Path,
) -> anyhow::Result<()> {
let file = std::fs::File::open(path)?;
let mmap = unsafe { memmap2::Mmap::map(&file) }?;
let archive = object::read::archive::ArchiveFile::parse(&*mmap)?;
for member in archive.members() {
let Ok(member) = member else { continue };
let Ok(data) = member.data(&*mmap) else {
continue;
};
let Ok(obj) = object::File::parse(data) else {
continue;
};
for sym in obj.symbols() {
if !sym.is_definition() {
continue;
}
if let Ok(name) = sym.name() {
if !name.is_empty() {
map.entry(name.to_string())
.or_insert_with(|| crate_name.to_string());
}
}
}
}
Ok(())
}
pub struct Attributor<'a> {
rlib: &'a RlibMap,
crates: &'a HashSet<String>,
std_set: HashSet<&'static str>,
split_std: bool,
}
pub struct Attributed {
pub crate_name: String,
pub confidence: Confidence,
pub demangled: String,
}
impl<'a> Attributor<'a> {
pub fn new(rlib: &'a RlibMap, crates: &'a HashSet<String>, split_std: bool) -> Self {
Attributor {
rlib,
crates,
std_set: std_crates(),
split_std,
}
}
fn canon(&self, name: &str) -> String {
if !self.split_std && self.std_set.contains(name) && !self.crates.contains(name) {
"std".to_string()
} else {
name.to_string()
}
}
fn first_known_ident(&self, s: &str) -> Option<String> {
let b = s.as_bytes();
let mut i = 0;
while i < b.len() {
let c = b[i];
if c.is_ascii_alphabetic() || c == b'_' {
let start = i;
while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == b'_') {
i += 1;
}
let tok = &s[start..i];
if self.crates.contains(tok) || self.std_set.contains(tok) {
return Some(tok.to_string());
}
} else {
i += 1;
}
}
None
}
pub fn attribute(&self, mangled: &str, address: u64, dwarf: Option<&DwarfResolver>) -> Attributed {
let demangled = format!("{:#}", rustc_demangle::demangle(mangled));
if let Some(dw) = dwarf {
if let Some(c) = dw.crate_for_addr(address) {
return Attributed {
crate_name: self.canon(&c),
confidence: Confidence::Exact,
demangled,
};
}
}
if let Some(c) = self.rlib.get(mangled) {
return Attributed {
crate_name: self.canon(c),
confidence: Confidence::Exact,
demangled,
};
}
if let Some(c) = self.first_known_ident(&demangled) {
return Attributed {
crate_name: self.canon(&c),
confidence: Confidence::Heuristic,
demangled,
};
}
let bucket = if demangled.contains("::") {
"[unknown]"
} else {
"[runtime]"
};
Attributed {
crate_name: bucket.to_string(),
confidence: Confidence::Unknown,
demangled,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn crates(names: &[&str]) -> HashSet<String> {
names.iter().map(|s| s.to_string()).collect()
}
#[test]
fn attribution_priority_and_buckets() {
let rlib = RlibMap::build(&[]); let set = crates(&["regex_automata", "serde"]);
let attr = Attributor::new(&rlib, &set, false);
assert_eq!(attr.attribute("regex_automata::dfa::search", 0, None).crate_name, "regex_automata");
assert_eq!(attr.attribute("<str as core::fmt::Display>::fmt", 0, None).crate_name, "std");
assert_eq!(attr.attribute("mystery::thing", 0, None).crate_name, "[unknown]");
assert_eq!(attr.attribute("_main", 0, None).crate_name, "[runtime]");
}
#[test]
fn split_std_keeps_core_separate() {
let rlib = RlibMap::build(&[]);
let set = crates(&[]);
let attr = Attributor::new(&rlib, &set, true); assert_eq!(attr.attribute("core::ptr::drop_in_place", 0, None).crate_name, "core");
}
#[test]
fn real_dep_named_like_std_vendored_is_not_collapsed() {
let rlib = RlibMap::build(&[]);
let set = crates(&["object"]);
let attr = Attributor::new(&rlib, &set, false);
assert_eq!(attr.attribute("object::read::parse", 0, None).crate_name, "object");
}
}