Skip to content

Commit 9ce2a07

Browse files
Rollup merge of #126682 - Zalathar:coverage-attr, r=lcnr
coverage: Overhaul validation of the `#[coverage(..)]` attribute This PR makes sweeping changes to how the (currently-unstable) coverage attribute is validated: - Multiple coverage attributes on the same item/expression are now treated as an error. - The attribute must always be `#[coverage(off)]` or `#[coverage(on)]`, and the error messages for this are more consistent. - A trailing comma is still allowed after off/on, since that's part of the normal attribute syntax. - Some places that silently ignored a coverage attribute now produce an error instead. - These cases were all clearly bugs. - Some places that ignored a coverage attribute (with a warning) now produce an error instead. - These were originally added as lints, but I don't think it makes much sense to knowingly allow new attributes to be used in meaningless places. - Some of these errors might soon disappear, if it's easy to extend recursive coverage attributes to things like modules and impl blocks. --- One of the goals of this PR is to lay a more solid foundation for making the coverage attribute recursive, so that it applies to all nested functions/closures instead of just the one it is directly attached to. Fixes #126658. This PR incorporates #126659, which adds more tests for validation of the coverage attribute. `@rustbot` label +A-code-coverage
2 parents 49bdf46 + 1852141 commit 9ce2a07

18 files changed

+687
-358
lines changed

compiler/rustc_codegen_ssa/messages.ftl

-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ codegen_ssa_create_temp_dir = couldn't create a temp dir: {$error}
2727
2828
codegen_ssa_error_creating_remark_dir = failed to create remark directory: {$error}
2929
30-
codegen_ssa_expected_coverage_symbol = expected `coverage(off)` or `coverage(on)`
31-
3230
codegen_ssa_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)`
3331
3432
codegen_ssa_extern_funcs_not_found = some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified

compiler/rustc_codegen_ssa/src/codegen_attrs.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ use rustc_span::{sym, Span};
1515
use rustc_target::spec::{abi, SanitizerSet};
1616

1717
use crate::errors;
18-
use crate::target_features::from_target_feature;
19-
use crate::{
20-
errors::{ExpectedCoverageSymbol, ExpectedUsedSymbol},
21-
target_features::check_target_feature_trait_unsafe,
22-
};
18+
use crate::target_features::{check_target_feature_trait_unsafe, from_target_feature};
2319

2420
fn linkage_by_name(tcx: TyCtxt<'_>, def_id: LocalDefId, name: &str) -> Linkage {
2521
use rustc_middle::mir::mono::Linkage::*;
@@ -139,7 +135,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
139135
// coverage on a smaller scope within an excluded larger scope.
140136
}
141137
Some(_) | None => {
142-
tcx.dcx().emit_err(ExpectedCoverageSymbol { span: attr.span });
138+
tcx.dcx()
139+
.span_delayed_bug(attr.span, "unexpected value of coverage attribute");
143140
}
144141
}
145142
}
@@ -174,7 +171,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
174171
codegen_fn_attrs.flags |= CodegenFnAttrFlags::USED;
175172
}
176173
Some(_) => {
177-
tcx.dcx().emit_err(ExpectedUsedSymbol { span: attr.span });
174+
tcx.dcx().emit_err(errors::ExpectedUsedSymbol { span: attr.span });
178175
}
179176
None => {
180177
// Unfortunately, unconditionally using `llvm.used` causes

compiler/rustc_codegen_ssa/src/errors.rs

-7
Original file line numberDiff line numberDiff line change
@@ -564,13 +564,6 @@ pub struct UnknownArchiveKind<'a> {
564564
pub kind: &'a str,
565565
}
566566

567-
#[derive(Diagnostic)]
568-
#[diag(codegen_ssa_expected_coverage_symbol)]
569-
pub struct ExpectedCoverageSymbol {
570-
#[primary_span]
571-
pub span: Span,
572-
}
573-
574567
#[derive(Diagnostic)]
575568
#[diag(codegen_ssa_expected_used_symbol)]
576569
pub struct ExpectedUsedSymbol {

compiler/rustc_feature/src/builtin_attrs.rs

+15-11
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ pub struct AttributeTemplate {
105105
pub word: bool,
106106
/// If `Some`, the attribute is allowed to take a list of items like `#[allow(..)]`.
107107
pub list: Option<&'static str>,
108+
/// If non-empty, the attribute is allowed to take a list containing exactly
109+
/// one of the listed words, like `#[coverage(off)]`.
110+
pub one_of: &'static [Symbol],
108111
/// If `Some`, the attribute is allowed to be a name/value pair where the
109112
/// value is a string, like `#[must_use = "reason"]`.
110113
pub name_value_str: Option<&'static str>,
@@ -165,19 +168,20 @@ pub enum AttributeDuplicates {
165168
/// E.g., `template!(Word, List: "description")` means that the attribute
166169
/// supports forms `#[attr]` and `#[attr(description)]`.
167170
macro_rules! template {
168-
(Word) => { template!(@ true, None, None) };
169-
(List: $descr: expr) => { template!(@ false, Some($descr), None) };
170-
(NameValueStr: $descr: expr) => { template!(@ false, None, Some($descr)) };
171-
(Word, List: $descr: expr) => { template!(@ true, Some($descr), None) };
172-
(Word, NameValueStr: $descr: expr) => { template!(@ true, None, Some($descr)) };
171+
(Word) => { template!(@ true, None, &[], None) };
172+
(List: $descr: expr) => { template!(@ false, Some($descr), &[], None) };
173+
(OneOf: $one_of: expr) => { template!(@ false, None, $one_of, None) };
174+
(NameValueStr: $descr: expr) => { template!(@ false, None, &[], Some($descr)) };
175+
(Word, List: $descr: expr) => { template!(@ true, Some($descr), &[], None) };
176+
(Word, NameValueStr: $descr: expr) => { template!(@ true, None, &[], Some($descr)) };
173177
(List: $descr1: expr, NameValueStr: $descr2: expr) => {
174-
template!(@ false, Some($descr1), Some($descr2))
178+
template!(@ false, Some($descr1), &[], Some($descr2))
175179
};
176180
(Word, List: $descr1: expr, NameValueStr: $descr2: expr) => {
177-
template!(@ true, Some($descr1), Some($descr2))
181+
template!(@ true, Some($descr1), &[], Some($descr2))
178182
};
179-
(@ $word: expr, $list: expr, $name_value_str: expr) => { AttributeTemplate {
180-
word: $word, list: $list, name_value_str: $name_value_str
183+
(@ $word: expr, $list: expr, $one_of: expr, $name_value_str: expr) => { AttributeTemplate {
184+
word: $word, list: $list, one_of: $one_of, name_value_str: $name_value_str
181185
} };
182186
}
183187

@@ -478,8 +482,8 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
478482
EncodeCrossCrate::No, experimental!(no_sanitize)
479483
),
480484
gated!(
481-
coverage, Normal, template!(Word, List: "on|off"),
482-
WarnFollowing, EncodeCrossCrate::No,
485+
coverage, Normal, template!(OneOf: &[sym::off, sym::on]),
486+
ErrorPreceding, EncodeCrossCrate::No,
483487
coverage_attribute, experimental!(coverage)
484488
),
485489

compiler/rustc_parse/src/validate_attr.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ use crate::{errors, parse_in};
44

55
use rustc_ast::token::Delimiter;
66
use rustc_ast::tokenstream::DelimSpan;
7-
use rustc_ast::MetaItemKind;
8-
use rustc_ast::{self as ast, AttrArgs, AttrArgsEq, Attribute, DelimArgs, MetaItem, Safety};
7+
use rustc_ast::{
8+
self as ast, AttrArgs, AttrArgsEq, Attribute, DelimArgs, MetaItem, MetaItemKind,
9+
NestedMetaItem, Safety,
10+
};
911
use rustc_errors::{Applicability, FatalError, PResult};
1012
use rustc_feature::{
1113
AttributeSafety, AttributeTemplate, BuiltinAttribute, Features, BUILTIN_ATTRIBUTE_MAP,
@@ -184,9 +186,13 @@ pub(super) fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim
184186

185187
/// Checks that the given meta-item is compatible with this `AttributeTemplate`.
186188
fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
189+
let is_one_allowed_subword = |items: &[NestedMetaItem]| match items {
190+
[item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
191+
_ => false,
192+
};
187193
match meta {
188194
MetaItemKind::Word => template.word,
189-
MetaItemKind::List(..) => template.list.is_some(),
195+
MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items),
190196
MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
191197
MetaItemKind::NameValue(..) => false,
192198
}
@@ -230,6 +236,7 @@ fn emit_malformed_attribute(
230236
if let Some(descr) = template.list {
231237
suggestions.push(format!("#{inner}[{name}({descr})]"));
232238
}
239+
suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
233240
if let Some(descr) = template.name_value_str {
234241
suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
235242
}

compiler/rustc_passes/messages.ftl

+3-12
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,9 @@ passes_continue_labeled_block =
103103
.label = labeled blocks cannot be `continue`'d
104104
.block_label = labeled block the `continue` points to
105105
106-
passes_coverage_fn_defn =
107-
`#[coverage]` may only be applied to function definitions
108-
109-
passes_coverage_ignored_function_prototype =
110-
`#[coverage]` is ignored on function prototypes
111-
112-
passes_coverage_not_coverable =
113-
`#[coverage]` must be applied to coverable code
114-
.label = not coverable code
115-
116-
passes_coverage_propagate =
117-
`#[coverage]` does not propagate into items and must be applied to the contained functions directly
106+
passes_coverage_not_fn_or_closure =
107+
attribute should be applied to a function definition or closure
108+
.label = not a function or closure
118109
119110
passes_dead_codes =
120111
{ $multiple ->

compiler/rustc_passes/src/check_attr.rs

+5-37
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
122122
self.check_diagnostic_on_unimplemented(attr.span, hir_id, target)
123123
}
124124
[sym::inline] => self.check_inline(hir_id, attr, span, target),
125-
[sym::coverage] => self.check_coverage(hir_id, attr, span, target),
125+
[sym::coverage] => self.check_coverage(attr, span, target),
126126
[sym::non_exhaustive] => self.check_non_exhaustive(hir_id, attr, span, target),
127127
[sym::marker] => self.check_marker(hir_id, attr, span, target),
128128
[sym::target_feature] => {
@@ -369,47 +369,15 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
369369
}
370370
}
371371

372-
/// Checks if a `#[coverage]` is applied directly to a function
373-
fn check_coverage(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
372+
/// Checks that `#[coverage(..)]` is applied to a function or closure.
373+
fn check_coverage(&self, attr: &Attribute, span: Span, target: Target) -> bool {
374374
match target {
375-
// #[coverage] on function is fine
375+
// #[coverage(..)] on function is fine
376376
Target::Fn
377377
| Target::Closure
378378
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
379-
380-
// function prototypes can't be covered
381-
Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
382-
self.tcx.emit_node_span_lint(
383-
UNUSED_ATTRIBUTES,
384-
hir_id,
385-
attr.span,
386-
errors::IgnoredCoverageFnProto,
387-
);
388-
true
389-
}
390-
391-
Target::Mod | Target::ForeignMod | Target::Impl | Target::Trait => {
392-
self.tcx.emit_node_span_lint(
393-
UNUSED_ATTRIBUTES,
394-
hir_id,
395-
attr.span,
396-
errors::IgnoredCoveragePropagate,
397-
);
398-
true
399-
}
400-
401-
Target::Expression | Target::Statement | Target::Arm => {
402-
self.tcx.emit_node_span_lint(
403-
UNUSED_ATTRIBUTES,
404-
hir_id,
405-
attr.span,
406-
errors::IgnoredCoverageFnDefn,
407-
);
408-
true
409-
}
410-
411379
_ => {
412-
self.dcx().emit_err(errors::IgnoredCoverageNotCoverable {
380+
self.dcx().emit_err(errors::CoverageNotFnOrClosure {
413381
attr_span: attr.span,
414382
defn_span: span,
415383
});

compiler/rustc_passes/src/errors.rs

+2-14
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,9 @@ pub struct InlineNotFnOrClosure {
6060
pub defn_span: Span,
6161
}
6262

63-
#[derive(LintDiagnostic)]
64-
#[diag(passes_coverage_ignored_function_prototype)]
65-
pub struct IgnoredCoverageFnProto;
66-
67-
#[derive(LintDiagnostic)]
68-
#[diag(passes_coverage_propagate)]
69-
pub struct IgnoredCoveragePropagate;
70-
71-
#[derive(LintDiagnostic)]
72-
#[diag(passes_coverage_fn_defn)]
73-
pub struct IgnoredCoverageFnDefn;
74-
7563
#[derive(Diagnostic)]
76-
#[diag(passes_coverage_not_coverable, code = E0788)]
77-
pub struct IgnoredCoverageNotCoverable {
64+
#[diag(passes_coverage_not_fn_or_closure, code = E0788)]
65+
pub struct CoverageNotFnOrClosure {
7866
#[primary_span]
7967
pub attr_span: Span,
8068
#[label]

tests/ui/coverage-attr/bad-syntax.rs

+13-26
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,45 @@
11
#![feature(coverage_attribute)]
2+
//@ edition: 2021
23

34
// Tests the error messages produced (or not produced) by various unusual
45
// uses of the `#[coverage(..)]` attribute.
56

6-
// FIXME(#126658): Multiple coverage attributes with the same value are useless,
7-
// and should probably produce a diagnostic.
8-
#[coverage(off)]
7+
#[coverage(off)] //~ ERROR multiple `coverage` attributes
98
#[coverage(off)]
109
fn multiple_consistent() {}
1110

12-
// FIXME(#126658): When there are multiple inconsistent coverage attributes,
13-
// it's unclear which one will prevail.
14-
#[coverage(off)]
11+
#[coverage(off)] //~ ERROR multiple `coverage` attributes
1512
#[coverage(on)]
1613
fn multiple_inconsistent() {}
1714

18-
#[coverage] //~ ERROR expected `coverage(off)` or `coverage(on)`
15+
#[coverage] //~ ERROR malformed `coverage` attribute input
1916
fn bare_word() {}
2017

21-
// FIXME(#126658): This shows as multiple different errors, one of which suggests
22-
// writing bare `#[coverage]`, which is not allowed.
23-
#[coverage = true]
24-
//~^ ERROR expected `coverage(off)` or `coverage(on)`
25-
//~| ERROR malformed `coverage` attribute input
26-
//~| HELP the following are the possible correct uses
27-
//~| SUGGESTION #[coverage(on|off)]
18+
#[coverage = true] //~ ERROR malformed `coverage` attribute input
2819
fn key_value() {}
2920

30-
#[coverage()] //~ ERROR expected `coverage(off)` or `coverage(on)`
21+
#[coverage()] //~ ERROR malformed `coverage` attribute input
3122
fn list_empty() {}
3223

33-
#[coverage(off, off)] //~ ERROR expected `coverage(off)` or `coverage(on)`
24+
#[coverage(off, off)] //~ ERROR malformed `coverage` attribute input
3425
fn list_consistent() {}
3526

36-
#[coverage(off, on)] //~ ERROR expected `coverage(off)` or `coverage(on)`
27+
#[coverage(off, on)] //~ ERROR malformed `coverage` attribute input
3728
fn list_inconsistent() {}
3829

39-
#[coverage(bogus)] //~ ERROR expected `coverage(off)` or `coverage(on)`
30+
#[coverage(bogus)] //~ ERROR malformed `coverage` attribute input
4031
fn bogus_word() {}
4132

42-
#[coverage(bogus, off)] //~ ERROR expected `coverage(off)` or `coverage(on)`
33+
#[coverage(bogus, off)] //~ ERROR malformed `coverage` attribute input
4334
fn bogus_word_before() {}
4435

45-
#[coverage(off, bogus)] //~ ERROR expected `coverage(off)` or `coverage(on)`
36+
#[coverage(off, bogus)] //~ ERROR malformed `coverage` attribute input
4637
fn bogus_word_after() {}
4738

48-
#[coverage(off,)]
39+
#[coverage(off,)] // (OK!)
4940
fn comma_after() {}
5041

51-
// FIXME(#126658): This shows as multiple different errors.
52-
#[coverage(,off)]
53-
//~^ ERROR expected identifier, found `,`
54-
//~| HELP remove this comma
55-
//~| ERROR expected `coverage(off)` or `coverage(on)`
42+
#[coverage(,off)] //~ ERROR expected identifier, found `,`
5643
fn comma_before() {}
5744

5845
fn main() {}

0 commit comments

Comments
 (0)