use std::collections::VecDeque; use std::iter; use rustc_data_structures::fx::FxHashSet; use rustc_middle::mir; use rustc_middle::ty::TyCtxt; 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::{Hole, RawSpanFromMir, SpanFromMir}; use crate::coverage::{ExtractedHirInfo, mappings, unexpand}; mod from_mir; pub(super) fn extract_refined_covspans<'tcx>( tcx: TyCtxt<'tcx>, mir_body: &mir::Body<'tcx>, hir_info: &ExtractedHirInfo, graph: &CoverageGraph, code_mappings: &mut impl Extend, ) { 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::>(); // 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)); remove_unwanted_expansion_spans(&mut covspans); shrink_visible_macro_spans(tcx, &mut covspans); // We no longer need the extra information in `SpanFromMir`, so convert to `Covspan`. let mut covspans = covspans.into_iter().map(SpanFromMir::into_covspan).collect::>(); let compare_covspans = |a: &Covspan, b: &Covspan| { compare_spans(a.span, b.span) // After deduplication, we want to keep only the most-dominated BCB. .then_with(|| graph.cmp_in_dominator_order(a.bcb, b.bcb).reverse()) }; covspans.sort_by(compare_covspans); // Among covspans with the same span, keep only one, // preferring the one with the most-dominated BCB. // (Ideally we should try to preserve _all_ non-dominating BCBs, but that // requires a lot more complexity in the span refiner, for little benefit.) covspans.dedup_by(|b, a| a.span.source_equal(b.span)); // Sort the holes, and merge overlapping/adjacent holes. let mut holes = hir_info .hole_spans .iter() .copied() // Discard any holes that aren't directly visible within the body span. .filter(|&hole_span| body_span.contains(hole_span) && body_span.eq_ctxt(hole_span)) .map(|span| Hole { span }) .collect::>(); holes.sort_by(|a, b| compare_spans(a.span, b.span)); holes.dedup_by(|b, a| a.merge_if_overlapping_or_adjacent(b)); // Split the covspans into separate buckets that don't overlap any holes. let buckets = divide_spans_into_buckets(covspans, &holes); for covspans in buckets { let _span = debug_span!("processing bucket", ?covspans).entered(); let mut covspans = remove_unwanted_overlapping_spans(covspans); debug!(?covspans, "after removing overlaps"); // Do one last merge pass, to simplify the output. covspans.dedup_by(|b, a| a.merge_if_eligible(b)); debug!(?covspans, "after merge"); code_mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| { // Each span produced by the refiner represents an ordinary code region. mappings::CodeMapping { span, bcb } })); } } /// Macros that expand into branches (e.g. `assert!`, `trace!`) tend to generate /// multiple condition/consequent blocks that have the span of the whole macro /// invocation, which is unhelpful. Keeping only the first such span seems to /// give better mappings, so remove the others. /// /// Similarly, `await` expands to a branch on the discriminant of `Poll`, which /// leads to incorrect coverage if the `Future` is immediately ready (#98712). /// /// (The input spans should be sorted in BCB dominator order, so that the /// retained "first" span is likely to dominate the others.) fn remove_unwanted_expansion_spans(covspans: &mut Vec) { let mut deduplicated_spans = FxHashSet::default(); covspans.retain(|covspan| { match covspan.expn_kind { // Retain only the first await-related or macro-expanded covspan with this span. Some(ExpnKind::Desugaring(DesugaringKind::Await)) => { deduplicated_spans.insert(covspan.span) } Some(ExpnKind::Macro(MacroKind::Bang, _)) => deduplicated_spans.insert(covspan.span), // Ignore (retain) other spans. _ => true, } }); } /// When a span corresponds to a macro invocation that is visible from the /// function body, truncate it to just the macro name plus `!`. /// This seems to give better results for code that uses macros. fn shrink_visible_macro_spans(tcx: TyCtxt<'_>, covspans: &mut Vec) { let source_map = tcx.sess.source_map(); for covspan in covspans { if matches!(covspan.expn_kind, Some(ExpnKind::Macro(MacroKind::Bang, _))) { covspan.span = source_map.span_through_char(covspan.span, '!'); } } } /// Uses the holes to divide the given covspans into buckets, such that: /// - No span in any hole overlaps a bucket (discarding spans if necessary). /// - The spans in each bucket are strictly after all spans in previous buckets, /// and strictly before all spans in subsequent buckets. /// /// The lists of covspans and holes must be sorted. /// The resulting buckets are sorted relative to each other, and each bucket's /// contents are sorted. #[instrument(level = "debug")] fn divide_spans_into_buckets(input_covspans: Vec, holes: &[Hole]) -> Vec> { debug_assert!(input_covspans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le())); debug_assert!(holes.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le())); // Now we're ready to start grouping spans into buckets separated by holes. let mut input_covspans = VecDeque::from(input_covspans); // For each hole: // - Identify the spans that are entirely or partly before the hole. // - Discard any that overlap with the hole. // - Add the remaining identified spans to the corresponding bucket. let mut buckets = (0..holes.len()).map(|_| vec![]).collect::>(); for (hole, bucket) in holes.iter().zip(&mut buckets) { bucket.extend( drain_front_while(&mut input_covspans, |c| c.span.lo() < hole.span.hi()) .filter(|c| !c.span.overlaps(hole.span)), ); } // Any remaining spans form their own final bucket, after the final hole. // (If there were no holes, this will just be all of the initial spans.) buckets.push(Vec::from(input_covspans)); buckets } /// Similar to `.drain(..)`, but stops just before it would remove an item not /// satisfying the predicate. fn drain_front_while<'a, T>( queue: &'a mut VecDeque, mut pred_fn: impl FnMut(&T) -> bool, ) -> impl Iterator { iter::from_fn(move || queue.pop_front_if(|x| pred_fn(x))) } /// Takes one of the buckets of (sorted) spans extracted from MIR, and "refines" /// those spans by removing spans that overlap in unwanted ways. #[instrument(level = "debug")] fn remove_unwanted_overlapping_spans(sorted_spans: Vec) -> Vec { debug_assert!(sorted_spans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le())); // Holds spans that have been read from the input vector, but haven't yet // been committed to the output vector. let mut pending = vec![]; let mut refined = vec![]; for curr in sorted_spans { pending.retain(|prev: &Covspan| { if prev.span.hi() <= curr.span.lo() { // There's no overlap between the previous/current covspans, // so move the previous one into the refined list. refined.push(prev.clone()); false } else { // Otherwise, retain the previous covspan only if it has the // same BCB. This tends to discard long outer spans that enclose // smaller inner spans with different control flow. prev.bcb == curr.bcb } }); pending.push(curr); } // Drain the rest of the pending list into the refined list. refined.extend(pending); refined } #[derive(Clone, Debug)] struct Covspan { span: Span, bcb: BasicCoverageBlock, } impl Covspan { /// If `self` and `other` can be merged (i.e. they have the same BCB), /// mutates `self.span` to also include `other.span` and returns true. /// /// Note that compatible covspans can be merged even if their underlying /// spans are not overlapping/adjacent; any space between them will also be /// part of the merged covspan. fn merge_if_eligible(&mut self, other: &Self) -> bool { if self.bcb != other.bcb { return false; } self.span = self.span.to(other.span); true } } /// Compares two spans in (lo ascending, hi descending) order. fn compare_spans(a: Span, b: Span) -> std::cmp::Ordering { // First sort by span start. Ord::cmp(&a.lo(), &b.lo()) // If span starts are the same, sort by span end in reverse order. // This ensures that if spans A and B are adjacent in the list, // and they overlap but are not equal, then either: // - Span A extends further left, or // - Both have the same start and span A extends further right .then_with(|| Ord::cmp(&a.hi(), &b.hi()).reverse()) }