in src/collector.rs [165:340]
fn collect_function_with_inlines_recursive<'a>(
fun: &Function<'a>,
lines: &mut Lines,
source: &mut SourceFiles,
inline_origins: &mut InlineOrigins<'a>,
call_depth: u32,
) {
// This function converts between two representations of line information:
// "Lines for both self-lines and for inlined calls" -> "Only self-lines"
//
// `fun` contains the debug info for our function, with some data for each instruction
// that our function is made of, associated via the instruction's code address.
// `fun.lines` contains line records, each of which covers a range of code addresses.
// `fun.inlinees` contains inlinee records, each of which has its own set of line
// records (at `inlinee.lines`) covering code addresses.
//
// We can divide the instructions in a function into two buckets:
// (1) Instructions which are part of an inlined function call, and
// (2) instructions which are *not* part of an inlined function call.
//
// Our incoming line records cover both (1) and (2) types of instructions.
// We want to call `lines.add_line` *only for type (2)*.
//
// So we need to know which address ranges are covered by inline calls, so that we
// can filter out those address ranges and skip calling `lines.add_line` for them.
// First we gather the address ranges covered by inlined calls.
// We also recurse into the inlinees, while we're at it.
// The order of calls to `add_line` and `add_inline` is irrelevant; `Lines` will sort
// everything by address once the entire outer function has been processed.
let mut inline_ranges = Vec::new();
for inlinee in &fun.inlinees {
if inlinee.lines.is_empty() {
continue;
}
let inline_origin_id = inline_origins.get_id(&inlinee.name);
for line in &inlinee.lines {
let start = line.address;
let end = line.address + line.size.unwrap_or(1);
inline_ranges.push((start..end, inline_origin_id));
}
// Recurse.
Self::collect_function_with_inlines_recursive(
inlinee,
lines,
source,
inline_origins,
call_depth + 1,
);
}
// Sort the inline ranges.
inline_ranges.sort_unstable_by_key(|(range, _origin)| range.start);
// Walk two iterators. We assume that fun.lines is already sorted by address.
let mut line_iter = fun.lines.iter();
let mut inline_iter = inline_ranges.into_iter();
let mut next_line = line_iter.next();
let mut next_inline = inline_iter.next();
let mut prev_line_info = None;
// Iterate over the line records.
while let Some(line) = next_line.take() {
let line_range_start = line.address;
let line_range_end = line.address + line.size.unwrap_or(1);
let file_id = source.get_id(fun.compilation_dir, &line.file);
let file_id = source.get_true_id(file_id);
let line_no = line.line as u32;
// The incoming line record can be a "self line", or a "call line", or even a mixture.
//
// Examples:
//
// a) Just self line:
// Line: |==============|
// Inlines: (none)
//
// Effect: add_line()
//
// b) Just call line:
// Line: |==============|
// Inlines: |--------------|
//
// Effect: add_inline()
//
// c) Just call line, for multiple inlined calls:
// Line: |==========================|
// Inlines: |----------||--------------|
//
// Effect: add_inline(), add_inline()
//
// d) Call line and trailing self line:
// Line: |==================|
// Inlines: |-----------|
//
// Effect: add_inline(), add_line()
//
// e) Leading self line and also call line:
// Line: |==================|
// Inlines: |-----------|
//
// Effect: add_line(), add_inline()
//
// f) Interleaving
// Line: |======================================|
// Inlines: |-----------| |-------|
//
// Effect: add_line(), add_inline(), add_line(), add_inline(), add_line()
//
// g) Bad debug info
// Line: |=======|
// Inlines: |-------------|
//
// Effect: add_inline()
let mut current_address = line_range_start;
while current_address < line_range_end {
// Emit a line at current_address if current_address is not covered by an inlined call.
if next_inline.is_none() || next_inline.as_ref().unwrap().0.start > current_address
{
let line_info = (line_no, file_id);
if prev_line_info.as_ref() != Some(&line_info) {
lines.add_line(current_address as u32, line_no, file_id);
prev_line_info = Some(line_info);
}
}
// If there is an inlined call covered by this line record, turn this line into that
// call's "call line" and emit an inline record.
if next_inline.is_some() && next_inline.as_ref().unwrap().0.start < line_range_end {
let (inline_range, inline_origin_id) = next_inline.take().unwrap();
let call_line_number = line_no;
let call_file_id = file_id;
lines.add_inline(
InlineSite {
inline_origin_id,
call_depth,
call_line_number,
call_file_id,
},
InlineAddressRange {
rva: inline_range.start as u32,
len: (inline_range.end - inline_range.start) as u32,
},
);
// Advance current_address to the end of this inline range.
current_address = inline_range.end;
prev_line_info = None;
next_inline = inline_iter.next();
} else {
// No further inline ranges are overlapping with this line record. Advance to the
// end of the line record.
current_address = line_range_end;
}
}
// Advance the line iterator.
next_line = line_iter.next();
// Skip any lines that start before current_address.
// Such lines can exist if the debug information is faulty, or if the compiler created
// multiple identical small "call line" records instead of one combined record
// covering the entire inline range. We can't have different "call lines" for a single
// inline range anyway, so it's fine to skip these.
while next_line.is_some() && next_line.as_ref().unwrap().address < current_address {
next_line = line_iter.next();
}
}
}