-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Tracking issue for function attribute #[coverage]
#84605
Comments
cc: @tmandry @wesleywiser |
Nit: should probably follow the pattern of |
Just a wish that I have: Another thing I wish for would be a crate-level attr that would disable coverage for every |
Adds feature-gated `#[no_coverage]` function attribute, to fix derived Eq `0` coverage issue rust-lang#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: rust-lang#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 rust-lang#84605. r? `@tmandry` cc: `@wesleywiser`
I considered this, and discussed it in the initial PR. I'll copy those comments to this tracking issue, to make it easer to read and reply: @tmandry said:
@richkadel replied:
@tmandry replied:
|
@Swatinem - Can you expand on this? What are the use cases for disabling coverage at the crate level, or in crate tests? For example, the use case addressed in PR #83601 is clear: The implementation of I personally see the Generally speaking... I think we want to keep the bar high for now (by restricting the scope of the attribute to functions, until any broader requirement is vetted by the Rust community). So if there are other strong cases for excluding coverage instrumentation, please post them here so we can validate the use cases before expanding the scope of the coverage attribute. |
One more comment for clarity: PR #83601 has been merged and adds the feature-gated |
I should have added: This tracking issue will remain open until the attribute is stablized. This means there is still an opportunity to change the attribute, based on the tracking issue feedback. Thanks! |
This might be a bit personal preference, but is also done that way in other language ecosystems. |
Note: there are previous initiatives to introduce inheritable/scoped/propagated attributes, e.g. for the optimize attribute, but implementing the propagation behaviour there seemed to be pretty involved to me at the time and IMO any attempts to introduce such attribute propagation mechanisms should be a standalone RFC introducing a mechanism in a more general way. |
72: Set cfg(coverage) to easily use #[no_coverage] r=taiki-e a=taiki-e To exclude the specific function from coverage, use the `#[no_coverage]` attribute (rust-lang/rust#84605). Since `#[no_coverage]` is unstable, it is recommended to use it together with `cfg(coverage)` set by cargo-llvm-cov. ```rust #![cfg_attr(coverage, feature(no_coverage))] #[cfg_attr(coverage, no_coverage)] fn exclude_from_coverage() { // ... } ``` Closes #71 Co-authored-by: Taiki Endo <te316e89@gmail.com>
Would it be possible to add this attribute to the code generated by |
Unfortunately, I can understand the motivation for this request, and you may want to file a new issue, if it doesn't already exist. There is a potential downside: coverage reports would not be able to show that an unreachable block was unintentionally executed. Maybe that's a minor tradeoff, but the current coverage reports confirm that unreachable blocks are not reached, by showing the unreachable block has a coverage count of zero. I think adding support for |
And I may be wrong about block |
To me, For macros, we can just think about this as applying to a particular expansion (and the attribute applies to the block in HIR, i.e. after macro expansion). Maybe there are other issues I'm not thinking about. |
I don't think accidentally executing an "unreachable" expression is an issue for coverage, as that's not the job of coverage; tests are where that should be caught. There is a tracking issue for this already, I wasn't sure if there was an easy-ish way to land it. Apparently that's not the case. I would imagine in the future statement and expression-level masking for coverage purposes will exist, which would necessarily allow for this. Ultimately this would just cause the macro to include the attribute in its expansion. |
Is there a good reason why derived traits are included in coverage reports, and not also marked as But, I could see an argument for including them by default but allowing a |
In terms of specifically how this attribute should be named, I would be in favour of going with a
This obviously wouldn't be required for an initial stable |
I'm not convinced ignoring trait functions is a good idea, from a test coverage perspective. When you derive a trait, your struct's API contract has changed. That struct now supports every function defined in that trait. To guarantee 100% test coverage of the behaviors of every function for your struct, you would have to test it. Without adding a test, the " |
Yes, the API contract changes when you derive standard traits, but I personally don't believe that testing them adds any value for the developer. For example, I should clarify I'm explicitly talking about the standard derives; individual proc macros can make the decision whether they want to add the attribute to their generated code or not. |
Are there known cases where the instrumentor won't create a counter for a function marked for coverage? If the coverage counter as created and added to the coverage map it doesn't matter if dead-code elimination or link-time GC removes the function, the coverage will still correctly report that the function wasn't hit. |
From what I understand, coverage is instrumented after dead code elimination, so, dead code won't ever be instrumented. What you're saying makes sense as a solution, but I'm not sure it's possible with LLVM or the current setup, and it's better to just explain the state of coverage now than block the attribute on it being improved. |
I wasn't suggesting to hold up the implementation, I was just surprised there were cases where instrumentation (or at least, counters) weren't emitted for a function when coverage was enabled. Though I'm surprised by your assertion, as I understand it, coverage markers are added by the frontend, whereas DCE is surely done by the backend (along with coverage-lowering which is uninteresting for this question)? |
Yeah, it's a very confusing system for sure. My understanding is that the entire thing is done after code generation, meaning that effectively, only the code that ends up in the final executable will show up on the list of coverage markers. The compiler can add in extra annotations to source that tell the code generator how coverage should be set up, but ultimately, those markers are ignored until the final code is generated and the final list is made. This is why so many suggestions about coverage involve disabling LTO, disabling dead code removal, etc. Because ultimately, the original source doesn't matter for coverage, and the instrumentation just tracks back to the original code from the code that's left at the end. |
That's true for things like gcov and kcov, where coverage data is reverse engineered from the compiled binary, but the whole point of frontend-driven coverage is that the compiler knows how to refer to the source code to create coverage data (see https://clang.llvm.org/docs/SourceBasedCodeCoverage.html and https://rustc-dev-guide.rust-lang.org/llvm-coverage-instrumentation.html which both make it clear that producing the coverage map and injecting the In fact a quick prototype shows that dead code is still reported as missed so maybe this is working today anyway?
Edit: and yes, the
|
In my mind, the main thing we are stabilizing is the syntax of The precise effect of those attributes should be left to the discretion of the compiler implementation, in much the same way that (We can still document the intended meaning of those attributes, and in practice the compiler will make a good-faith effort to honour that as much as reasonably possible. But we shouldn't be making any stable promises about which functions do or don't get instrumented, just as we don't make any stable promises about how the code inside those functions is instrumented.) |
In the current implementation, there are three main reasons why a function might not show up in the final coverage output:
|
An important note for T-lang raised by Oli on the implementation PR:
Also copying my own response from there:
|
As far as I'm aware, the closest precedent for my proposed behaviour (scanning all enclosing functions/impls/modules up to the crate root) is the current behaviour of the lint-control attributes (e.g. So what's new here is extending that sort of behaviour to a non-diagnostic attribute. (It's slightly surprising that this hasn't come up before, but looking through the existing built-in attributes, there isn't much demand for recursive behaviour outside of lints.) |
I'm also surprised this is the first place this has come up, but it also feels like precisely the right behaviour, so. 🤷🏻 |
I believe As an interested user of this attribute, this behavior makes sense to me 👍 Thank you to everyone helping make it happen! |
@rfcbot resolved should-attribute-disable-coverage-for-nested-functions? |
@rfcbot resolved should-attribute-disable-coverage-for-inlined-functions? |
@rfcbot reviewed |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
@rustbot labels -I-lang-nominated This is in FCP, so we can unnominate. We discussed this briefly in the extended triage meeting today. |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
This might come late in the discussion, but have you considered stabilizing coverage as a diagnostic namespace attribute? E.g.: #[diagnostic::coverage(off)]
fn foobar() {} The main advantage of doing that is that people could then use coverage annotations while keeping a MSRV of And I do think coverage as a diagnostic makes sense. Yes, it changes the emitted artifact, but it doesn't change the program semantics. And more importantly, it doesn't affect dependencies: if a dependency has coverage annotations you don't understand, you can just ignore them without negative impact to your output. |
Coverage isn't a diagnostic, though; it's a build flag that can change the way the crate is built under certain circumstances. |
This issue will track the approval and stabilization of the attribute
coverage
, needed to give developers a way to "hide" a function from the coverage instrumentation enabled byrustc -Z instrument-coverage
.The
Eq
trait in thestd
library implements a marker function that is not meant to be executed, but results in an uncovered region in all rust programs that deriveEq
. This attribute will allow the compiler to skip that function, and remove the uncovered regions.Unresolved questions
The text was updated successfully, but these errors were encountered: