#![allow(unused_variables)]
pub use crate::project_context::ProjectContext;
use crate::sym::resolve_line_file_path;
use gimli::{ColumnType, Dwarf, Reader};
#[derive(Debug, Clone)]
pub struct CrateLineEntry {
pub address: u64,
pub line: u32,
pub column: Option<u32>,
}
#[derive(Debug)]
pub struct CrateLineTable {
entries: Vec<CrateLineEntry>,
}
impl CrateLineTable {
pub fn build<R: Reader>(
dwarf: &Dwarf<R>,
project_context: &ProjectContext,
) -> Result<Self, gimli::Error> {
let mut entries = Vec::new();
let mut units = dwarf.units();
while let Some(header) = units.next()? {
let unit = dwarf.unit(header)?;
if let Some(program) = &unit.line_program {
let mut rows = program.clone().rows();
while let Some((header, row)) = rows.next_row()? {
if let Some(file_entry) = row.file(header) {
let full_path = resolve_line_file_path(
dwarf,
&unit,
file_entry,
header,
project_context.project_root(),
)?;
if project_context.is_crate_source(&full_path)
&& let Some(line) = row.line()
{
let column = match row.column() {
ColumnType::LeftEdge => None,
ColumnType::Column(c) => Some(c.get() as u32),
};
entries.push(CrateLineEntry {
address: row.address(),
line: line.get() as u32,
column,
});
}
}
}
}
}
entries.sort_by_key(|e| e.address);
Ok(Self { entries })
}
pub fn get_line(&self, func_start: u64, call_site_addr: u64) -> (Option<u32>, Option<u32>) {
let start_idx = self.entries.partition_point(|e| e.address < func_start);
let end_idx = self
.entries
.partition_point(|e| e.address <= call_site_addr);
if end_idx > start_idx {
let entry = &self.entries[end_idx - 1];
(Some(entry.line), entry.column)
} else {
(None, None)
}
}
pub fn get_nearest_line(
&self,
func_start: u64,
call_site_addr: u64,
func_end: u64,
) -> (Option<u32>, Option<u32>) {
let start_idx = self.entries.partition_point(|e| e.address < func_start);
let end_idx = self
.entries
.partition_point(|e| e.address <= call_site_addr);
let backward = if end_idx > start_idx {
Some(&self.entries[end_idx - 1])
} else {
None
};
let forward = if end_idx < self.entries.len() {
let entry = &self.entries[end_idx];
if entry.address < func_end {
Some(entry)
} else {
None
}
} else {
None
};
match (backward, forward) {
(Some(bw), Some(fw)) => {
let bw_dist = call_site_addr - bw.address;
let fw_dist = fw.address - call_site_addr;
if fw_dist < bw_dist {
(Some(fw.line), fw.column)
} else {
(Some(bw.line), bw.column)
}
}
(Some(bw), None) => (Some(bw.line), bw.column),
(None, _) => (None, None),
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub(crate) fn from_entries(entries: Vec<CrateLineEntry>) -> Self {
Self { entries }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_table(entries: &[(u64, u32, Option<u32>)]) -> CrateLineTable {
CrateLineTable::from_entries(
entries
.iter()
.map(|&(address, line, column)| CrateLineEntry {
address,
line,
column,
})
.collect(),
)
}
#[test]
fn test_empty_table() {
let table = make_table(&[]);
assert!(table.is_empty());
assert_eq!(table.len(), 0);
assert_eq!(table.get_line(0, 100), (None, None));
}
#[test]
fn test_single_entry() {
let table = make_table(&[(100, 10, Some(5))]);
assert!(!table.is_empty());
assert_eq!(table.len(), 1);
assert_eq!(table.get_line(100, 100), (Some(10), Some(5)));
}
#[test]
fn test_get_line_exact_match() {
let table = make_table(&[(100, 10, None), (200, 20, Some(3)), (300, 30, Some(7))]);
assert_eq!(table.get_line(200, 200), (Some(20), Some(3)));
}
#[test]
fn test_get_line_returns_last_in_range() {
let table = make_table(&[(100, 10, None), (150, 15, Some(2)), (200, 20, None)]);
assert_eq!(table.get_line(100, 200), (Some(20), None));
assert_eq!(table.get_line(100, 160), (Some(15), Some(2)));
}
#[test]
fn test_get_line_no_entries_in_range() {
let table = make_table(&[(100, 10, None), (200, 20, None)]);
assert_eq!(table.get_line(0, 50), (None, None));
assert_eq!(table.get_line(110, 190), (None, None));
}
#[test]
fn test_get_line_func_start_equals_call_site() {
let table = make_table(&[(100, 10, Some(1))]);
assert_eq!(table.get_line(100, 100), (Some(10), Some(1)));
}
#[test]
fn test_get_line_column_none() {
let table = make_table(&[(100, 10, None)]);
assert_eq!(table.get_line(100, 100), (Some(10), None));
}
#[test]
fn test_from_entries_preserves_order() {
let table = make_table(&[(300, 30, None), (100, 10, None), (200, 20, None)]);
assert_eq!(table.len(), 3);
}
#[test]
fn test_nearest_line_prefers_forward_when_closer() {
let table = make_table(&[(100, 26, None), (200, 28, Some(29))]);
assert_eq!(table.get_nearest_line(100, 180, 300), (Some(28), Some(29)));
}
#[test]
fn test_nearest_line_prefers_backward_when_closer() {
let table = make_table(&[(100, 10, Some(5)), (200, 11, None)]);
assert_eq!(table.get_nearest_line(100, 104, 300), (Some(10), Some(5)));
}
#[test]
fn test_nearest_line_exact_match() {
let table = make_table(&[(100, 10, Some(5)), (200, 20, None)]);
assert_eq!(table.get_nearest_line(100, 100, 300), (Some(10), Some(5)));
}
#[test]
fn test_nearest_line_no_entries() {
let table = make_table(&[]);
assert_eq!(table.get_nearest_line(100, 150, 300), (None, None));
}
#[test]
fn test_nearest_line_forward_beyond_func_end() {
let table = make_table(&[(100, 10, None), (400, 40, None)]);
assert_eq!(table.get_nearest_line(100, 150, 300), (Some(10), None));
}
#[test]
fn test_nearest_line_equal_distance_prefers_backward() {
let table = make_table(&[(100, 10, None), (200, 20, None)]);
assert_eq!(table.get_nearest_line(100, 150, 300), (Some(10), None));
}
}