diff --git a/compiler/rustc_attr_data_structures/src/attributes.rs b/compiler/rustc_attr_data_structures/src/attributes.rs index 610c93a253ceb..55019cd57a71d 100644 --- a/compiler/rustc_attr_data_structures/src/attributes.rs +++ b/compiler/rustc_attr_data_structures/src/attributes.rs @@ -110,18 +110,10 @@ pub enum DeprecatedSince { Err, } -#[derive( - Copy, - Debug, - Eq, - PartialEq, - Encodable, - Decodable, - Clone, - HashStable_Generic, - PrintAttribute -)] -pub enum CoverageStatus { +/// Successfully-parsed value of a `#[coverage(..)]` attribute. +#[derive(Copy, Debug, Eq, PartialEq, Encodable, Decodable, Clone)] +#[derive(HashStable_Generic, PrintAttribute)] +pub enum CoverageAttrKind { On, Off, } @@ -304,8 +296,8 @@ pub enum AttributeKind { /// Represents `#[const_trait]`. ConstTrait(Span), - /// Represents `#[coverage]`. - Coverage(Span, CoverageStatus), + /// Represents `#[coverage(..)]`. + Coverage(Span, CoverageAttrKind), ///Represents `#[rustc_deny_explicit_impl]`. DenyExplicitImpl(Span), diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index bb28121c2c5d0..afa9abed0bb59 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -1,4 +1,4 @@ -use rustc_attr_data_structures::{AttributeKind, CoverageStatus, OptimizeAttr, UsedBy}; +use rustc_attr_data_structures::{AttributeKind, CoverageAttrKind, OptimizeAttr, UsedBy}; use rustc_feature::{AttributeTemplate, template}; use rustc_session::parse::feature_err; use rustc_span::{Span, Symbol, sym}; @@ -78,16 +78,16 @@ impl SingleAttributeParser for CoverageParser { return None; }; - let status = match arg.path().word_sym() { - Some(sym::off) => CoverageStatus::Off, - Some(sym::on) => CoverageStatus::On, + let kind = match arg.path().word_sym() { + Some(sym::off) => CoverageAttrKind::Off, + Some(sym::on) => CoverageAttrKind::On, None | Some(_) => { fail_incorrect_argument(arg.span()); return None; } }; - Some(AttributeKind::Coverage(cx.attr_span, status)) + Some(AttributeKind::Coverage(cx.attr_span, kind)) } } diff --git a/compiler/rustc_mir_transform/src/coverage/query.rs b/compiler/rustc_mir_transform/src/coverage/query.rs index 551f720c869e8..73795e47d2e9d 100644 --- a/compiler/rustc_mir_transform/src/coverage/query.rs +++ b/compiler/rustc_mir_transform/src/coverage/query.rs @@ -1,4 +1,4 @@ -use rustc_attr_data_structures::{AttributeKind, CoverageStatus, find_attr}; +use rustc_attr_data_structures::{AttributeKind, CoverageAttrKind, find_attr}; use rustc_index::bit_set::DenseBitSet; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir::coverage::{BasicCoverageBlock, CoverageIdsInfo, CoverageKind, MappingKind}; @@ -32,16 +32,6 @@ fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { return false; } - // Don't instrument functions with `#[automatically_derived]` on their - // enclosing impl block, on the assumption that most users won't care about - // coverage for derived impls. - if let Some(impl_of) = tcx.impl_of_assoc(def_id.to_def_id()) - && tcx.is_automatically_derived(impl_of) - { - trace!("InstrumentCoverage skipped for {def_id:?} (automatically derived)"); - return false; - } - if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::NAKED) { trace!("InstrumentCoverage skipped for {def_id:?} (`#[naked]`)"); return false; @@ -57,20 +47,32 @@ fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { /// Query implementation for `coverage_attr_on`. fn coverage_attr_on(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { - // Check for annotations directly on this def. - if let Some(coverage_status) = - find_attr!(tcx.get_all_attrs(def_id), AttributeKind::Coverage(_, status) => status) + // Check for a `#[coverage(..)]` attribute on this def. + if let Some(kind) = + find_attr!(tcx.get_all_attrs(def_id), AttributeKind::Coverage(_sp, kind) => kind) { - *coverage_status == CoverageStatus::On - } else { - match tcx.opt_local_parent(def_id) { - // Check the parent def (and so on recursively) until we find an - // enclosing attribute or reach the crate root. - Some(parent) => tcx.coverage_attr_on(parent), - // We reached the crate root without seeing a coverage attribute, so - // allow coverage instrumentation by default. - None => true, + match kind { + CoverageAttrKind::On => return true, + CoverageAttrKind::Off => return false, } + }; + + // Treat `#[automatically_derived]` as an implied `#[coverage(off)]`, on + // the assumption that most users won't want coverage for derived impls. + // + // This affects not just the associated items of an impl block, but also + // any closures and other nested functions within those associated items. + if tcx.is_automatically_derived(def_id.to_def_id()) { + return false; + } + + // Check the parent def (and so on recursively) until we find an + // enclosing attribute or reach the crate root. + match tcx.opt_local_parent(def_id) { + Some(parent) => tcx.coverage_attr_on(parent), + // We reached the crate root without seeing a coverage attribute, so + // allow coverage instrumentation by default. + None => true, } } diff --git a/tests/coverage/auto-derived.auto.cov-map b/tests/coverage/auto-derived.auto.cov-map new file mode 100644 index 0000000000000..e3d411d895aaa --- /dev/null +++ b/tests/coverage/auto-derived.auto.cov-map @@ -0,0 +1,23 @@ +Function name: ::my_assoc_fn::inner_fn_on +Raw bytes (24): 0x[01, 01, 00, 04, 01, 1f, 09, 00, 19, 01, 01, 0d, 00, 10, 01, 00, 11, 00, 23, 01, 01, 09, 00, 0a] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 31, 9) to (start + 0, 25) +- Code(Counter(0)) at (prev + 1, 13) to (start + 0, 16) +- Code(Counter(0)) at (prev + 0, 17) to (start + 0, 35) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +Highest counter ID seen: c0 + +Function name: auto_derived::main +Raw bytes (19): 0x[01, 01, 00, 03, 01, 33, 01, 00, 0a, 01, 01, 05, 00, 1a, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 3 +- Code(Counter(0)) at (prev + 51, 1) to (start + 0, 10) +- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 26) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c0 + diff --git a/tests/coverage/auto-derived.auto.coverage b/tests/coverage/auto-derived.auto.coverage new file mode 100644 index 0000000000000..242abbf803183 --- /dev/null +++ b/tests/coverage/auto-derived.auto.coverage @@ -0,0 +1,54 @@ + LL| |#![feature(coverage_attribute)] + LL| |//@ edition: 2024 + LL| |//@ revisions: base auto on + LL| | + LL| |// Tests for how `#[automatically_derived]` affects coverage instrumentation. + LL| |// + LL| |// The actual behaviour is an implementation detail, so this test mostly exists + LL| |// to show when that behaviour has been accidentally or deliberately changed. + LL| |// + LL| |// Revision guide: + LL| |// - base: Test baseline instrumentation behaviour without `#[automatically_derived]` + LL| |// - auto: Test how `#[automatically_derived]` affects instrumentation + LL| |// - on: Test interaction between auto-derived and `#[coverage(on)]` + LL| | + LL| |struct MyStruct; + LL| | + LL| |trait MyTrait { + LL| | fn my_assoc_fn(); + LL| |} + LL| | + LL| |#[cfg_attr(auto, automatically_derived)] + LL| |#[cfg_attr(on, automatically_derived)] + LL| |#[cfg_attr(on, coverage(on))] + LL| |impl MyTrait for MyStruct { + LL| | fn my_assoc_fn() { + LL| | fn inner_fn() { + LL| | say("in inner fn"); + LL| | } + LL| | + LL| | #[coverage(on)] + LL| 1| fn inner_fn_on() { + LL| 1| say("in inner fn (on)"); + LL| 1| } + LL| | + LL| | let closure = || { + LL| | say("in closure"); + LL| | }; + LL| | + LL| | closure(); + LL| | inner_fn(); + LL| | inner_fn_on(); + LL| | } + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |#[inline(never)] + LL| |fn say(s: &str) { + LL| | println!("{s}"); + LL| |} + LL| | + LL| 1|fn main() { + LL| 1| MyStruct::my_assoc_fn(); + LL| 1|} + diff --git a/tests/coverage/auto-derived.base.cov-map b/tests/coverage/auto-derived.base.cov-map new file mode 100644 index 0000000000000..2dcd616300d9d --- /dev/null +++ b/tests/coverage/auto-derived.base.cov-map @@ -0,0 +1,61 @@ +Function name: ::my_assoc_fn +Raw bytes (34): 0x[01, 01, 00, 06, 01, 19, 05, 00, 15, 01, 0a, 0d, 00, 14, 01, 04, 09, 00, 12, 01, 01, 09, 00, 11, 01, 01, 09, 00, 14, 01, 01, 05, 00, 06] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 25, 5) to (start + 0, 21) +- Code(Counter(0)) at (prev + 10, 13) to (start + 0, 20) +- Code(Counter(0)) at (prev + 4, 9) to (start + 0, 18) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 17) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 20) +- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 6) +Highest counter ID seen: c0 + +Function name: ::my_assoc_fn::inner_fn +Raw bytes (24): 0x[01, 01, 00, 04, 01, 1a, 09, 00, 16, 01, 01, 0d, 00, 10, 01, 00, 11, 00, 1e, 01, 01, 09, 00, 0a] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 26, 9) to (start + 0, 22) +- Code(Counter(0)) at (prev + 1, 13) to (start + 0, 16) +- Code(Counter(0)) at (prev + 0, 17) to (start + 0, 30) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +Highest counter ID seen: c0 + +Function name: ::my_assoc_fn::inner_fn_on +Raw bytes (24): 0x[01, 01, 00, 04, 01, 1f, 09, 00, 19, 01, 01, 0d, 00, 10, 01, 00, 11, 00, 23, 01, 01, 09, 00, 0a] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 31, 9) to (start + 0, 25) +- Code(Counter(0)) at (prev + 1, 13) to (start + 0, 16) +- Code(Counter(0)) at (prev + 0, 17) to (start + 0, 35) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +Highest counter ID seen: c0 + +Function name: ::my_assoc_fn::{closure#0} +Raw bytes (24): 0x[01, 01, 00, 04, 01, 23, 1a, 00, 1b, 01, 01, 0d, 00, 10, 01, 00, 11, 00, 1d, 01, 01, 09, 00, 0a] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 35, 26) to (start + 0, 27) +- Code(Counter(0)) at (prev + 1, 13) to (start + 0, 16) +- Code(Counter(0)) at (prev + 0, 17) to (start + 0, 29) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +Highest counter ID seen: c0 + +Function name: auto_derived::main +Raw bytes (19): 0x[01, 01, 00, 03, 01, 33, 01, 00, 0a, 01, 01, 05, 00, 1a, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 3 +- Code(Counter(0)) at (prev + 51, 1) to (start + 0, 10) +- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 26) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c0 + diff --git a/tests/coverage/auto-derived.base.coverage b/tests/coverage/auto-derived.base.coverage new file mode 100644 index 0000000000000..6ab6aa5c4dfc4 --- /dev/null +++ b/tests/coverage/auto-derived.base.coverage @@ -0,0 +1,54 @@ + LL| |#![feature(coverage_attribute)] + LL| |//@ edition: 2024 + LL| |//@ revisions: base auto on + LL| | + LL| |// Tests for how `#[automatically_derived]` affects coverage instrumentation. + LL| |// + LL| |// The actual behaviour is an implementation detail, so this test mostly exists + LL| |// to show when that behaviour has been accidentally or deliberately changed. + LL| |// + LL| |// Revision guide: + LL| |// - base: Test baseline instrumentation behaviour without `#[automatically_derived]` + LL| |// - auto: Test how `#[automatically_derived]` affects instrumentation + LL| |// - on: Test interaction between auto-derived and `#[coverage(on)]` + LL| | + LL| |struct MyStruct; + LL| | + LL| |trait MyTrait { + LL| | fn my_assoc_fn(); + LL| |} + LL| | + LL| |#[cfg_attr(auto, automatically_derived)] + LL| |#[cfg_attr(on, automatically_derived)] + LL| |#[cfg_attr(on, coverage(on))] + LL| |impl MyTrait for MyStruct { + LL| 1| fn my_assoc_fn() { + LL| 1| fn inner_fn() { + LL| 1| say("in inner fn"); + LL| 1| } + LL| | + LL| | #[coverage(on)] + LL| 1| fn inner_fn_on() { + LL| 1| say("in inner fn (on)"); + LL| 1| } + LL| | + LL| 1| let closure = || { + LL| 1| say("in closure"); + LL| 1| }; + LL| | + LL| 1| closure(); + LL| 1| inner_fn(); + LL| 1| inner_fn_on(); + LL| 1| } + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |#[inline(never)] + LL| |fn say(s: &str) { + LL| | println!("{s}"); + LL| |} + LL| | + LL| 1|fn main() { + LL| 1| MyStruct::my_assoc_fn(); + LL| 1|} + diff --git a/tests/coverage/auto-derived.on.cov-map b/tests/coverage/auto-derived.on.cov-map new file mode 100644 index 0000000000000..2dcd616300d9d --- /dev/null +++ b/tests/coverage/auto-derived.on.cov-map @@ -0,0 +1,61 @@ +Function name: ::my_assoc_fn +Raw bytes (34): 0x[01, 01, 00, 06, 01, 19, 05, 00, 15, 01, 0a, 0d, 00, 14, 01, 04, 09, 00, 12, 01, 01, 09, 00, 11, 01, 01, 09, 00, 14, 01, 01, 05, 00, 06] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 25, 5) to (start + 0, 21) +- Code(Counter(0)) at (prev + 10, 13) to (start + 0, 20) +- Code(Counter(0)) at (prev + 4, 9) to (start + 0, 18) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 17) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 20) +- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 6) +Highest counter ID seen: c0 + +Function name: ::my_assoc_fn::inner_fn +Raw bytes (24): 0x[01, 01, 00, 04, 01, 1a, 09, 00, 16, 01, 01, 0d, 00, 10, 01, 00, 11, 00, 1e, 01, 01, 09, 00, 0a] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 26, 9) to (start + 0, 22) +- Code(Counter(0)) at (prev + 1, 13) to (start + 0, 16) +- Code(Counter(0)) at (prev + 0, 17) to (start + 0, 30) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +Highest counter ID seen: c0 + +Function name: ::my_assoc_fn::inner_fn_on +Raw bytes (24): 0x[01, 01, 00, 04, 01, 1f, 09, 00, 19, 01, 01, 0d, 00, 10, 01, 00, 11, 00, 23, 01, 01, 09, 00, 0a] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 31, 9) to (start + 0, 25) +- Code(Counter(0)) at (prev + 1, 13) to (start + 0, 16) +- Code(Counter(0)) at (prev + 0, 17) to (start + 0, 35) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +Highest counter ID seen: c0 + +Function name: ::my_assoc_fn::{closure#0} +Raw bytes (24): 0x[01, 01, 00, 04, 01, 23, 1a, 00, 1b, 01, 01, 0d, 00, 10, 01, 00, 11, 00, 1d, 01, 01, 09, 00, 0a] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 35, 26) to (start + 0, 27) +- Code(Counter(0)) at (prev + 1, 13) to (start + 0, 16) +- Code(Counter(0)) at (prev + 0, 17) to (start + 0, 29) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +Highest counter ID seen: c0 + +Function name: auto_derived::main +Raw bytes (19): 0x[01, 01, 00, 03, 01, 33, 01, 00, 0a, 01, 01, 05, 00, 1a, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => $DIR/auto-derived.rs +Number of expressions: 0 +Number of file 0 mappings: 3 +- Code(Counter(0)) at (prev + 51, 1) to (start + 0, 10) +- Code(Counter(0)) at (prev + 1, 5) to (start + 0, 26) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c0 + diff --git a/tests/coverage/auto-derived.on.coverage b/tests/coverage/auto-derived.on.coverage new file mode 100644 index 0000000000000..6ab6aa5c4dfc4 --- /dev/null +++ b/tests/coverage/auto-derived.on.coverage @@ -0,0 +1,54 @@ + LL| |#![feature(coverage_attribute)] + LL| |//@ edition: 2024 + LL| |//@ revisions: base auto on + LL| | + LL| |// Tests for how `#[automatically_derived]` affects coverage instrumentation. + LL| |// + LL| |// The actual behaviour is an implementation detail, so this test mostly exists + LL| |// to show when that behaviour has been accidentally or deliberately changed. + LL| |// + LL| |// Revision guide: + LL| |// - base: Test baseline instrumentation behaviour without `#[automatically_derived]` + LL| |// - auto: Test how `#[automatically_derived]` affects instrumentation + LL| |// - on: Test interaction between auto-derived and `#[coverage(on)]` + LL| | + LL| |struct MyStruct; + LL| | + LL| |trait MyTrait { + LL| | fn my_assoc_fn(); + LL| |} + LL| | + LL| |#[cfg_attr(auto, automatically_derived)] + LL| |#[cfg_attr(on, automatically_derived)] + LL| |#[cfg_attr(on, coverage(on))] + LL| |impl MyTrait for MyStruct { + LL| 1| fn my_assoc_fn() { + LL| 1| fn inner_fn() { + LL| 1| say("in inner fn"); + LL| 1| } + LL| | + LL| | #[coverage(on)] + LL| 1| fn inner_fn_on() { + LL| 1| say("in inner fn (on)"); + LL| 1| } + LL| | + LL| 1| let closure = || { + LL| 1| say("in closure"); + LL| 1| }; + LL| | + LL| 1| closure(); + LL| 1| inner_fn(); + LL| 1| inner_fn_on(); + LL| 1| } + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |#[inline(never)] + LL| |fn say(s: &str) { + LL| | println!("{s}"); + LL| |} + LL| | + LL| 1|fn main() { + LL| 1| MyStruct::my_assoc_fn(); + LL| 1|} + diff --git a/tests/coverage/auto-derived.rs b/tests/coverage/auto-derived.rs new file mode 100644 index 0000000000000..cbf8279918a21 --- /dev/null +++ b/tests/coverage/auto-derived.rs @@ -0,0 +1,53 @@ +#![feature(coverage_attribute)] +//@ edition: 2024 +//@ revisions: base auto on + +// Tests for how `#[automatically_derived]` affects coverage instrumentation. +// +// The actual behaviour is an implementation detail, so this test mostly exists +// to show when that behaviour has been accidentally or deliberately changed. +// +// Revision guide: +// - base: Test baseline instrumentation behaviour without `#[automatically_derived]` +// - auto: Test how `#[automatically_derived]` affects instrumentation +// - on: Test interaction between auto-derived and `#[coverage(on)]` + +struct MyStruct; + +trait MyTrait { + fn my_assoc_fn(); +} + +#[cfg_attr(auto, automatically_derived)] +#[cfg_attr(on, automatically_derived)] +#[cfg_attr(on, coverage(on))] +impl MyTrait for MyStruct { + fn my_assoc_fn() { + fn inner_fn() { + say("in inner fn"); + } + + #[coverage(on)] + fn inner_fn_on() { + say("in inner fn (on)"); + } + + let closure = || { + say("in closure"); + }; + + closure(); + inner_fn(); + inner_fn_on(); + } +} + +#[coverage(off)] +#[inline(never)] +fn say(s: &str) { + println!("{s}"); +} + +fn main() { + MyStruct::my_assoc_fn(); +}