Skip to content

Commit 70b69a2

Browse files
authored
Rollup merge of #126721 - Zalathar:nested-cov-attr, r=oli-obk
coverage: Make `#[coverage(..)]` apply recursively to nested functions This PR makes the (currently-unstable) `#[coverage(off)]` and `#[coverage(on)]` attributes apply recursively to all nested functions/closures, instead of just the function they are directly attached to. Those attributes can now also be applied to modules and to impl/impl-trait blocks, where they have no direct effect, but will be inherited by all enclosed functions/closures/methods that don't override the inherited value. --- Fixes #126625.
2 parents 5ec93b8 + 7f37f8a commit 70b69a2

23 files changed

+344
-344
lines changed

compiler/rustc_codegen_ssa/src/codegen_attrs.rs

-17
Original file line numberDiff line numberDiff line change
@@ -124,22 +124,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
124124
.emit();
125125
}
126126
}
127-
sym::coverage => {
128-
let inner = attr.meta_item_list();
129-
match inner.as_deref() {
130-
Some([item]) if item.has_name(sym::off) => {
131-
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_COVERAGE;
132-
}
133-
Some([item]) if item.has_name(sym::on) => {
134-
// Allow #[coverage(on)] for being explicit, maybe also in future to enable
135-
// coverage on a smaller scope within an excluded larger scope.
136-
}
137-
Some(_) | None => {
138-
tcx.dcx()
139-
.span_delayed_bug(attr.span, "unexpected value of coverage attribute");
140-
}
141-
}
142-
}
143127
sym::rustc_std_internal_symbol => {
144128
codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL
145129
}
@@ -584,7 +568,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
584568
}
585569

586570
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED) {
587-
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_COVERAGE;
588571
codegen_fn_attrs.inline = InlineAttr::Never;
589572
}
590573

compiler/rustc_middle/src/middle/codegen_fn_attrs.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,7 @@ bitflags::bitflags! {
8787
/// #[cmse_nonsecure_entry]: with a TrustZone-M extension, declare a
8888
/// function as an entry function from Non-Secure code.
8989
const CMSE_NONSECURE_ENTRY = 1 << 13;
90-
/// `#[coverage(off)]`: indicates that the function should be ignored by
91-
/// the MIR `InstrumentCoverage` pass and not added to the coverage map
92-
/// during codegen.
93-
const NO_COVERAGE = 1 << 14;
90+
// (Bit 14 was used for `#[coverage(off)]`, but is now unused.)
9491
/// `#[used(linker)]`:
9592
/// indicates that neither LLVM nor the linker will eliminate this function.
9693
const USED_LINKER = 1 << 15;

compiler/rustc_middle/src/query/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,15 @@ rustc_queries! {
572572
separate_provide_extern
573573
}
574574

575+
/// Checks for the nearest `#[coverage(off)]` or `#[coverage(on)]` on
576+
/// this def and any enclosing defs, up to the crate root.
577+
///
578+
/// Returns `false` if `#[coverage(off)]` was found, or `true` if
579+
/// either `#[coverage(on)]` or no coverage attribute was found.
580+
query coverage_attr_on(key: LocalDefId) -> bool {
581+
desc { |tcx| "checking for `#[coverage(..)]` on `{}`", tcx.def_path_str(key) }
582+
}
583+
575584
/// Summarizes coverage IDs inserted by the `InstrumentCoverage` MIR pass
576585
/// (for compiler option `-Cinstrument-coverage`), after MIR optimizations
577586
/// have had a chance to potentially remove some of them.

compiler/rustc_mir_transform/src/coverage/query.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use rustc_middle::query::TyCtxtAt;
66
use rustc_middle::ty::{self, TyCtxt};
77
use rustc_middle::util::Providers;
88
use rustc_span::def_id::LocalDefId;
9+
use rustc_span::sym;
910

1011
/// Registers query/hook implementations related to coverage.
1112
pub(crate) fn provide(providers: &mut Providers) {
1213
providers.hooks.is_eligible_for_coverage =
1314
|TyCtxtAt { tcx, .. }, def_id| is_eligible_for_coverage(tcx, def_id);
15+
providers.queries.coverage_attr_on = coverage_attr_on;
1416
providers.queries.coverage_ids_info = coverage_ids_info;
1517
}
1618

@@ -38,14 +40,43 @@ fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
3840
return false;
3941
}
4042

41-
if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
43+
if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::NAKED) {
44+
trace!("InstrumentCoverage skipped for {def_id:?} (`#[naked]`)");
45+
return false;
46+
}
47+
48+
if !tcx.coverage_attr_on(def_id) {
4249
trace!("InstrumentCoverage skipped for {def_id:?} (`#[coverage(off)]`)");
4350
return false;
4451
}
4552

4653
true
4754
}
4855

56+
/// Query implementation for `coverage_attr_on`.
57+
fn coverage_attr_on(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
58+
// Check for annotations directly on this def.
59+
if let Some(attr) = tcx.get_attr(def_id, sym::coverage) {
60+
match attr.meta_item_list().as_deref() {
61+
Some([item]) if item.has_name(sym::off) => return false,
62+
Some([item]) if item.has_name(sym::on) => return true,
63+
Some(_) | None => {
64+
// Other possibilities should have been rejected by `rustc_parse::validate_attr`.
65+
tcx.dcx().span_bug(attr.span, "unexpected value of coverage attribute");
66+
}
67+
}
68+
}
69+
70+
match tcx.opt_local_parent(def_id) {
71+
// Check the parent def (and so on recursively) until we find an
72+
// enclosing attribute or reach the crate root.
73+
Some(parent) => tcx.coverage_attr_on(parent),
74+
// We reached the crate root without seeing a coverage attribute, so
75+
// allow coverage instrumentation by default.
76+
None => true,
77+
}
78+
}
79+
4980
/// Query implementation for `coverage_ids_info`.
5081
fn coverage_ids_info<'tcx>(
5182
tcx: TyCtxt<'tcx>,

compiler/rustc_passes/src/check_attr.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -369,13 +369,16 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
369369
}
370370
}
371371

372-
/// Checks that `#[coverage(..)]` is applied to a function or closure.
372+
/// Checks that `#[coverage(..)]` is applied to a function/closure/method,
373+
/// or to an impl block or module.
373374
fn check_coverage(&self, attr: &Attribute, span: Span, target: Target) -> bool {
374375
match target {
375-
// #[coverage(..)] on function is fine
376376
Target::Fn
377377
| Target::Closure
378-
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
378+
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent)
379+
| Target::Impl
380+
| Target::Mod => true,
381+
379382
_ => {
380383
self.dcx().emit_err(errors::CoverageNotFnOrClosure {
381384
attr_span: attr.span,

tests/coverage/attr/impl.cov-map

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Function name: <impl::MyStruct>::off_on (unused)
2+
Raw bytes (9): 0x[01, 01, 00, 01, 00, 0e, 05, 00, 13]
3+
Number of files: 1
4+
- file 0 => global file 1
5+
Number of expressions: 0
6+
Number of file 0 mappings: 1
7+
- Code(Zero) at (prev + 14, 5) to (start + 0, 19)
8+
9+
Function name: <impl::MyStruct>::on_inherit (unused)
10+
Raw bytes (9): 0x[01, 01, 00, 01, 00, 16, 05, 00, 17]
11+
Number of files: 1
12+
- file 0 => global file 1
13+
Number of expressions: 0
14+
Number of file 0 mappings: 1
15+
- Code(Zero) at (prev + 22, 5) to (start + 0, 23)
16+
17+
Function name: <impl::MyStruct>::on_on (unused)
18+
Raw bytes (9): 0x[01, 01, 00, 01, 00, 19, 05, 00, 12]
19+
Number of files: 1
20+
- file 0 => global file 1
21+
Number of expressions: 0
22+
Number of file 0 mappings: 1
23+
- Code(Zero) at (prev + 25, 5) to (start + 0, 18)
24+

tests/coverage/attr/impl.coverage

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
LL| |#![feature(coverage_attribute)]
2+
LL| |//@ edition: 2021
3+
LL| |
4+
LL| |// Checks that `#[coverage(..)]` can be applied to impl and impl-trait blocks,
5+
LL| |// and is inherited by any enclosed functions.
6+
LL| |
7+
LL| |struct MyStruct;
8+
LL| |
9+
LL| |#[coverage(off)]
10+
LL| |impl MyStruct {
11+
LL| | fn off_inherit() {}
12+
LL| |
13+
LL| | #[coverage(on)]
14+
LL| 0| fn off_on() {}
15+
LL| |
16+
LL| | #[coverage(off)]
17+
LL| | fn off_off() {}
18+
LL| |}
19+
LL| |
20+
LL| |#[coverage(on)]
21+
LL| |impl MyStruct {
22+
LL| 0| fn on_inherit() {}
23+
LL| |
24+
LL| | #[coverage(on)]
25+
LL| 0| fn on_on() {}
26+
LL| |
27+
LL| | #[coverage(off)]
28+
LL| | fn on_off() {}
29+
LL| |}
30+
LL| |
31+
LL| |trait MyTrait {
32+
LL| | fn method();
33+
LL| |}
34+
LL| |
35+
LL| |#[coverage(off)]
36+
LL| |impl MyTrait for MyStruct {
37+
LL| | fn method() {}
38+
LL| |}
39+
LL| |
40+
LL| |#[coverage(off)]
41+
LL| |fn main() {}
42+

tests/coverage/attr/impl.rs

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#![feature(coverage_attribute)]
2+
//@ edition: 2021
3+
4+
// Checks that `#[coverage(..)]` can be applied to impl and impl-trait blocks,
5+
// and is inherited by any enclosed functions.
6+
7+
struct MyStruct;
8+
9+
#[coverage(off)]
10+
impl MyStruct {
11+
fn off_inherit() {}
12+
13+
#[coverage(on)]
14+
fn off_on() {}
15+
16+
#[coverage(off)]
17+
fn off_off() {}
18+
}
19+
20+
#[coverage(on)]
21+
impl MyStruct {
22+
fn on_inherit() {}
23+
24+
#[coverage(on)]
25+
fn on_on() {}
26+
27+
#[coverage(off)]
28+
fn on_off() {}
29+
}
30+
31+
trait MyTrait {
32+
fn method();
33+
}
34+
35+
#[coverage(off)]
36+
impl MyTrait for MyStruct {
37+
fn method() {}
38+
}
39+
40+
#[coverage(off)]
41+
fn main() {}

tests/coverage/attr/module.cov-map

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Function name: module::off::on (unused)
2+
Raw bytes (9): 0x[01, 01, 00, 01, 00, 0c, 05, 00, 0f]
3+
Number of files: 1
4+
- file 0 => global file 1
5+
Number of expressions: 0
6+
Number of file 0 mappings: 1
7+
- Code(Zero) at (prev + 12, 5) to (start + 0, 15)
8+
9+
Function name: module::on::inherit (unused)
10+
Raw bytes (9): 0x[01, 01, 00, 01, 00, 14, 05, 00, 14]
11+
Number of files: 1
12+
- file 0 => global file 1
13+
Number of expressions: 0
14+
Number of file 0 mappings: 1
15+
- Code(Zero) at (prev + 20, 5) to (start + 0, 20)
16+
17+
Function name: module::on::on (unused)
18+
Raw bytes (9): 0x[01, 01, 00, 01, 00, 17, 05, 00, 0f]
19+
Number of files: 1
20+
- file 0 => global file 1
21+
Number of expressions: 0
22+
Number of file 0 mappings: 1
23+
- Code(Zero) at (prev + 23, 5) to (start + 0, 15)
24+

tests/coverage/attr/module.coverage

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
LL| |#![feature(coverage_attribute)]
2+
LL| |//@ edition: 2021
3+
LL| |
4+
LL| |// Checks that `#[coverage(..)]` can be applied to modules, and is inherited
5+
LL| |// by any enclosed functions.
6+
LL| |
7+
LL| |#[coverage(off)]
8+
LL| |mod off {
9+
LL| | fn inherit() {}
10+
LL| |
11+
LL| | #[coverage(on)]
12+
LL| 0| fn on() {}
13+
LL| |
14+
LL| | #[coverage(off)]
15+
LL| | fn off() {}
16+
LL| |}
17+
LL| |
18+
LL| |#[coverage(on)]
19+
LL| |mod on {
20+
LL| 0| fn inherit() {}
21+
LL| |
22+
LL| | #[coverage(on)]
23+
LL| 0| fn on() {}
24+
LL| |
25+
LL| | #[coverage(off)]
26+
LL| | fn off() {}
27+
LL| |}
28+
LL| |
29+
LL| |#[coverage(off)]
30+
LL| |mod nested_a {
31+
LL| | mod nested_b {
32+
LL| | fn inner() {}
33+
LL| | }
34+
LL| |}
35+
LL| |
36+
LL| |#[coverage(off)]
37+
LL| |fn main() {}
38+

tests/coverage/attr/module.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#![feature(coverage_attribute)]
2+
//@ edition: 2021
3+
4+
// Checks that `#[coverage(..)]` can be applied to modules, and is inherited
5+
// by any enclosed functions.
6+
7+
#[coverage(off)]
8+
mod off {
9+
fn inherit() {}
10+
11+
#[coverage(on)]
12+
fn on() {}
13+
14+
#[coverage(off)]
15+
fn off() {}
16+
}
17+
18+
#[coverage(on)]
19+
mod on {
20+
fn inherit() {}
21+
22+
#[coverage(on)]
23+
fn on() {}
24+
25+
#[coverage(off)]
26+
fn off() {}
27+
}
28+
29+
#[coverage(off)]
30+
mod nested_a {
31+
mod nested_b {
32+
fn inner() {}
33+
}
34+
}
35+
36+
#[coverage(off)]
37+
fn main() {}

0 commit comments

Comments
 (0)