Skip to content

Commit dbb73f8

Browse files
committed
Auto merge of #73461 - calebzulawski:validate-attribute-placement, r=matthewjasper
Validate built-in attribute placement Closes #54584, closes #47725, closes #54044. I've changed silently ignoring some incorrectly placed attributes to errors. I'm not sure what the policy is since this can theoretically break code (should they be warnings instead? does it warrant a crater run?).
2 parents 9891908 + 82cb379 commit dbb73f8

18 files changed

+1156
-462
lines changed

compiler/rustc_passes/src/check_attr.rs

+183-14
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,26 @@ impl CheckAttrVisitor<'tcx> {
6666
} else if self.tcx.sess.check_name(attr, sym::marker) {
6767
self.check_marker(attr, span, target)
6868
} else if self.tcx.sess.check_name(attr, sym::target_feature) {
69-
self.check_target_feature(attr, span, target)
69+
self.check_target_feature(hir_id, attr, span, target)
7070
} else if self.tcx.sess.check_name(attr, sym::track_caller) {
7171
self.check_track_caller(&attr.span, attrs, span, target)
7272
} else if self.tcx.sess.check_name(attr, sym::doc) {
7373
self.check_doc_alias(attr, hir_id, target)
74+
} else if self.tcx.sess.check_name(attr, sym::no_link) {
75+
self.check_no_link(&attr, span, target)
76+
} else if self.tcx.sess.check_name(attr, sym::export_name) {
77+
self.check_export_name(&attr, span, target)
7478
} else {
79+
// lint-only checks
80+
if self.tcx.sess.check_name(attr, sym::cold) {
81+
self.check_cold(hir_id, attr, span, target);
82+
} else if self.tcx.sess.check_name(attr, sym::link_name) {
83+
self.check_link_name(hir_id, attr, span, target);
84+
} else if self.tcx.sess.check_name(attr, sym::link_section) {
85+
self.check_link_section(hir_id, attr, span, target);
86+
} else if self.tcx.sess.check_name(attr, sym::no_mangle) {
87+
self.check_no_mangle(hir_id, attr, span, target);
88+
}
7589
true
7690
};
7791
}
@@ -109,12 +123,12 @@ impl CheckAttrVisitor<'tcx> {
109123
lint.build("`#[inline]` is ignored on constants")
110124
.warn(
111125
"this was previously accepted by the compiler but is \
112-
being phased out; it will become a hard error in \
113-
a future release!",
126+
being phased out; it will become a hard error in \
127+
a future release!",
114128
)
115129
.note(
116130
"see issue #65833 <https://github.com/rust-lang/rust/issues/65833> \
117-
for more information",
131+
for more information",
118132
)
119133
.emit();
120134
});
@@ -153,7 +167,7 @@ impl CheckAttrVisitor<'tcx> {
153167
.emit();
154168
false
155169
}
156-
Target::Fn | Target::Method(..) | Target::ForeignFn => true,
170+
Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => true,
157171
_ => {
158172
struct_span_err!(
159173
self.tcx.sess,
@@ -202,10 +216,31 @@ impl CheckAttrVisitor<'tcx> {
202216
}
203217

204218
/// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid.
205-
fn check_target_feature(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
219+
fn check_target_feature(
220+
&self,
221+
hir_id: HirId,
222+
attr: &Attribute,
223+
span: &Span,
224+
target: Target,
225+
) -> bool {
206226
match target {
207227
Target::Fn
208228
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
229+
// FIXME: #[target_feature] was previously erroneously allowed on statements and some
230+
// crates used this, so only emit a warning.
231+
Target::Statement => {
232+
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
233+
lint.build("attribute should be applied to a function")
234+
.warn(
235+
"this was previously accepted by the compiler but is \
236+
being phased out; it will become a hard error in \
237+
a future release!",
238+
)
239+
.span_label(*span, "not a function")
240+
.emit();
241+
});
242+
true
243+
}
209244
_ => {
210245
self.tcx
211246
.sess
@@ -277,6 +312,136 @@ impl CheckAttrVisitor<'tcx> {
277312
true
278313
}
279314

315+
/// Checks if `#[cold]` is applied to a non-function. Returns `true` if valid.
316+
fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
317+
match target {
318+
Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => {}
319+
_ => {
320+
// FIXME: #[cold] was previously allowed on non-functions and some crates used
321+
// this, so only emit a warning.
322+
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
323+
lint.build("attribute should be applied to a function")
324+
.warn(
325+
"this was previously accepted by the compiler but is \
326+
being phased out; it will become a hard error in \
327+
a future release!",
328+
)
329+
.span_label(*span, "not a function")
330+
.emit();
331+
});
332+
}
333+
}
334+
}
335+
336+
/// Checks if `#[link_name]` is applied to an item other than a foreign function or static.
337+
fn check_link_name(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
338+
match target {
339+
Target::ForeignFn | Target::ForeignStatic => {}
340+
_ => {
341+
// FIXME: #[cold] was previously allowed on non-functions/statics and some crates
342+
// used this, so only emit a warning.
343+
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
344+
let mut diag =
345+
lint.build("attribute should be applied to a foreign function or static");
346+
diag.warn(
347+
"this was previously accepted by the compiler but is \
348+
being phased out; it will become a hard error in \
349+
a future release!",
350+
);
351+
352+
// See issue #47725
353+
if let Target::ForeignMod = target {
354+
if let Some(value) = attr.value_str() {
355+
diag.span_help(
356+
attr.span,
357+
&format!(r#"try `#[link(name = "{}")]` instead"#, value),
358+
);
359+
} else {
360+
diag.span_help(attr.span, r#"try `#[link(name = "...")]` instead"#);
361+
}
362+
}
363+
364+
diag.span_label(*span, "not a foreign function or static");
365+
diag.emit();
366+
});
367+
}
368+
}
369+
}
370+
371+
/// Checks if `#[no_link]` is applied to an `extern crate`. Returns `true` if valid.
372+
fn check_no_link(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
373+
if target == Target::ExternCrate {
374+
true
375+
} else {
376+
self.tcx
377+
.sess
378+
.struct_span_err(attr.span, "attribute should be applied to an `extern crate` item")
379+
.span_label(*span, "not an `extern crate` item")
380+
.emit();
381+
false
382+
}
383+
}
384+
385+
/// Checks if `#[export_name]` is applied to a function or static. Returns `true` if valid.
386+
fn check_export_name(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
387+
match target {
388+
Target::Static | Target::Fn | Target::Method(..) => true,
389+
_ => {
390+
self.tcx
391+
.sess
392+
.struct_span_err(
393+
attr.span,
394+
"attribute should be applied to a function or static",
395+
)
396+
.span_label(*span, "not a function or static")
397+
.emit();
398+
false
399+
}
400+
}
401+
}
402+
403+
/// Checks if `#[link_section]` is applied to a function or static.
404+
fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
405+
match target {
406+
Target::Static | Target::Fn | Target::Method(..) => {}
407+
_ => {
408+
// FIXME: #[link_section] was previously allowed on non-functions/statics and some
409+
// crates used this, so only emit a warning.
410+
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
411+
lint.build("attribute should be applied to a function or static")
412+
.warn(
413+
"this was previously accepted by the compiler but is \
414+
being phased out; it will become a hard error in \
415+
a future release!",
416+
)
417+
.span_label(*span, "not a function or static")
418+
.emit();
419+
});
420+
}
421+
}
422+
}
423+
424+
/// Checks if `#[no_mangle]` is applied to a function or static.
425+
fn check_no_mangle(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
426+
match target {
427+
Target::Static | Target::Fn | Target::Method(..) => {}
428+
_ => {
429+
// FIXME: #[no_mangle] was previously allowed on non-functions/statics and some
430+
// crates used this, so only emit a warning.
431+
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
432+
lint.build("attribute should be applied to a function or static")
433+
.warn(
434+
"this was previously accepted by the compiler but is \
435+
being phased out; it will become a hard error in \
436+
a future release!",
437+
)
438+
.span_label(*span, "not a function or static")
439+
.emit();
440+
});
441+
}
442+
}
443+
}
444+
280445
/// Checks if the `#[repr]` attributes on `item` are valid.
281446
fn check_repr(
282447
&self,
@@ -321,7 +486,11 @@ impl CheckAttrVisitor<'tcx> {
321486
}
322487
sym::simd => {
323488
is_simd = true;
324-
if target != Target::Struct { ("a", "struct") } else { continue }
489+
if target != Target::Struct {
490+
("a", "struct")
491+
} else {
492+
continue;
493+
}
325494
}
326495
sym::transparent => {
327496
is_transparent = true;
@@ -358,7 +527,11 @@ impl CheckAttrVisitor<'tcx> {
358527
| sym::isize
359528
| sym::usize => {
360529
int_reprs += 1;
361-
if target != Target::Enum { ("an", "enum") } else { continue }
530+
if target != Target::Enum {
531+
("an", "enum")
532+
} else {
533+
continue;
534+
}
362535
}
363536
_ => continue,
364537
};
@@ -421,10 +594,8 @@ impl CheckAttrVisitor<'tcx> {
421594
fn check_stmt_attributes(&self, stmt: &hir::Stmt<'_>) {
422595
// When checking statements ignore expressions, they will be checked later
423596
if let hir::StmtKind::Local(ref l) = stmt.kind {
597+
self.check_attributes(l.hir_id, &l.attrs, &stmt.span, Target::Statement, None);
424598
for attr in l.attrs.iter() {
425-
if self.tcx.sess.check_name(attr, sym::inline) {
426-
self.check_inline(l.hir_id, attr, &stmt.span, Target::Statement);
427-
}
428599
if self.tcx.sess.check_name(attr, sym::repr) {
429600
self.emit_repr_error(
430601
attr.span,
@@ -442,10 +613,8 @@ impl CheckAttrVisitor<'tcx> {
442613
hir::ExprKind::Closure(..) => Target::Closure,
443614
_ => Target::Expression,
444615
};
616+
self.check_attributes(expr.hir_id, &expr.attrs, &expr.span, target, None);
445617
for attr in expr.attrs.iter() {
446-
if self.tcx.sess.check_name(attr, sym::inline) {
447-
self.check_inline(expr.hir_id, attr, &expr.span, target);
448-
}
449618
if self.tcx.sess.check_name(attr, sym::repr) {
450619
self.emit_repr_error(
451620
attr.span,

compiler/rustc_typeck/src/collect.rs

+11-19
Original file line numberDiff line numberDiff line change
@@ -2525,10 +2525,17 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
25252525
codegen_fn_attrs.export_name = Some(s);
25262526
}
25272527
} else if tcx.sess.check_name(attr, sym::target_feature) {
2528-
if !tcx.features().target_feature_11 {
2529-
check_target_feature_safe_fn(tcx, id, attr.span);
2530-
} else if let Some(local_id) = id.as_local() {
2531-
if tcx.fn_sig(id).unsafety() == hir::Unsafety::Normal {
2528+
if !tcx.is_closure(id) && tcx.fn_sig(id).unsafety() == hir::Unsafety::Normal {
2529+
if !tcx.features().target_feature_11 {
2530+
let mut err = feature_err(
2531+
&tcx.sess.parse_sess,
2532+
sym::target_feature_11,
2533+
attr.span,
2534+
"`#[target_feature(..)]` can only be applied to `unsafe` functions",
2535+
);
2536+
err.span_label(tcx.def_span(id), "not an `unsafe` function");
2537+
err.emit();
2538+
} else if let Some(local_id) = id.as_local() {
25322539
check_target_feature_trait_unsafe(tcx, local_id, attr.span);
25332540
}
25342541
}
@@ -2785,21 +2792,6 @@ fn check_link_name_xor_ordinal(
27852792
}
27862793
}
27872794

2788-
/// Checks the function annotated with `#[target_feature]` is unsafe,
2789-
/// reporting an error if it isn't.
2790-
fn check_target_feature_safe_fn(tcx: TyCtxt<'_>, id: DefId, attr_span: Span) {
2791-
if tcx.is_closure(id) || tcx.fn_sig(id).unsafety() == hir::Unsafety::Normal {
2792-
let mut err = feature_err(
2793-
&tcx.sess.parse_sess,
2794-
sym::target_feature_11,
2795-
attr_span,
2796-
"`#[target_feature(..)]` can only be applied to `unsafe` functions",
2797-
);
2798-
err.span_label(tcx.def_span(id), "not an `unsafe` function");
2799-
err.emit();
2800-
}
2801-
}
2802-
28032795
/// Checks the function annotated with `#[target_feature]` is not a safe
28042796
/// trait method implementation, reporting an error if it is.
28052797
fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, attr_span: Span) {

src/test/ui/check-static-recursion-foreign.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// run-pass
22

3-
#![allow(dead_code)]
43
// Static recursion check shouldn't fail when given a foreign item (#18279)
54

65
// aux-build:check_static_recursion_foreign_helper.rs
@@ -15,12 +14,10 @@ extern crate libc;
1514

1615
use libc::c_int;
1716

18-
#[link_name = "check_static_recursion_foreign_helper"]
1917
extern "C" {
20-
#[allow(dead_code)]
2118
static test_static: c_int;
2219
}
2320

24-
static B: &'static c_int = unsafe { &test_static };
21+
pub static B: &'static c_int = unsafe { &test_static };
2522

2623
pub fn main() {}

0 commit comments

Comments
 (0)