use anyhow::Result;
use llvm_profparser::{
CoverageMappingInfo,
coverage_mapping::read_object_file,
instrumentation_profile::types::{InstrumentationProfile, NamedInstrProfRecord},
};
use std::{
collections::HashMap,
io::Read,
path::{Path, PathBuf},
};
use crate::errors::RustLlvmError;
pub struct CoverageLibrary {
object_files: HashMap<PathBuf, CoverageMappingInfo>,
lookup_map: HashMap<PathBuf, HashMap<CoverageFunctionLocator, FilenamesRef>>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct CoverageFunctionLocator {
name_hash: u64,
fn_hash: u64,
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct FilenamesRef(u64, usize);
impl Default for CoverageLibrary {
fn default() -> Self {
Self::new()
}
}
impl CoverageLibrary {
pub fn new() -> Self {
CoverageLibrary {
object_files: HashMap::new(),
lookup_map: HashMap::new(),
}
}
pub fn load_binary(&mut self, path: &Path) -> Result<()> {
let object_file = read_object_file(path, 10)?;
let mut object_file_lookup_map = HashMap::new();
for c in &object_file.cov_fun {
let mut file_id: Option<usize> = None;
for region in &c.regions {
match file_id {
Some(existing_file_id) if existing_file_id != region.file_id => {
panic!(
"llvm coverage region had multiple file_ids for a single function -- this isn't currently supported"
);
}
_ => {
file_id = Some(region.file_id);
}
}
}
let Some(file_id) = file_id else {
panic!(
"llvm coverage region file_id could not be identified -- this suggests no regions in this function?"
);
};
let key = CoverageFunctionLocator {
name_hash: c.header.name_hash,
fn_hash: c.header.fn_hash,
};
let previous_value =
object_file_lookup_map.insert(key, FilenamesRef(c.header.filenames_ref, file_id));
assert!(previous_value.is_none()); }
self.lookup_map
.insert(PathBuf::from(path), object_file_lookup_map);
self.object_files.insert(PathBuf::from(path), object_file);
Ok(())
}
pub fn search_metadata(
&self,
point: &InstrumentationPoint,
) -> Result<Option<InstrumentationPointMetadata>> {
let (Some(name_hash), Some(fn_hash)) = (point.rec.name_hash, point.rec.hash) else {
return Ok(None);
};
let object_file_lookup_map = self
.lookup_map
.get(point.binary_path)
.ok_or_else(|| RustLlvmError::LibraryMissingBinary(point.binary_path.clone()))?;
let coverage_locator = CoverageFunctionLocator { name_hash, fn_hash };
let filenames_ref = object_file_lookup_map
.get(&coverage_locator)
.ok_or(RustLlvmError::CoverageMismatch)?;
let object_file = self
.object_files
.get(point.binary_path)
.expect("must be stored in both internal members");
match object_file.cov_map.get(&filenames_ref.0) {
Some(file) => {
Ok(Some(InstrumentationPointMetadata {
file_path: file[filenames_ref.1].clone(),
function_name: point.rec.name.clone().unwrap(),
}))
}
None => {
Ok(None)
}
}
}
}
pub struct ProfilingData {
instrumentation_profile: InstrumentationProfile,
binary_path: PathBuf,
}
impl ProfilingData {
pub fn new_from_profraw_reader<T: Read>(mut reader: T, binary_path: &Path) -> Result<Self> {
let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;
Ok(ProfilingData {
instrumentation_profile: llvm_profparser::parse_bytes(&buf)?,
binary_path: PathBuf::from(binary_path),
})
}
pub fn get_hit_instrumentation_points(&self) -> Vec<InstrumentationPoint<'_>> {
let mut res = Vec::new();
for rec in self.instrumentation_profile.records() {
for c in rec.counts() {
if *c > 0 {
res.push(InstrumentationPoint {
rec: rec.clone(),
binary_path: &self.binary_path,
});
break;
}
}
}
res
}
}
#[derive(Debug)]
pub struct InstrumentationPoint<'a> {
rec: NamedInstrProfRecord,
binary_path: &'a PathBuf,
}
#[derive(Debug)]
pub struct InstrumentationPointMetadata {
pub file_path: PathBuf,
pub function_name: String,
}
#[cfg(test)]
mod tests {
use super::*;
use std::env::current_exe;
#[test]
#[ignore = "needs testtrim to be build with instrument-coverage"]
fn load_binary() {
let mut lib = CoverageLibrary::new();
lib.load_binary(¤t_exe().expect("current_exe()"))
.expect("failed to load binary");
}
#[test]
fn sentinel_local_file() {
let x = 1 + 1;
assert_eq!(x, 2);
}
}