use std::{cmp::Reverse, collections::BinaryHeap};
use crate::base::{FileInfo, Function, LineInfo};
use symbolic_common::Name;
pub struct FunctionBuilder<'s> {
name: Name<'s>,
compilation_dir: &'s [u8],
address: u64,
size: u64,
inlinees: BinaryHeap<Reverse<FunctionBuilderInlinee<'s>>>,
lines: Vec<LineInfo<'s>>,
}
impl<'s> FunctionBuilder<'s> {
pub fn new(name: Name<'s>, compilation_dir: &'s [u8], address: u64, size: u64) -> Self {
Self {
name,
compilation_dir,
address,
size,
inlinees: BinaryHeap::new(),
lines: Vec::new(),
}
}
pub fn add_inlinee(
&mut self,
depth: u32,
name: Name<'s>,
address: u64,
size: u64,
call_file: FileInfo<'s>,
call_line: u64,
) {
if address < self.address {
return;
}
self.inlinees.push(Reverse(FunctionBuilderInlinee {
depth,
address,
size,
name,
call_file,
call_line,
}));
}
pub fn add_leaf_line(
&mut self,
address: u64,
size: Option<u64>,
file: FileInfo<'s>,
line: u64,
) {
if address < self.address {
return;
}
self.lines.push(LineInfo {
address,
size,
file,
line,
});
}
pub fn finish(self) -> Function<'s> {
let FunctionBuilder {
name,
compilation_dir,
address,
size,
inlinees,
mut lines,
} = self;
let inlinees = ensure_proper_nesting(inlinees);
lines.sort_by_key(|line| line.address);
let outer_function = Function {
address,
size,
name,
compilation_dir,
lines: Vec::new(),
inlinees: Vec::new(),
inline: false,
};
let outer_function_end = address + size;
let mut stack = FunctionBuilderStack::new(outer_function);
let mut inlinee_iter = inlinees.into_iter();
let mut line_iter = lines.into_iter();
let mut next_inlinee = inlinee_iter.next();
let mut next_line = line_iter.next();
loop {
if next_inlinee.is_some()
&& (next_line.is_none()
|| next_inlinee.as_ref().unwrap().address
<= next_line.as_ref().unwrap().address)
{
let inlinee = next_inlinee.take().unwrap();
stack.flush_address(inlinee.address);
stack.flush_depth(inlinee.depth);
if inlinee.address >= outer_function_end {
break;
}
stack.last_mut().lines.push(LineInfo {
address: inlinee.address,
size: Some(inlinee.size),
file: inlinee.call_file,
line: inlinee.call_line,
});
stack.push(Function {
address: inlinee.address,
size: inlinee.size,
name: inlinee.name,
compilation_dir,
lines: Vec::new(),
inlinees: Vec::new(),
inline: true,
});
next_inlinee = inlinee_iter.next();
continue;
}
if let Some(mut line) = next_line.take() {
stack.flush_address(line.address);
if line.address >= outer_function_end {
break;
}
if let Some(size) = line.size {
let line_end = line.address.saturating_add(size);
let current_innermost_fun_end = stack.last_mut().end_address();
let split_address = if let Some(next_inlinee) = next_inlinee.as_ref() {
next_inlinee.address.min(current_innermost_fun_end)
} else {
current_innermost_fun_end
};
if split_address < line_end {
let mut split_line = line.clone();
split_line.address = split_address;
split_line.size = Some(line_end - split_address);
line.size = Some(split_address - line.address);
stack.last_mut().lines.push(line);
next_line = Some(split_line);
continue;
}
}
stack.last_mut().lines.push(line);
next_line = line_iter.next();
continue;
}
break;
}
stack.finish()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
struct FunctionBuilderInlinee<'s> {
pub depth: u32,
pub address: u64,
pub size: u64,
pub name: Name<'s>,
pub call_file: FileInfo<'s>,
pub call_line: u64,
}
impl<'s> PartialOrd for FunctionBuilderInlinee<'s> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
(self.address, self.depth).partial_cmp(&(other.address, other.depth))
}
}
impl<'s> Ord for FunctionBuilderInlinee<'s> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(self.address, self.depth).cmp(&(other.address, other.depth))
}
}
struct FunctionBuilderStack<'s> {
stack: Vec<(u64, Function<'s>)>,
}
impl<'s> FunctionBuilderStack<'s> {
pub fn new(outer_function: Function<'s>) -> Self {
let end_address = outer_function.address.saturating_add(outer_function.size);
let stack = vec![(end_address, outer_function)];
Self { stack }
}
pub fn last_mut(&mut self) -> &mut Function<'s> {
&mut self.stack.last_mut().unwrap().1
}
fn pop(&mut self) {
assert!(self.stack.len() > 1);
let fun = self.stack.pop().unwrap().1;
self.stack.last_mut().unwrap().1.inlinees.push(fun);
}
pub fn flush_address(&mut self, address: u64) {
while self.stack.len() > 1 && self.stack.last().unwrap().0 <= address {
self.pop();
}
}
pub fn flush_depth(&mut self, depth: u32) {
while self.stack.len() > depth as usize + 1 {
self.pop();
}
}
pub fn push(&mut self, inlinee: Function<'s>) {
let end_address = inlinee.address.saturating_add(inlinee.size);
self.stack.push((end_address, inlinee));
}
pub fn finish(mut self) -> Function<'s> {
while self.stack.len() > 1 {
self.pop();
}
self.stack.pop().unwrap().1
}
}
fn ensure_proper_nesting(
mut inlinees: BinaryHeap<Reverse<FunctionBuilderInlinee>>,
) -> Vec<FunctionBuilderInlinee> {
let mut result = Vec::with_capacity(inlinees.len());
let mut end_address_stack = Vec::new();
while let Some(Reverse(mut inlinee)) = inlinees.pop() {
let depth = inlinee.depth as usize;
let start_address = inlinee.address;
let mut end_address = match start_address.checked_add(inlinee.size) {
Some(end_address) => end_address,
None => continue,
};
if end_address_stack.len() < depth {
continue;
}
if let Some(&previous_sibling_end_address) = end_address_stack.get(depth) {
if end_address <= previous_sibling_end_address {
continue;
}
if start_address < previous_sibling_end_address {
let new_start_address = previous_sibling_end_address;
inlinee.address = new_start_address;
inlinee.size = end_address - new_start_address;
inlinees.push(Reverse(inlinee));
continue;
}
}
end_address_stack.truncate(depth);
debug_assert_eq!(end_address_stack.len(), depth, "due to len() check + trunc");
if let Some(&caller_end_address) = end_address_stack.last() {
if start_address >= caller_end_address {
continue;
}
if end_address > caller_end_address {
let split_address = caller_end_address;
let mut split_inlinee = inlinee.clone();
split_inlinee.address = split_address;
split_inlinee.size = end_address - split_address;
inlinees.push(Reverse(split_inlinee));
inlinee.size = split_address - start_address;
end_address = split_address;
}
} else {
}
result.push(inlinee);
end_address_stack.push(end_address); }
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple() {
let mut builder = FunctionBuilder::new(Name::from("foo"), &[], 0x10, 0x30);
builder.add_leaf_line(0x10, Some(0x30), FileInfo::from_filename(b"foo.c"), 1);
let func = builder.finish();
assert_eq!(func.name.as_str(), "foo");
assert_eq!(&func.lines, &[LineInfo::new(0x10, 0x30, b"foo.c", 1)]);
}
#[test]
fn test_inlinee() {
let mut builder = FunctionBuilder::new(Name::from("foo"), &[], 0x10, 0x30);
builder.add_inlinee(
0,
Name::from("bar"),
0x20,
0x20,
FileInfo::from_filename(b"foo.c"),
2,
);
builder.add_leaf_line(0x10, Some(0x10), FileInfo::from_filename(b"foo.c"), 1);
builder.add_leaf_line(0x20, Some(0x20), FileInfo::from_filename(b"bar.c"), 1);
let func = builder.finish();
assert_eq!(func.name.as_str(), "foo");
assert_eq!(
&func.lines,
&[
LineInfo::new(0x10, 0x10, b"foo.c", 1),
LineInfo::new(0x20, 0x20, b"foo.c", 2)
]
);
assert_eq!(func.inlinees.len(), 1);
assert_eq!(func.inlinees[0].name.as_str(), "bar");
assert_eq!(
&func.inlinees[0].lines,
&[LineInfo::new(0x20, 0x20, b"bar.c", 1)]
);
}
#[test]
fn test_longer_line_record() {
let mut builder = FunctionBuilder::new(Name::from("parent"), &[], 0x10, 0x40);
builder.add_inlinee(
0,
Name::from("child1"),
0x20,
0x10,
FileInfo::from_filename(b"parent.c"),
1,
);
builder.add_inlinee(
1,
Name::from("child2"),
0x20,
0x10,
FileInfo::from_filename(b"child1.c"),
1,
);
builder.add_inlinee(
0,
Name::from("child2"),
0x30,
0x10,
FileInfo::from_filename(b"parent.c"),
2,
);
builder.add_leaf_line(0x10, Some(0x10), FileInfo::from_filename(b"parent.c"), 1);
builder.add_leaf_line(0x20, Some(0x20), FileInfo::from_filename(b"child2.c"), 1);
builder.add_leaf_line(0x40, Some(0x10), FileInfo::from_filename(b"parent.c"), 1);
let func = builder.finish();
assert_eq!(func.name.as_str(), "parent");
assert_eq!(
&func.lines,
&[
LineInfo::new(0x10, 0x10, b"parent.c", 1),
LineInfo::new(0x20, 0x10, b"parent.c", 1),
LineInfo::new(0x30, 0x10, b"parent.c", 2),
LineInfo::new(0x40, 0x10, b"parent.c", 1),
]
);
assert_eq!(func.inlinees.len(), 2);
assert_eq!(func.inlinees[0].name.as_str(), "child1");
assert_eq!(
&func.inlinees[0].lines,
&[LineInfo::new(0x20, 0x10, b"child1.c", 1),]
);
assert_eq!(func.inlinees[0].inlinees.len(), 1);
assert_eq!(func.inlinees[0].inlinees[0].name.as_str(), "child2");
assert_eq!(
&func.inlinees[0].inlinees[0].lines,
&[LineInfo::new(0x20, 0x10, b"child2.c", 1),]
);
assert_eq!(func.inlinees[1].name.as_str(), "child2");
assert_eq!(
&func.inlinees[1].lines,
&[LineInfo::new(0x30, 0x10, b"child2.c", 1),]
);
}
}