Skip to content

Commit 20040fa

Browse files
committed
Auto merge of #84562 - richkadel:issue-83601, r=tmandry
Adds feature-gated `#[no_coverage]` function attribute, to fix derived Eq `0` coverage issue #83601 Derived Eq no longer shows uncovered The Eq trait has a special hidden function. MIR `InstrumentCoverage` would add this function to the coverage map, but it is never called, so the `Eq` trait would always appear uncovered. Fixes: #83601 The fix required creating a new function attribute `no_coverage` to mark functions that should be ignored by `InstrumentCoverage` and the coverage `mapgen` (during codegen). Adding a `no_coverage` feature gate with tracking issue #84605. r? `@tmandry` cc: `@wesleywiser`
2 parents 237eab1 + 3a5df48 commit 20040fa

File tree

18 files changed

+198
-2
lines changed

18 files changed

+198
-2
lines changed

compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,19 @@ pub fn expand_deriving_eq(
1616
push: &mut dyn FnMut(Annotatable),
1717
) {
1818
let inline = cx.meta_word(span, sym::inline);
19+
let no_coverage_ident =
20+
rustc_ast::attr::mk_nested_word_item(Ident::new(sym::no_coverage, span));
21+
let no_coverage_feature =
22+
rustc_ast::attr::mk_list_item(Ident::new(sym::feature, span), vec![no_coverage_ident]);
23+
let no_coverage = cx.meta_word(span, sym::no_coverage);
1924
let hidden = rustc_ast::attr::mk_nested_word_item(Ident::new(sym::hidden, span));
2025
let doc = rustc_ast::attr::mk_list_item(Ident::new(sym::doc, span), vec![hidden]);
21-
let attrs = vec![cx.attribute(inline), cx.attribute(doc)];
26+
let attrs = vec![
27+
cx.attribute(inline),
28+
cx.attribute(no_coverage_feature),
29+
cx.attribute(no_coverage),
30+
cx.attribute(doc),
31+
];
2232
let trait_def = TraitDef {
2333
span,
2434
attributes: Vec::new(),

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_codegen_ssa::traits::{ConstMethods, CoverageInfoMethods};
88
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
99
use rustc_hir::def_id::{DefId, DefIdSet, LOCAL_CRATE};
1010
use rustc_llvm::RustString;
11+
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
1112
use rustc_middle::mir::coverage::CodeRegion;
1213
use rustc_span::Symbol;
1314

@@ -280,6 +281,10 @@ fn add_unused_functions<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) {
280281

281282
let mut unused_def_ids_by_file: FxHashMap<Symbol, Vec<DefId>> = FxHashMap::default();
282283
for &non_codegenned_def_id in all_def_ids.difference(codegenned_def_ids) {
284+
let codegen_fn_attrs = tcx.codegen_fn_attrs(non_codegenned_def_id);
285+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
286+
continue;
287+
}
283288
// Make sure the non-codegenned (unused) function has a file_name
284289
if let Some(non_codegenned_file_name) = tcx.covered_file_name(non_codegenned_def_id) {
285290
let def_ids =

compiler/rustc_feature/src/active.rs

+4
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,10 @@ declare_features! (
646646
/// Allows `extern "wasm" fn`
647647
(active, wasm_abi, "1.53.0", Some(83788), None),
648648

649+
/// Allows function attribute `#[no_coverage]`, to bypass coverage
650+
/// instrumentation of that function.
651+
(active, no_coverage, "1.53.0", Some(84605), None),
652+
649653
/// Allows trait bounds in `const fn`.
650654
(active, const_fn_trait_bound, "1.53.0", Some(57563), None),
651655

compiler/rustc_feature/src/builtin_attrs.rs

+7
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,13 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
273273
template!(List: "address, memory, thread"),
274274
experimental!(no_sanitize)
275275
),
276+
ungated!(
277+
// Not exclusively gated at the crate level (though crate-level is
278+
// supported). The feature can alternatively be enabled on individual
279+
// functions.
280+
no_coverage, AssumedUsed,
281+
template!(Word),
282+
),
276283

277284
// FIXME: #14408 assume docs are used since rustdoc looks at them.
278285
ungated!(doc, AssumedUsed, template!(List: "hidden|inline|...", NameValueStr: "string")),

compiler/rustc_middle/src/middle/codegen_fn_attrs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ bitflags! {
8989
/// #[cmse_nonsecure_entry]: with a TrustZone-M extension, declare a
9090
/// function as an entry function from Non-Secure code.
9191
const CMSE_NONSECURE_ENTRY = 1 << 14;
92+
/// `#[no_coverage]`: indicates that the function should be ignored by
93+
/// the MIR `InstrumentCoverage` pass and not added to the coverage map
94+
/// during codegen.
95+
const NO_COVERAGE = 1 << 15;
9296
}
9397
}
9498

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

+6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use rustc_index::vec::IndexVec;
2323
use rustc_middle::hir;
2424
use rustc_middle::hir::map::blocks::FnLikeNode;
2525
use rustc_middle::ich::StableHashingContext;
26+
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
2627
use rustc_middle::mir::coverage::*;
2728
use rustc_middle::mir::{
2829
self, BasicBlock, BasicBlockData, Coverage, SourceInfo, Statement, StatementKind, Terminator,
@@ -87,6 +88,11 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
8788
_ => {}
8889
}
8990

91+
let codegen_fn_attrs = tcx.codegen_fn_attrs(mir_source.def_id());
92+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
93+
return;
94+
}
95+
9096
trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());
9197
Instrumentor::new(&self.name(), tcx, mir_body).inject_counters();
9298
trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ symbols! {
781781
no,
782782
no_builtins,
783783
no_core,
784+
no_coverage,
784785
no_crate_inject,
785786
no_debug,
786787
no_default_passes,

compiler/rustc_typeck/src/collect.rs

+28
Original file line numberDiff line numberDiff line change
@@ -2661,6 +2661,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
26612661
let mut inline_span = None;
26622662
let mut link_ordinal_span = None;
26632663
let mut no_sanitize_span = None;
2664+
let mut no_coverage_feature_enabled = false;
2665+
let mut no_coverage_attr = None;
26642666
for attr in attrs.iter() {
26652667
if tcx.sess.check_name(attr, sym::cold) {
26662668
codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD;
@@ -2724,6 +2726,15 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
27242726
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED;
27252727
} else if tcx.sess.check_name(attr, sym::no_mangle) {
27262728
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE;
2729+
} else if attr.has_name(sym::feature) {
2730+
if let Some(list) = attr.meta_item_list() {
2731+
if list.iter().any(|nested_meta_item| nested_meta_item.has_name(sym::no_coverage)) {
2732+
tcx.sess.mark_attr_used(attr);
2733+
no_coverage_feature_enabled = true;
2734+
}
2735+
}
2736+
} else if tcx.sess.check_name(attr, sym::no_coverage) {
2737+
no_coverage_attr = Some(attr);
27272738
} else if tcx.sess.check_name(attr, sym::rustc_std_internal_symbol) {
27282739
codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL;
27292740
} else if tcx.sess.check_name(attr, sym::used) {
@@ -2934,6 +2945,23 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
29342945
}
29352946
}
29362947

2948+
if let Some(no_coverage_attr) = no_coverage_attr {
2949+
if tcx.sess.features_untracked().no_coverage || no_coverage_feature_enabled {
2950+
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_COVERAGE
2951+
} else {
2952+
let mut err = feature_err(
2953+
&tcx.sess.parse_sess,
2954+
sym::no_coverage,
2955+
no_coverage_attr.span,
2956+
"the `#[no_coverage]` attribute is an experimental feature",
2957+
);
2958+
if tcx.sess.parse_sess.unstable_features.is_nightly_build() {
2959+
err.help("or, alternatively, add `#[feature(no_coverage)]` to the function");
2960+
}
2961+
err.emit();
2962+
}
2963+
}
2964+
29372965
codegen_fn_attrs.inline = attrs.iter().fold(InlineAttr::None, |ia, attr| {
29382966
if !attr.has_name(sym::inline) {
29392967
return ia;

library/core/src/cmp.rs

+2
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ pub trait Eq: PartialEq<Self> {
274274
//
275275
// This should never be implemented by hand.
276276
#[doc(hidden)]
277+
#[cfg_attr(not(bootstrap), feature(no_coverage))]
278+
#[cfg_attr(not(bootstrap), no_coverage)]
277279
#[inline]
278280
#[stable(feature = "rust1", since = "1.0.0")]
279281
fn assert_receiver_is_total_eq(&self) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
1| |// Shows that rust-lang/rust/83601 is resolved
2+
2| |
3+
3| 3|#[derive(Debug, PartialEq, Eq)]
4+
^2
5+
------------------
6+
| <issue_83601::Foo as core::cmp::PartialEq>::eq:
7+
| 3| 2|#[derive(Debug, PartialEq, Eq)]
8+
------------------
9+
| Unexecuted instantiation: <issue_83601::Foo as core::cmp::PartialEq>::ne
10+
------------------
11+
4| |struct Foo(u32);
12+
5| |
13+
6| 1|fn main() {
14+
7| 1| let bar = Foo(1);
15+
8| 0| assert_eq!(bar, Foo(1));
16+
9| 1| let baz = Foo(0);
17+
10| 0| assert_ne!(baz, Foo(1));
18+
11| 1| println!("{:?}", Foo(1));
19+
12| 1| println!("{:?}", bar);
20+
13| 1| println!("{:?}", baz);
21+
14| 1|}
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
1| |// Enables `no_coverage` on the entire crate
2+
2| |#![feature(no_coverage)]
3+
3| |
4+
4| |#[no_coverage]
5+
5| |fn do_not_add_coverage_1() {
6+
6| | println!("called but not covered");
7+
7| |}
8+
8| |
9+
9| |#[no_coverage]
10+
10| |fn do_not_add_coverage_2() {
11+
11| | println!("called but not covered");
12+
12| |}
13+
13| |
14+
14| 1|fn main() {
15+
15| 1| do_not_add_coverage_1();
16+
16| 1| do_not_add_coverage_2();
17+
17| 1|}
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
1| |// Enables `no_coverage` on individual functions
2+
2| |
3+
3| |#[feature(no_coverage)]
4+
4| |#[no_coverage]
5+
5| |fn do_not_add_coverage_1() {
6+
6| | println!("called but not covered");
7+
7| |}
8+
8| |
9+
9| |#[no_coverage]
10+
10| |#[feature(no_coverage)]
11+
11| |fn do_not_add_coverage_2() {
12+
12| | println!("called but not covered");
13+
13| |}
14+
14| |
15+
15| 1|fn main() {
16+
16| 1| do_not_add_coverage_1();
17+
17| 1| do_not_add_coverage_2();
18+
18| 1|}
19+

src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
2| |// structure of this test.
33
3| |
44
4| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
5-
^0 ^0 ^0 ^0 ^1 ^1 ^0^0
5+
^0 ^0 ^0 ^1 ^1 ^0^0
66
------------------
77
| Unexecuted instantiation: <partial_eq::Version as core::cmp::PartialEq>::ne
88
------------------
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Shows that rust-lang/rust/83601 is resolved
2+
3+
#[derive(Debug, PartialEq, Eq)]
4+
struct Foo(u32);
5+
6+
fn main() {
7+
let bar = Foo(1);
8+
assert_eq!(bar, Foo(1));
9+
let baz = Foo(0);
10+
assert_ne!(baz, Foo(1));
11+
println!("{:?}", Foo(1));
12+
println!("{:?}", bar);
13+
println!("{:?}", baz);
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Enables `no_coverage` on the entire crate
2+
#![feature(no_coverage)]
3+
4+
#[no_coverage]
5+
fn do_not_add_coverage_1() {
6+
println!("called but not covered");
7+
}
8+
9+
#[no_coverage]
10+
fn do_not_add_coverage_2() {
11+
println!("called but not covered");
12+
}
13+
14+
fn main() {
15+
do_not_add_coverage_1();
16+
do_not_add_coverage_2();
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Enables `no_coverage` on individual functions
2+
3+
#[feature(no_coverage)]
4+
#[no_coverage]
5+
fn do_not_add_coverage_1() {
6+
println!("called but not covered");
7+
}
8+
9+
#[no_coverage]
10+
#[feature(no_coverage)]
11+
fn do_not_add_coverage_2() {
12+
println!("called but not covered");
13+
}
14+
15+
fn main() {
16+
do_not_add_coverage_1();
17+
do_not_add_coverage_2();
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![crate_type = "lib"]
2+
3+
#[no_coverage]
4+
#[feature(no_coverage)] // does not have to be enabled before `#[no_coverage]`
5+
fn no_coverage_is_enabled_on_this_function() {}
6+
7+
#[no_coverage] //~ ERROR the `#[no_coverage]` attribute is an experimental feature
8+
fn requires_feature_no_coverage() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0658]: the `#[no_coverage]` attribute is an experimental feature
2+
--> $DIR/feature-gate-no_coverage.rs:7:1
3+
|
4+
LL | #[no_coverage]
5+
| ^^^^^^^^^^^^^^
6+
|
7+
= note: see issue #84605 <https://github.com/rust-lang/rust/issues/84605> for more information
8+
= help: add `#![feature(no_coverage)]` to the crate attributes to enable
9+
= help: or, alternatively, add `#[feature(no_coverage)]` to the function
10+
11+
error: aborting due to previous error
12+
13+
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)