Skip to content

Commit 1d7b248

Browse files
committed
fix coverage in attr macro spans
Such as #[fuchsia_async::run_singlethreaded] Note the spanview output seems to show good coverage spans, but when running coverage reports, the fuchsia_async functions showed `0` for coverage of tests that ran once, and `llvm-cov` reports a Malformed Coverage error.
1 parent f3eabbd commit 1d7b248

File tree

2 files changed

+112
-9
lines changed

2 files changed

+112
-9
lines changed

compiler/rustc_mir/src/transform/coverage/mod.rs

+94-9
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use rustc_middle::mir::{
3232
use rustc_middle::ty::TyCtxt;
3333
use rustc_span::def_id::DefId;
3434
use rustc_span::source_map::SourceMap;
35-
use rustc_span::{CharPos, ExpnKind, Pos, SourceFile, Span, Symbol};
35+
use rustc_span::{CharPos, ExpnData, ExpnKind, Pos, SourceFile, Span, Symbol};
3636

3737
/// A simple error message wrapper for `coverage::Error`s.
3838
#[derive(Debug)]
@@ -552,24 +552,109 @@ fn get_body_span<'tcx>(
552552
let mut body_span = hir_body.value.span;
553553
let def_id = mir_body.source.def_id();
554554

555-
if tcx.is_closure(def_id) {
555+
// If debug is enabled, log expansion span info, if any.
556+
debug!("{}", {
557+
let mut span = body_span;
558+
while span.from_expansion() {
559+
let ExpnData { kind, call_site, .. } = span.ctxt().outer_expn_data();
560+
debug!(
561+
"Body expansion span: {:?}...
562+
expanded from {:?}
563+
at call_site={:?}",
564+
span, kind, call_site
565+
);
566+
span = call_site;
567+
}
568+
"-- end of debug message showing body_span expansion --"
569+
});
570+
571+
if body_span.in_derive_expansion() {
572+
// Derive macro expansions can be expanded, but they expand to structs
573+
// and struct fields, and I think it's less helpful in coverage reports.
574+
return body_span;
575+
}
576+
577+
let mut body_is_from_expansion = false;
578+
if !tcx.is_closure(def_id) {
579+
body_is_from_expansion = body_span.from_expansion();
580+
} else {
556581
// If the MIR function is a closure, and if the closure body span
557582
// starts from a macro, but it's content is not in that macro, try
558583
// to find a non-macro callsite, and instrument the spans there
559584
// instead.
560-
loop {
585+
while body_span.from_expansion() {
586+
body_is_from_expansion = true;
561587
let expn_data = body_span.ctxt().outer_expn_data();
562-
if expn_data.is_root() {
563-
break;
588+
body_span = expn_data.call_site;
589+
match expn_data.kind {
590+
ExpnKind::Macro { .. } => {}
591+
_ => break,
592+
}
593+
}
594+
};
595+
596+
if !body_is_from_expansion {
597+
// If the body_span (or recomputed body_span, for closures with macros)
598+
// is not from an expansion, then there is no need to try to compute
599+
// a span from the MIR statements and terminators.
600+
return body_span;
601+
}
602+
603+
// If the body is from an expansion, then there's no certainty that the
604+
// internal statements were also expanded into the same ctxt or elsewhere.
605+
// So, search the MIR `Statement` and `Terminator` spans, and if their
606+
// spans exist ouside the `body_span` compute a different `body_span`
607+
// from the spans of the MIR's `Statement`s and `Terminator`s, so
608+
// coverage can be applied to those spans.
609+
610+
if let Ok(Some(max_span)) = spans::filtered_from_mir(mir_body).try_fold(
611+
None,
612+
|some_max_span: Option<Span>, mut span| {
613+
if span.is_empty() || span == body_span {
614+
// A span that matches the body_span doesn't add anything, and both spans that match
615+
// the body_span, and empty spans can exist in the given body_span, even if the
616+
// statements we want to cover are from a separate and distinct span, so we need
617+
// to ignore them. If no other statements exist outside the body_span, then we will
618+
// keep the given body_span.
619+
return Ok(some_max_span); // continue iterating
620+
}
621+
622+
while span.ctxt() != body_span.ctxt() {
623+
if !span.from_expansion() {
624+
return Ok(some_max_span); // continue iterating
625+
}
626+
span = span.ctxt().outer_expn_data().call_site;
627+
}
628+
629+
if body_span.contains(span) {
630+
debug!(
631+
"using body_span={:?}; it contains a statement or terminator with span={:?}",
632+
body_span, span
633+
);
634+
return Err(()); // break from iterating
564635
}
565-
if let ExpnKind::Macro { .. } = expn_data.kind {
566-
body_span = expn_data.call_site;
636+
637+
if let Some(max_span) = some_max_span {
638+
Ok(Some(max_span.to(span))) // extend max_span
567639
} else {
568-
break;
640+
Ok(Some(span)) // initialize max_span to first useful span
569641
}
642+
},
643+
) {
644+
debug!(
645+
"max_span = {:?}; overlaps body_span? {}, body_span hi() only touches? {}",
646+
max_span,
647+
body_span.overlaps(max_span),
648+
body_span.hi() == max_span.lo()
649+
);
650+
if body_span.overlaps(max_span) {
651+
// extend the body_span to include the computed max_span
652+
body_span = body_span.to(max_span);
653+
} else {
654+
// use the computed max_span instead of the original body_span
655+
body_span = max_span;
570656
}
571657
}
572-
573658
body_span
574659
}
575660

compiler/rustc_mir/src/transform/coverage/spans.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::debug::term_type;
22
use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph, START_BCB};
3+
use super::spans;
34

45
use crate::util::spanview::source_range_no_file;
56

@@ -889,6 +890,23 @@ pub(super) fn filtered_terminator_span(terminator: &'a Terminator<'tcx>) -> Opti
889890
}
890891
}
891892

893+
/// Returns an iterator over all filtered statement and terminator spans.
894+
pub(super) fn filtered_from_mir<'a>(
895+
mir_body: &'a mir::Body<'a>,
896+
) -> impl Iterator<Item = Span> + 'a {
897+
mir_body.basic_blocks().iter().flat_map(|data| {
898+
data.statements
899+
.iter()
900+
.enumerate()
901+
.filter_map(move |(_, statement)| spans::filtered_statement_span(statement))
902+
.chain(
903+
data.terminator
904+
.iter()
905+
.filter_map(move |term| spans::filtered_terminator_span(term)),
906+
)
907+
})
908+
}
909+
892910
/// Returns an extrapolated span (pre-expansion[^1]) corresponding to a range
893911
/// within the function's body source. This span is guaranteed to be contained
894912
/// within, or equal to, the `body_span`. If the extrapolated span is not

0 commit comments

Comments
 (0)