use std::collections::BTreeMap;
use anyhow::Context;
use itertools::Itertools;
use rudy_dwarf::{
self, Binary, DebugFile, SourceFile, SymbolName, TypeName,
file::index_debug_file_sources,
find_type_by_name,
function::FunctionIndexEntry,
symbols::{DebugFiles, SymbolIndex},
types::{DieTypeDefinition, resolve_type_offset},
};
use crate::database::Db;
#[salsa::tracked(debug)]
pub struct Index<'db> {
#[returns(ref)]
pub debug_files: DebugFiles,
#[returns(ref)]
pub symbol_index: SymbolIndex,
pub indexed_debug_files: Vec<DebugFile>,
#[returns(ref)]
pub source_to_file: BTreeMap<SourceFile, Vec<DebugFile>>,
}
impl<'db> Index<'db> {
#[tracing::instrument(skip_all)]
pub fn get_function(
&self,
db: &'db dyn Db,
name: &SymbolName,
) -> Option<(DebugFile, FunctionIndexEntry<'_>)> {
let symbol_index = self.symbol_index(db);
let sym = symbol_index
.get_functions_by_lookup_name(&name.lookup_name)?
.get(name)
.or_else(|| {
tracing::info!("{name} not found in root symbol index");
None
})?
.clone();
let indexed = symbol_index.function_index(db, sym.debug_file)?;
indexed
.by_symbol_name(db)
.get(name)
.cloned()
.or_else(|| {
tracing::debug!(
"Function {name} not found in debug file {}, {:#?}",
sym.debug_file.name(db),
indexed.by_symbol_name(db)
);
None
})
.map(|entry| (sym.debug_file, entry))
}
}
#[tracing::instrument(skip_all)]
#[salsa::tracked(returns(ref))]
pub fn debug_index<'db>(db: &'db dyn Db, binary: Binary) -> Index<'db> {
let Ok((debug_files, symbol_index)) = rudy_dwarf::symbols::index_symbol_map(db, binary)
.with_context(|| {
format!(
"Failed to index debug files for binary: {}",
binary.name(db)
)
})
.inspect_err(|e| {
tracing::error!("Failed to index debug files: {e:?}");
})
else {
return Index::new(
db,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
);
};
tracing::trace!("Debug files found: {debug_files:#?}",);
let mut indexed_debug_files = vec![];
let mut source_file_index: BTreeMap<SourceFile, Vec<DebugFile>> = Default::default();
let workspace_root = rudy_dwarf::file::detect_cargo_root();
if workspace_root.is_none() {
tracing::warn!(
"Could not find Cargo workspace root, debug and source file indexing may be incomplete."
);
}
for debug_file in debug_files.values() {
let (_, sources) = index_debug_file_sources(db, *debug_file);
for source in sources {
source_file_index
.entry(source.clone())
.or_default()
.push(*debug_file);
}
if let Some(ref workspace_root) = workspace_root {
if sources.iter().any(|s| {
let p = &s.path;
p.starts_with(workspace_root) || p.starts_with(".")
}) {
tracing::debug!(
"Indexing debug file {} with local sources.",
debug_file.name(db),
);
indexed_debug_files.push(*debug_file);
} else {
tracing::debug!(
"Skipping debug file {} with no local sources.",
debug_file.name(db),
);
}
}
}
Index::new(
db,
debug_files,
symbol_index,
indexed_debug_files,
source_file_index,
)
}
#[salsa::tracked]
pub fn find_closest_function<'db>(
db: &'db dyn Db,
binary: Binary,
function_name: &'db str,
) -> Option<(SymbolName, DebugFile)> {
let index = debug_index(db, binary);
let mut segments = function_name
.split("::")
.map(|s| s.to_owned())
.collect::<Vec<String>>();
let name = segments.pop()?;
let module: Vec<String> = segments.iter().map(|s| s.to_string()).collect();
for (indexed_name, entry) in index.symbol_index(db).get_functions_by_lookup_name(&name)? {
if indexed_name.matches_name_and_module(&name, &module) {
return Some((indexed_name.clone(), entry.debug_file));
}
}
None
}
pub fn find_all_by_address(
db: &dyn Db,
binary: Binary,
address: u64,
) -> Vec<(SymbolName, rudy_dwarf::file::SourceLocation)> {
let index = debug_index(db, binary).symbol_index(db);
let Some((_, function_symbols)) = index.function_at_address(address) else {
tracing::debug!(
"No function found at address {address:#x} in binary {}",
binary.name(db)
);
return vec![];
};
function_symbols
.iter()
.map(|s| s.debug_file)
.unique()
.flat_map(|debug_file| {
let Some(function_index) = index.function_index(db, debug_file) else {
tracing::warn!(
"No function index found for debug file {}",
debug_file.name(db)
);
return vec![];
};
function_index.address_to_locations(db, address)
})
.collect()
}
pub fn resolve_type(
db: &dyn Db,
binary: Binary,
type_name: &str,
) -> anyhow::Result<Option<DieTypeDefinition>> {
let (segments, name) = if let Some((name, generics)) = type_name.split_once('<') {
let mut segments = name
.split("::")
.map(|s| s.to_owned())
.collect::<Vec<String>>();
let type_name = segments.pop().context("Type name cannot be empty")?;
(segments, format!("{type_name}<{generics}"))
} else {
let mut segments = type_name
.split("::")
.map(|s| s.to_owned())
.collect::<Vec<String>>();
let type_name = segments.pop().context("Type name cannot be empty")?;
(segments, type_name)
};
let parsed = TypeName::parse(&segments, &name)?;
tracing::info!("Finding type '{parsed}'");
let index = debug_index(db, binary);
let indexed_debug_files = index.indexed_debug_files(db);
if indexed_debug_files.is_empty() {
tracing::warn!(
"No indexed debug files found for binary: {}",
binary.name(db)
);
return Ok(None);
}
for debug_file in indexed_debug_files {
let Some(type_def) = find_type_by_name(db, debug_file, parsed.clone()) else {
continue;
};
return Ok(Some(resolve_type_offset(db, type_def)?));
}
Ok(None)
}