fn collect_function_with_inlines_recursive()

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();
            }
        }
    }