1
Fork 0

coverage: Separate span-extraction from unexpansion

This commit is contained in:
Zalathar 2025-03-04 14:17:16 +11:00
parent 5d85a714b1
commit 83b56eb059
2 changed files with 66 additions and 75 deletions

View file

@ -6,10 +6,8 @@ use rustc_span::{DesugaringKind, ExpnKind, MacroKind, Span};
use tracing::{debug, debug_span, instrument};
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
use crate::coverage::spans::from_mir::{
ExtractedCovspans, Hole, SpanFromMir, extract_covspans_from_mir,
};
use crate::coverage::{ExtractedHirInfo, mappings};
use crate::coverage::spans::from_mir::{Hole, RawSpanFromMir, SpanFromMir};
use crate::coverage::{ExtractedHirInfo, mappings, unexpand};
mod from_mir;
@ -19,7 +17,35 @@ pub(super) fn extract_refined_covspans(
graph: &CoverageGraph,
code_mappings: &mut impl Extend<mappings::CodeMapping>,
) {
let ExtractedCovspans { mut covspans } = extract_covspans_from_mir(mir_body, hir_info, graph);
let &ExtractedHirInfo { body_span, .. } = hir_info;
let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph);
let mut covspans = raw_spans
.into_iter()
.filter_map(|RawSpanFromMir { raw_span, bcb }| try {
let (span, expn_kind) =
unexpand::unexpand_into_body_span_with_expn_kind(raw_span, body_span)?;
// Discard any spans that fill the entire body, because they tend
// to represent compiler-inserted code, e.g. implicitly returning `()`.
if span.source_equal(body_span) {
return None;
};
SpanFromMir { span, expn_kind, bcb }
})
.collect::<Vec<_>>();
// Only proceed if we found at least one usable span.
if covspans.is_empty() {
return;
}
// Also add the adjusted function signature span, if available.
// Otherwise, add a fake span at the start of the body, to avoid an ugly
// gap between the start of the body and the first real span.
// FIXME: Find a more principled way to solve this problem.
covspans.push(SpanFromMir::for_fn_sig(
hir_info.fn_sig_span_extended.unwrap_or_else(|| body_span.shrink_to_lo()),
));
// First, perform the passes that need macro information.
covspans.sort_by(|a, b| graph.cmp_in_dominator_order(a.bcb, b.bcb));

View file

@ -1,3 +1,5 @@
use std::iter;
use rustc_middle::bug;
use rustc_middle::mir::coverage::CoverageKind;
use rustc_middle::mir::{
@ -5,87 +7,50 @@ use rustc_middle::mir::{
};
use rustc_span::{ExpnKind, Span};
use crate::coverage::ExtractedHirInfo;
use crate::coverage::graph::{
BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph, START_BCB,
};
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph, START_BCB};
use crate::coverage::spans::Covspan;
use crate::coverage::unexpand::unexpand_into_body_span_with_expn_kind;
pub(crate) struct ExtractedCovspans {
pub(crate) covspans: Vec<SpanFromMir>,
#[derive(Debug)]
pub(crate) struct RawSpanFromMir {
/// A span that has been extracted from a MIR statement/terminator, but
/// hasn't been "unexpanded", so it might not lie within the function body
/// span and might be part of an expansion with a different context.
pub(crate) raw_span: Span,
pub(crate) bcb: BasicCoverageBlock,
}
/// Traverses the MIR body to produce an initial collection of coverage-relevant
/// spans, each associated with a node in the coverage graph (BCB) and possibly
/// other metadata.
pub(crate) fn extract_covspans_from_mir(
mir_body: &mir::Body<'_>,
hir_info: &ExtractedHirInfo,
/// Generates an initial set of coverage spans from the statements and
/// terminators in the function's MIR body, each associated with its
/// corresponding node in the coverage graph.
///
/// This is necessarily an inexact process, because MIR isn't designed to
/// capture source spans at the level of detail we would want for coverage,
/// but it's good enough to be better than nothing.
pub(crate) fn extract_raw_spans_from_mir<'tcx>(
mir_body: &mir::Body<'tcx>,
graph: &CoverageGraph,
) -> ExtractedCovspans {
let &ExtractedHirInfo { body_span, .. } = hir_info;
let mut covspans = vec![];
) -> Vec<RawSpanFromMir> {
let mut raw_spans = vec![];
// We only care about blocks that are part of the coverage graph.
for (bcb, bcb_data) in graph.iter_enumerated() {
bcb_to_initial_coverage_spans(mir_body, body_span, bcb, bcb_data, &mut covspans);
}
let make_raw_span = |raw_span: Span| RawSpanFromMir { raw_span, bcb };
// Only add the signature span if we found at least one span in the body.
if !covspans.is_empty() {
// If there is no usable signature span, add a fake one (before refinement)
// to avoid an ugly gap between the body start and the first real span.
// FIXME: Find a more principled way to solve this problem.
let fn_sig_span = hir_info.fn_sig_span_extended.unwrap_or_else(|| body_span.shrink_to_lo());
covspans.push(SpanFromMir::for_fn_sig(fn_sig_span));
}
// A coverage graph node can consist of multiple basic blocks.
for &bb in &bcb_data.basic_blocks {
let bb_data = &mir_body[bb];
ExtractedCovspans { covspans }
}
let statements = bb_data.statements.iter();
raw_spans.extend(statements.filter_map(filtered_statement_span).map(make_raw_span));
// Generate a set of coverage spans from the filtered set of `Statement`s and `Terminator`s of
// the `BasicBlock`(s) in the given `BasicCoverageBlockData`. One coverage span is generated
// for each `Statement` and `Terminator`. (Note that subsequent stages of coverage analysis will
// merge some coverage spans, at which point a coverage span may represent multiple
// `Statement`s and/or `Terminator`s.)
fn bcb_to_initial_coverage_spans<'a, 'tcx>(
mir_body: &'a mir::Body<'tcx>,
body_span: Span,
bcb: BasicCoverageBlock,
bcb_data: &'a BasicCoverageBlockData,
initial_covspans: &mut Vec<SpanFromMir>,
) {
for &bb in &bcb_data.basic_blocks {
let data = &mir_body[bb];
let unexpand = move |expn_span| {
unexpand_into_body_span_with_expn_kind(expn_span, body_span)
// Discard any spans that fill the entire body, because they tend
// to represent compiler-inserted code, e.g. implicitly returning `()`.
.filter(|(span, _)| !span.source_equal(body_span))
};
let mut extract_statement_span = |statement| {
let expn_span = filtered_statement_span(statement)?;
let (span, expn_kind) = unexpand(expn_span)?;
initial_covspans.push(SpanFromMir::new(span, expn_kind, bcb));
Some(())
};
for statement in data.statements.iter() {
extract_statement_span(statement);
// There's only one terminator, but wrap it in an iterator to
// mirror the handling of statements.
let terminator = iter::once(bb_data.terminator());
raw_spans.extend(terminator.filter_map(filtered_terminator_span).map(make_raw_span));
}
let mut extract_terminator_span = |terminator| {
let expn_span = filtered_terminator_span(terminator)?;
let (span, expn_kind) = unexpand(expn_span)?;
initial_covspans.push(SpanFromMir::new(span, expn_kind, bcb));
Some(())
};
extract_terminator_span(data.terminator());
}
raw_spans
}
/// If the MIR `Statement` has a span contributive to computing coverage spans,
@ -219,7 +184,7 @@ pub(crate) struct SpanFromMir {
}
impl SpanFromMir {
fn for_fn_sig(fn_sig_span: Span) -> Self {
pub(crate) fn for_fn_sig(fn_sig_span: Span) -> Self {
Self::new(fn_sig_span, None, START_BCB)
}