Skip to content

coverage: Get hole spans from nested items without fully visiting them #137251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 26 additions & 25 deletions compiler/rustc_mir_transform/src/coverage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,35 +346,37 @@ fn extract_hole_spans_from_hir<'tcx>(
body_span: Span, // Usually `hir_body.value.span`, but not always
hir_body: &hir::Body<'tcx>,
) -> Vec<Span> {
struct HolesVisitor<'hir, F> {
tcx: TyCtxt<'hir>,
visit_hole_span: F,
struct HolesVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
body_span: Span,
hole_spans: Vec<Span>,
}

impl<'hir, F: FnMut(Span)> Visitor<'hir> for HolesVisitor<'hir, F> {
/// - We need `NestedFilter::INTRA = true` so that `visit_item` will be called.
/// - Bodies of nested items don't actually get visited, because of the
/// `visit_item` override.
/// - For nested bodies that are not part of an item, we do want to visit any
/// items contained within them.
type NestedFilter = nested_filter::All;
impl<'tcx> Visitor<'tcx> for HolesVisitor<'tcx> {
/// We have special handling for nested items, but we still want to
/// traverse into nested bodies of things that are not considered items,
/// such as "anon consts" (e.g. array lengths).
type NestedFilter = nested_filter::OnlyBodies;

fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
fn maybe_tcx(&mut self) -> TyCtxt<'tcx> {
self.tcx
}

fn visit_item(&mut self, item: &'hir hir::Item<'hir>) {
(self.visit_hole_span)(item.span);
/// We override `visit_nested_item` instead of `visit_item` because we
/// only need the item's span, not the item itself.
fn visit_nested_item(&mut self, id: hir::ItemId) -> Self::Result {
let span = self.tcx.def_span(id.owner_id.def_id);
self.visit_hole_span(span);
// Having visited this item, we don't care about its children,
// so don't call `walk_item`.
}

// We override `visit_expr` instead of the more specific expression
// visitors, so that we have direct access to the expression span.
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
match expr.kind {
hir::ExprKind::Closure(_) | hir::ExprKind::ConstBlock(_) => {
(self.visit_hole_span)(expr.span);
self.visit_hole_span(expr.span);
// Having visited this expression, we don't care about its
// children, so don't call `walk_expr`.
}
Expand All @@ -384,18 +386,17 @@ fn extract_hole_spans_from_hir<'tcx>(
}
}
}

let mut hole_spans = vec![];
let mut visitor = HolesVisitor {
tcx,
visit_hole_span: |hole_span| {
impl HolesVisitor<'_> {
fn visit_hole_span(&mut self, hole_span: Span) {
// Discard any holes that aren't directly visible within the body span.
if body_span.contains(hole_span) && body_span.eq_ctxt(hole_span) {
hole_spans.push(hole_span);
if self.body_span.contains(hole_span) && self.body_span.eq_ctxt(hole_span) {
self.hole_spans.push(hole_span);
}
},
};
}
}

let mut visitor = HolesVisitor { tcx, body_span, hole_spans: vec![] };

visitor.visit_body(hir_body);
hole_spans
visitor.hole_spans
}
31 changes: 18 additions & 13 deletions tests/coverage/holes.cov-map
Original file line number Diff line number Diff line change
@@ -1,52 +1,57 @@
Function name: <holes::main::MyStruct>::_method (unused)
Raw bytes (9): 0x[01, 01, 00, 01, 00, 25, 09, 00, 1d]
Raw bytes (9): 0x[01, 01, 00, 01, 00, 2b, 09, 00, 1d]
Number of files: 1
- file 0 => global file 1
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Zero) at (prev + 37, 9) to (start + 0, 29)
- Code(Zero) at (prev + 43, 9) to (start + 0, 29)
Highest counter ID seen: (none)

Function name: holes::main
Raw bytes (44): 0x[01, 01, 00, 08, 01, 08, 01, 06, 11, 01, 0f, 05, 00, 12, 01, 04, 05, 00, 12, 01, 07, 05, 00, 12, 01, 06, 05, 00, 12, 01, 06, 05, 03, 0f, 01, 0a, 05, 03, 0f, 01, 0a, 05, 01, 02]
Raw bytes (69): 0x[01, 01, 00, 0d, 01, 08, 01, 01, 12, 01, 05, 05, 00, 12, 01, 07, 09, 00, 11, 01, 09, 05, 00, 12, 01, 04, 05, 00, 12, 01, 07, 05, 00, 12, 01, 06, 05, 00, 12, 01, 04, 05, 00, 12, 01, 04, 05, 00, 12, 01, 06, 05, 03, 0f, 01, 0a, 05, 03, 0f, 01, 0a, 05, 0c, 0d, 01, 0f, 0e, 05, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 0
Number of file 0 mappings: 8
- Code(Counter(0)) at (prev + 8, 1) to (start + 6, 17)
- Code(Counter(0)) at (prev + 15, 5) to (start + 0, 18)
Number of file 0 mappings: 13
- Code(Counter(0)) at (prev + 8, 1) to (start + 1, 18)
- Code(Counter(0)) at (prev + 5, 5) to (start + 0, 18)
- Code(Counter(0)) at (prev + 7, 9) to (start + 0, 17)
- Code(Counter(0)) at (prev + 9, 5) to (start + 0, 18)
- Code(Counter(0)) at (prev + 4, 5) to (start + 0, 18)
- Code(Counter(0)) at (prev + 7, 5) to (start + 0, 18)
- Code(Counter(0)) at (prev + 6, 5) to (start + 0, 18)
- Code(Counter(0)) at (prev + 4, 5) to (start + 0, 18)
- Code(Counter(0)) at (prev + 4, 5) to (start + 0, 18)
- Code(Counter(0)) at (prev + 6, 5) to (start + 3, 15)
- Code(Counter(0)) at (prev + 10, 5) to (start + 3, 15)
- Code(Counter(0)) at (prev + 10, 5) to (start + 1, 2)
- Code(Counter(0)) at (prev + 10, 5) to (start + 12, 13)
- Code(Counter(0)) at (prev + 15, 14) to (start + 5, 2)
Highest counter ID seen: c0

Function name: holes::main::_unused_fn (unused)
Raw bytes (9): 0x[01, 01, 00, 01, 00, 19, 05, 00, 17]
Raw bytes (9): 0x[01, 01, 00, 01, 00, 1f, 05, 00, 17]
Number of files: 1
- file 0 => global file 1
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Zero) at (prev + 25, 5) to (start + 0, 23)
- Code(Zero) at (prev + 31, 5) to (start + 0, 23)
Highest counter ID seen: (none)

Function name: holes::main::{closure#0} (unused)
Raw bytes (9): 0x[01, 01, 00, 01, 00, 12, 09, 02, 0a]
Raw bytes (9): 0x[01, 01, 00, 01, 00, 18, 09, 02, 0a]
Number of files: 1
- file 0 => global file 1
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Zero) at (prev + 18, 9) to (start + 2, 10)
- Code(Zero) at (prev + 24, 9) to (start + 2, 10)
Highest counter ID seen: (none)

Function name: holes::main::{closure#1} (unused)
Raw bytes (9): 0x[01, 01, 00, 01, 00, 3d, 09, 02, 0a]
Raw bytes (9): 0x[01, 01, 00, 01, 00, 4b, 09, 02, 0a]
Number of files: 1
- file 0 => global file 1
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Zero) at (prev + 61, 9) to (start + 2, 10)
- Code(Zero) at (prev + 75, 9) to (start + 2, 10)
Highest counter ID seen: (none)

41 changes: 37 additions & 4 deletions tests/coverage/holes.coverage
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
LL| |
LL| 1|fn main() {
LL| 1| black_box(());
LL| 1|
LL| 1| // Splitting this across multiple lines makes it easier to see where the
LL| 1| // coverage mapping regions begin and end.
LL| 1| #[rustfmt::skip]
LL| |
LL| | static MY_STATIC: () = ();
LL| |
LL| 1| black_box(());
LL| |
LL| | const MY_CONST: () = ();
LL| |
LL| | // Splitting this across multiple lines makes it easier to see where the
LL| | // coverage mapping regions begin and end.
LL| | #[rustfmt::skip]
LL| 1| let _closure =
LL| | |
LL| | _arg: (),
Expand Down Expand Up @@ -39,6 +45,14 @@
LL| |
LL| 1| black_box(());
LL| |
LL| | trait MyTrait {}
LL| |
LL| 1| black_box(());
LL| |
LL| | impl MyTrait for MyStruct {}
LL| |
LL| 1| black_box(());
LL| |
LL| | macro_rules! _my_macro {
LL| | () => {};
LL| | }
Expand All @@ -64,5 +78,24 @@
LL| | ;
LL| |
LL| 1| black_box(());
LL| 1|
LL| 1| // This tests the edge case of a const block nested inside an "anon const",
LL| 1| // such as the length of an array literal. Handling this case requires
LL| 1| // `nested_filter::OnlyBodies` or equivalent.
LL| 1| #[rustfmt::skip]
LL| 1| let _const_block_inside_anon_const =
LL| 1| [
LL| 1| 0
LL| 1| ;
LL| 1| 7
LL| 1| +
LL| 1| const
LL| | {
LL| | 3
LL| 1| }
LL| 1| ]
LL| 1| ;
LL| 1|
LL| 1| black_box(());
LL| 1|}

33 changes: 33 additions & 0 deletions tests/coverage/holes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ use core::hint::black_box;
fn main() {
black_box(());

static MY_STATIC: () = ();

black_box(());

const MY_CONST: () = ();

// Splitting this across multiple lines makes it easier to see where the
// coverage mapping regions begin and end.
#[rustfmt::skip]
Expand Down Expand Up @@ -39,6 +45,14 @@ fn main() {

black_box(());

trait MyTrait {}

black_box(());

impl MyTrait for MyStruct {}

black_box(());

macro_rules! _my_macro {
() => {};
}
Expand All @@ -64,4 +78,23 @@ fn main() {
;

black_box(());

// This tests the edge case of a const block nested inside an "anon const",
// such as the length of an array literal. Handling this case requires
// `nested_filter::OnlyBodies` or equivalent.
#[rustfmt::skip]
let _const_block_inside_anon_const =
[
0
;
7
+
const
{
3
}
]
;

black_box(());
}
Loading