crates/mimir-binary/src/crates.rs
//! StableCrateId → crate identity (`package@version`, origin).
//!
//! A v0 symbol names its crate by NAME + StableCrateId. The StableCrateId is a
//! hash of (crate name, `-C metadata`, is-exe, rustc version): it differs
//! between `cargo check` and `cargo build`, between profiles and between
//! toolchains, so the source dump's ids (nightly `check`) never equal the
//! artifact's (stable `build --release`). The artifact names its own:
//!
//! | source (in order) | exact? | gives |
//! |----------------------------|--------|---------------------------------------------------|
//! | `cu` DWARF CU names | yes | crate + id + root file (artifact built with debuginfo) |
//! | `rlib` `--deps-dir` | yes | crate + id + root file (cargo's deps dir of the build) |
//! | `driver` `.stats.json` | yes | crate + id + package@version (artifact built BY the driver, `cargo mimir-dump --build`) |
//! | `sysroot` toolchain rlibs | yes | crate + id of std, core … and std's private deps (gimli, addr2line …) → `lang` |
//! | `name` unique name | by name| the ONE package whose lib crate has that name (cargo metadata), or a toolchain crate |
//! | ambiguous | — | counted in the ledger, version `?`, never guessed |
//!
//! Root file → package@version: the cargo-metadata target whose `src_path` is
//! that file; `/rustc/<commit>/library/…` and `/rust/deps/…` (the sysroot's
//! remapped paths) are toolchain crates (origin `lang`, rust-analyzer's URL
//! version, as the driver writes them).
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use mimir_schema::moniker;
use mimir_schema::Origin;
use crate::elf::{CuCrate, RlibCrate};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CrateIdent {
pub package: String,
pub version: String,
pub origin: Origin,
/// the driver's descriptor prefix for a NON-lib target (`<bin>:`), empty
/// for a lib — part of every moniker of that crate
pub prefix: String,
}
/// How a crate id was resolved.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Via {
Cu,
Rlib,
Driver,
Sysroot,
Name,
Ambiguous,
Unknown,
}
impl Via {
pub fn as_str(self) -> &'static str {
match self {
Via::Cu => "cu",
Via::Rlib => "rlib",
Via::Driver => "driver",
Via::Sysroot => "sysroot",
Via::Name => "name",
Via::Ambiguous => "ambiguous",
Via::Unknown => "unknown",
}
}
}
/// One package from cargo metadata.
#[derive(Debug, Clone)]
pub struct MetaPackage {
pub name: String,
pub version: String,
pub local: bool,
/// (crate name, src_path, kind) of every target — `kind` as cargo names
/// it, `lib` for the library in any crate type
pub targets: Vec<(String, PathBuf, String)>,
}
/// The toolchain's own crates (and the sysroot's private deps are recognised
/// by their remapped root path, not by name).
pub const LANG_CRATES: &[&str] = &[
"std", "core", "alloc", "proc_macro", "test", "compiler_builtins", "panic_unwind", "panic_abort",
"unwind", "std_detect", "rustc_std_workspace_core", "rustc_std_workspace_alloc", "rustc_std_workspace_std",
"profiler_builtins", "sysroot",
];
#[derive(Debug, Default)]
pub struct Resolver {
by_id: HashMap<u64, (CrateIdent, Via)>,
/// crate name → packages (cargo metadata)
meta_by_crate: HashMap<String, Vec<MetaPackage>>,
/// src_path → (package, version, local, target kind)
meta_by_src: HashMap<PathBuf, (String, String, bool, String)>,
/// rel-path suffix lookup for relative dep-info paths
meta_srcs: Vec<(PathBuf, String, String, bool, String)>,
/// crate name → the non-lib targets of that name: (package, target kind)
meta_bins: HashMap<String, Vec<(MetaPackage, String)>>,
/// crate name → ids known exactly (cu / rlib / driver / sysroot)
known_names: HashMap<String, Vec<u64>>,
pub conflicts: Vec<String>,
cache: HashMap<(String, u64), (Option<CrateIdent>, Via)>,
}
impl Resolver {
pub fn new(meta: &[MetaPackage]) -> Resolver {
let mut r = Resolver::default();
for p in meta {
for (cname, src, kind) in &p.targets {
let src = canon(src);
r.meta_by_src.insert(src.clone(), (p.name.clone(), p.version.clone(), p.local, kind.clone()));
r.meta_srcs.push((src, p.name.clone(), p.version.clone(), p.local, kind.clone()));
if moniker::LIB_TARGET_KINDS.contains(&kind.as_str()) {
r.meta_by_crate.entry(cname.clone()).or_default().push(p.clone());
} else {
r.meta_bins.entry(cname.clone()).or_default().push((p.clone(), kind.clone()));
}
}
}
r
}
fn insert(&mut self, crate_name: &str, id: u64, ident: CrateIdent, via: Via) {
let ids = self.known_names.entry(crate_name.to_string()).or_default();
if !ids.contains(&id) {
ids.push(id);
}
match self.by_id.get(&id) {
Some((old, _)) if *old != ident => {
self.conflicts.push(format!("{id:016x}: {}@{} vs {}@{} ({})", old.package, old.version, ident.package, ident.version, via.as_str()));
}
Some(_) => {}
None => {
self.by_id.insert(id, (ident, via));
}
}
}
/// A crate root file → its identity.
pub fn ident_of_root(&self, crate_name: &str, root: &Path) -> Option<CrateIdent> {
let s = root.to_string_lossy();
if s.starts_with("/rustc/") || s.starts_with("/rust/deps/") || s.contains("/lib/rustlib/src/rust/") {
return Some(lang(crate_name));
}
let mk = |p: &String, v: &String, local: bool, kind: &str| CrateIdent {
package: p.clone(),
version: v.clone(),
origin: if local { Origin::Local } else { Origin::Library },
prefix: moniker::target_prefix(kind, crate_name),
};
let c = canon(root);
if let Some((p, v, local, kind)) = self.meta_by_src.get(&c) {
return Some(mk(p, v, *local, kind));
}
if root.is_relative() {
let hits: Vec<_> = self.meta_srcs.iter().filter(|(src, ..)| src.ends_with(root)).collect();
if hits.len() == 1 {
let (_, p, v, local, kind) = hits[0];
return Some(mk(p, v, *local, kind));
}
}
// cargo's registry layout: …/registry/src/<index>/<name>-<version>/…
registry_dir_ident(root)
}
pub fn add_cus(&mut self, cus: &[CuCrate]) {
for cu in cus {
if let Some(id) = self.ident_of_root(&cu.crate_name, &cu.root_file) {
self.insert(&cu.crate_name, cu.stable_crate_id, id, Via::Cu);
}
}
}
pub fn add_rlibs(&mut self, rlibs: &[RlibCrate]) {
for r in rlibs {
let Some(root) = &r.root_file else { continue };
let Some(ident) = self.ident_of_root(&r.crate_name, root) else { continue };
for id in &r.ids {
self.insert(&r.crate_name, *id, ident.clone(), Via::Rlib);
}
}
}
/// Sysroot rlibs: toolchain crates (std's private deps included), origin
/// `lang` like the driver writes every crate under the sysroot.
pub fn add_sysroot(&mut self, rlibs: &[RlibCrate]) {
for r in rlibs {
for id in &r.ids {
self.insert(&r.crate_name, *id, lang(&r.crate_name), Via::Sysroot);
}
}
}
/// Driver records: `(id, crate name, package@version + prefix)`. The
/// driver knows which target of the package it compiled and writes the
/// prefix of a non-lib one (`moniker::target_prefix`).
pub fn add_driver(&mut self, ids: &[(u64, String, CrateIdent)]) {
for (id, crate_name, ident) in ids {
self.insert(crate_name, *id, ident.clone(), Via::Driver);
}
}
/// Resolve one `(crate name, StableCrateId)` from a symbol.
pub fn resolve(&mut self, crate_name: &str, id: u64) -> (Option<CrateIdent>, Via) {
if let Some(r) = self.cache.get(&(crate_name.to_string(), id)) {
return r.clone();
}
let r = self.compute(crate_name, id);
self.cache.insert((crate_name.to_string(), id), r.clone());
r
}
fn compute(&self, crate_name: &str, id: u64) -> (Option<CrateIdent>, Via) {
if let Some((ident, via)) = self.by_id.get(&id) {
return (Some(ident.clone()), *via);
}
let libs = self.meta_by_crate.get(crate_name).map(Vec::as_slice).unwrap_or(&[]);
let bins = self.meta_bins.get(crate_name).map(Vec::as_slice).unwrap_or(&[]);
let bin_ident = |(p, kind): &(MetaPackage, String)| CrateIdent {
package: p.name.clone(),
version: p.version.clone(),
origin: if p.local { Origin::Local } else { Origin::Library },
prefix: moniker::target_prefix(kind, crate_name),
};
// Other ids of this name are known exactly, this one is not: it is a
// DIFFERENT compile of that name — a bin target named like its
// package's lib (`lgz` bin + `lgz` lib), or another version.
if self.known_names.contains_key(crate_name) {
return if bins.len() == 1 { (Some(bin_ident(&bins[0])), Via::Name) } else { (None, Via::Ambiguous) };
}
match (libs.len(), bins.len(), LANG_CRATES.contains(&crate_name)) {
(0, 0, true) => (Some(lang(crate_name)), Via::Name),
(1, 0, false) => {
let p = &libs[0];
(
Some(CrateIdent {
package: p.name.clone(),
version: p.version.clone(),
origin: if p.local { Origin::Local } else { Origin::Library },
prefix: String::new(),
}),
Via::Name,
)
}
(0, 1, false) => (Some(bin_ident(&bins[0])), Via::Name),
(0, 0, false) => (None, Via::Unknown),
// a lib and a bin of one name, or several packages: never guessed
_ => (None, Via::Ambiguous),
}
}
pub fn known_ids(&self) -> BTreeMap<String, usize> {
let mut m = BTreeMap::new();
for (_, via) in self.by_id.values() {
*m.entry(via.as_str().to_string()).or_default() += 1;
}
m
}
}
fn lang(crate_name: &str) -> CrateIdent {
CrateIdent {
package: crate_name.to_string(),
version: moniker::lang_version(crate_name),
origin: Origin::Lang,
prefix: String::new(),
}
}
fn canon(p: &Path) -> PathBuf {
std::fs::canonicalize(p).unwrap_or_else(|_| p.to_path_buf())
}
fn registry_dir_ident(root: &Path) -> Option<CrateIdent> {
let comps: Vec<String> = root.components().map(|c| c.as_os_str().to_string_lossy().into_owned()).collect();
let i = comps.iter().position(|c| c == "registry")?;
if comps.get(i + 1).map(String::as_str) != Some("src") {
return None;
}
let dir = comps.get(i + 3)?;
// `<name>-<semver>`: the version starts after the last `-` followed by a digit
let cut = dir.char_indices().filter(|(j, c)| *c == '-' && dir[j + 1..].starts_with(|d: char| d.is_ascii_digit())).map(|(j, _)| j).next_back()?;
Some(CrateIdent { package: dir[..cut].to_string(), version: dir[cut + 1..].to_string(), origin: Origin::Library, prefix: String::new() })
}
/// `cargo metadata --format-version 1` JSON → packages.
pub fn packages_from_metadata(meta: &serde_json::Value) -> Vec<MetaPackage> {
let members: Vec<&str> = meta["workspace_members"].as_array().map(|a| a.iter().filter_map(|v| v.as_str()).collect()).unwrap_or_default();
let mut out = Vec::new();
for p in meta["packages"].as_array().into_iter().flatten() {
let id = p["id"].as_str().unwrap_or_default();
let targets = p["targets"]
.as_array()
.into_iter()
.flatten()
.filter_map(|t| {
let kinds: Vec<&str> = t["kind"].as_array().into_iter().flatten().filter_map(|k| k.as_str()).collect();
let kind = if kinds.iter().any(|k| moniker::LIB_TARGET_KINDS.contains(k)) { "lib" } else { kinds.first().copied()? };
Some((t["name"].as_str()?.replace('-', "_"), PathBuf::from(t["src_path"].as_str()?), kind.to_string()))
})
.collect();
out.push(MetaPackage {
name: p["name"].as_str().unwrap_or_default().to_string(),
version: p["version"].as_str().unwrap_or_default().to_string(),
local: members.contains(&id),
targets,
});
}
out
}
/// Run `cargo metadata` for a manifest.
pub fn cargo_metadata(manifest: &Path) -> Result<Vec<MetaPackage>, String> {
let o = std::process::Command::new("cargo")
.args(["metadata", "--format-version", "1", "--manifest-path"])
.arg(manifest)
.output()
.map_err(|e| format!("cargo metadata: {e}"))?;
if !o.status.success() {
return Err(format!("cargo metadata failed: {}", String::from_utf8_lossy(&o.stderr)));
}
let v: serde_json::Value = serde_json::from_slice(&o.stdout).map_err(|e| e.to_string())?;
Ok(packages_from_metadata(&v))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_layout() {
let i = registry_dir_ident(Path::new("/home/x/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/serde-json-1.0.228/src/lib.rs")).unwrap();
assert_eq!((i.package.as_str(), i.version.as_str()), ("serde-json", "1.0.228"));
assert!(registry_dir_ident(Path::new("/w/a/src/lib.rs")).is_none());
}
#[test]
fn sysroot_paths_are_lang() {
let r = Resolver::new(&[]);
let i = r.ident_of_root("memchr", Path::new("/rust/deps/memchr-2.7.6/src/lib.rs")).unwrap();
assert_eq!(i.origin, Origin::Lang);
assert_eq!(i.version, moniker::lang_version("memchr"));
}
#[test]
fn ambiguous_names_are_not_guessed() {
let pk = |v: &str| MetaPackage { name: "a".into(), version: v.into(), local: false, targets: vec![("a".into(), PathBuf::from(format!("/x/a-{v}/src/lib.rs")), "lib".into())] };
let mut r = Resolver::new(&[pk("0.1.0"), pk("0.0.9")]);
assert_eq!(r.resolve("a", 42), (None, Via::Ambiguous));
r.add_driver(&[(42, "a".into(), CrateIdent { package: "a".into(), version: "0.0.9".into(), origin: Origin::Library, prefix: String::new() })]);
// cached answer is per (name, id); a fresh id resolves through the driver map
assert_eq!(r.resolve("a", 43).1, Via::Ambiguous);
let mut r2 = Resolver::new(&[pk("0.1.0"), pk("0.0.9")]);
r2.add_driver(&[(42, "a".into(), CrateIdent { package: "a".into(), version: "0.0.9".into(), origin: Origin::Library, prefix: String::new() })]);
assert_eq!(r2.resolve("a", 42).1, Via::Driver);
assert_eq!(r2.resolve("a", 42).0.unwrap().prefix, "");
// an unknown id of a name whose other ids are known: never the lib's
assert_eq!(r2.resolve("a", 44), (None, Via::Ambiguous));
// a bin named like its package's lib resolves to the bin target (prefixed)
let lgz = MetaPackage {
name: "lgz".into(),
version: "0.3.14".into(),
local: true,
targets: vec![("lgz".into(), PathBuf::from("/w/lgz/src/lib.rs"), "lib".into()), ("lgz".into(), PathBuf::from("/w/lgz/src/bin/lgz.rs"), "bin".into())],
};
let mut r3 = Resolver::new(&[lgz]);
assert_eq!(r3.resolve("lgz", 1), (None, Via::Ambiguous), "lib or bin: not guessed by name alone");
let mut r4 = Resolver::new(&[r3_pkg()]);
r4.add_rlibs(&[RlibCrate { crate_name: "lgz".into(), ids: vec![5], root_file: Some(PathBuf::from("/w/lgz/src/lib.rs")), path: PathBuf::new() }]);
let (bin, via) = r4.resolve("lgz", 6);
assert_eq!((bin.unwrap().prefix.as_str(), via), ("`bin=lgz`:", Via::Name));
assert_eq!(r4.resolve("lgz", 5).0.unwrap().prefix, "");
}
fn r3_pkg() -> MetaPackage {
MetaPackage {
name: "lgz".into(),
version: "0.3.14".into(),
local: true,
targets: vec![("lgz".into(), PathBuf::from("/w/lgz/src/lib.rs"), "lib".into()), ("lgz".into(), PathBuf::from("/w/lgz/src/bin/lgz.rs"), "bin".into())],
}
}
}