Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 643bc06

Browse files
committedJun 8, 2024
Add hard error and migration lint for unsafe attrs
1 parent 8fb1930 commit 643bc06

21 files changed

+456
-44
lines changed
 

‎compiler/rustc_feature/src/builtin_attrs.rs

-4
Original file line numberDiff line numberDiff line change
@@ -1145,10 +1145,6 @@ pub fn is_valid_for_get_attr(name: Symbol) -> bool {
11451145
})
11461146
}
11471147

1148-
pub fn is_unsafe_attr(name: Symbol) -> bool {
1149-
BUILTIN_ATTRIBUTE_MAP.get(&name).is_some_and(|attr| attr.safety == AttributeSafety::Unsafe)
1150-
}
1151-
11521148
pub static BUILTIN_ATTRIBUTE_MAP: LazyLock<FxHashMap<Symbol, &BuiltinAttribute>> =
11531149
LazyLock::new(|| {
11541150
let mut map = FxHashMap::default();

‎compiler/rustc_feature/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ pub use accepted::ACCEPTED_FEATURES;
123123
pub use builtin_attrs::AttributeDuplicates;
124124
pub use builtin_attrs::{
125125
deprecated_attributes, encode_cross_crate, find_gated_cfg, is_builtin_attr_name,
126-
is_unsafe_attr, is_valid_for_get_attr, AttributeGate, AttributeTemplate, AttributeType,
126+
is_valid_for_get_attr, AttributeGate, AttributeSafety, AttributeTemplate, AttributeType,
127127
BuiltinAttribute, GatedCfg, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
128128
};
129129
pub use removed::REMOVED_FEATURES;

‎compiler/rustc_lint/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,10 @@ lint_unnameable_test_items = cannot test inner items
821821
lint_unnecessary_qualification = unnecessary qualification
822822
.suggestion = remove the unnecessary path segments
823823
824+
lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
825+
.label = usage of unsafe attribute
826+
lint_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`
827+
824828
lint_unsupported_group = `{$lint_group}` lint group is not supported with ´--force-warn´
825829
826830
lint_untranslatable_diag = diagnostics should be created using translatable messages

‎compiler/rustc_lint/src/context/diagnostics.rs

+10
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,16 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: &
319319
BuiltinLintDiag::UnusedQualifications { removal_span } => {
320320
lints::UnusedQualifications { removal_span }.decorate_lint(diag);
321321
}
322+
BuiltinLintDiag::UnsafeAttrOutsideUnsafe {
323+
attribute_name_span,
324+
sugg_spans: (left, right),
325+
} => {
326+
lints::UnsafeAttrOutsideUnsafe {
327+
span: attribute_name_span,
328+
suggestion: lints::UnsafeAttrOutsideUnsafeSuggestion { left, right },
329+
}
330+
.decorate_lint(diag);
331+
}
322332
BuiltinLintDiag::AssociatedConstElidedLifetime { elided, span: lt_span } => {
323333
let lt_span = if elided { lt_span.shrink_to_hi() } else { lt_span };
324334
let code = if elided { "'static " } else { "'static" };

‎compiler/rustc_lint/src/lints.rs

+21
Original file line numberDiff line numberDiff line change
@@ -2874,3 +2874,24 @@ pub struct RedundantImportVisibility {
28742874
pub import_vis: String,
28752875
pub max_vis: String,
28762876
}
2877+
2878+
#[derive(LintDiagnostic)]
2879+
#[diag(lint_unsafe_attr_outside_unsafe)]
2880+
pub struct UnsafeAttrOutsideUnsafe {
2881+
#[label]
2882+
pub span: Span,
2883+
#[subdiagnostic]
2884+
pub suggestion: UnsafeAttrOutsideUnsafeSuggestion,
2885+
}
2886+
2887+
#[derive(Subdiagnostic)]
2888+
#[multipart_suggestion(
2889+
lint_unsafe_attr_outside_unsafe_suggestion,
2890+
applicability = "machine-applicable"
2891+
)]
2892+
pub struct UnsafeAttrOutsideUnsafeSuggestion {
2893+
#[suggestion_part(code = "unsafe(")]
2894+
pub left: Span,
2895+
#[suggestion_part(code = ")")]
2896+
pub right: Span,
2897+
}

‎compiler/rustc_lint_defs/src/builtin.rs

+41
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ declare_lint_pass! {
114114
UNNAMEABLE_TYPES,
115115
UNREACHABLE_CODE,
116116
UNREACHABLE_PATTERNS,
117+
UNSAFE_ATTR_OUTSIDE_UNSAFE,
117118
UNSAFE_OP_IN_UNSAFE_FN,
118119
UNSTABLE_NAME_COLLISIONS,
119120
UNSTABLE_SYNTAX_PRE_EXPANSION,
@@ -4841,3 +4842,43 @@ declare_lint! {
48414842
reference: "issue #123743 <https://github.com/rust-lang/rust/issues/123743>",
48424843
};
48434844
}
4845+
4846+
declare_lint! {
4847+
/// The `unsafe_attr_outside_unsafe` lint detects a missing unsafe keyword
4848+
/// on attributes considered unsafe.
4849+
///
4850+
/// ### Example
4851+
///
4852+
/// ```rust
4853+
/// #![feature(unsafe_attributes)]
4854+
/// #![warn(unsafe_attr_outside_unsafe)]
4855+
/// #![allow(dead_code)]
4856+
///
4857+
/// #[unsafe(no_mangle)]
4858+
/// extern "C" fn foo() {}
4859+
///
4860+
/// fn main() {}
4861+
/// ```
4862+
///
4863+
/// {{produces}}
4864+
///
4865+
/// ### Explanation
4866+
///
4867+
/// Some attributes (e.g. no_mangle, export_name, link_section -- see here
4868+
/// for a more complete list)are considered "unsafe" attributes. An unsafe
4869+
/// attribute must only be used inside unsafe(...) in the attribute.
4870+
///
4871+
/// This lint can automatically wrap the attributes in `unsafe(...)` , but this
4872+
/// obviously cannot verify that the preconditions of the `unsafe`
4873+
/// attributes are fulfilled, so that is still up to the user.
4874+
///
4875+
/// The lint is currently "allow" by default, but that might change in the
4876+
/// future.
4877+
pub UNSAFE_ATTR_OUTSIDE_UNSAFE,
4878+
Allow,
4879+
"detects unsafe attributes outside of unsafe",
4880+
@future_incompatible = FutureIncompatibleInfo {
4881+
reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024),
4882+
reference: "issue #123757 <https://github.com/rust-lang/rust/issues/123757>",
4883+
};
4884+
}

‎compiler/rustc_lint_defs/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,10 @@ pub enum BuiltinLintDiag {
691691
/// The span of the unnecessarily-qualified path to remove.
692692
removal_span: Span,
693693
},
694+
UnsafeAttrOutsideUnsafe {
695+
attribute_name_span: Span,
696+
sugg_spans: (Span, Span),
697+
},
694698
AssociatedConstElidedLifetime {
695699
elided: bool,
696700
span: Span,

‎compiler/rustc_parse/messages.ftl

+9
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,10 @@ parse_inner_doc_comment_not_permitted = expected outer doc comment
364364
.label_does_not_annotate_this = the inner doc comment doesn't annotate this {$item}
365365
.sugg_change_inner_to_outer = to annotate the {$item}, change the doc comment from inner to outer style
366366
367+
parse_invalid_attr_unsafe = `{$name}` is not an unsafe attribute
368+
.suggestion = remove the `unsafe(...)`
369+
.note = extraneous unsafe is not allowed in attributes
370+
367371
parse_invalid_block_macro_segment = cannot use a `block` macro fragment here
368372
.label = the `block` fragment is within this context
369373
.suggestion = wrap this in another block
@@ -864,6 +868,11 @@ parse_unmatched_angle_brackets = {$num_extra_brackets ->
864868
*[other] remove extra angle brackets
865869
}
866870
871+
parse_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
872+
.label = usage of unsafe attribute
873+
parse_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`
874+
875+
867876
parse_unskipped_whitespace = whitespace symbol '{$ch}' is not skipped
868877
.label = {parse_unskipped_whitespace}
869878

‎compiler/rustc_parse/src/errors.rs

+31
Original file line numberDiff line numberDiff line change
@@ -2989,3 +2989,34 @@ pub(crate) struct ExprRArrowCall {
29892989
#[suggestion(style = "short", applicability = "machine-applicable", code = ".")]
29902990
pub span: Span,
29912991
}
2992+
2993+
#[derive(Diagnostic)]
2994+
#[diag(parse_invalid_attr_unsafe)]
2995+
#[note]
2996+
pub struct InvalidAttrUnsafe {
2997+
#[primary_span]
2998+
pub span: Span,
2999+
pub name: Path,
3000+
}
3001+
3002+
#[derive(Diagnostic)]
3003+
#[diag(parse_unsafe_attr_outside_unsafe)]
3004+
pub struct UnsafeAttrOutsideUnsafe {
3005+
#[primary_span]
3006+
#[label]
3007+
pub span: Span,
3008+
#[subdiagnostic]
3009+
pub suggestion: UnsafeAttrOutsideUnsafeSuggestion,
3010+
}
3011+
3012+
#[derive(Subdiagnostic)]
3013+
#[multipart_suggestion(
3014+
parse_unsafe_attr_outside_unsafe_suggestion,
3015+
applicability = "machine-applicable"
3016+
)]
3017+
pub struct UnsafeAttrOutsideUnsafeSuggestion {
3018+
#[suggestion_part(code = "unsafe(")]
3019+
pub left: Span,
3020+
#[suggestion_part(code = ")")]
3021+
pub right: Span,
3022+
}

‎compiler/rustc_parse/src/validate_attr.rs

+52-5
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,68 @@ use crate::{errors, parse_in};
55
use rustc_ast::token::Delimiter;
66
use rustc_ast::tokenstream::DelimSpan;
77
use rustc_ast::MetaItemKind;
8-
use rustc_ast::{self as ast, AttrArgs, AttrArgsEq, Attribute, DelimArgs, MetaItem};
8+
use rustc_ast::{self as ast, AttrArgs, AttrArgsEq, Attribute, DelimArgs, MetaItem, Safety};
99
use rustc_errors::{Applicability, FatalError, PResult};
10-
use rustc_feature::{AttributeTemplate, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
10+
use rustc_feature::{AttributeSafety, AttributeTemplate, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
1111
use rustc_session::errors::report_lit_error;
12-
use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
12+
use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE};
1313
use rustc_session::lint::BuiltinLintDiag;
1414
use rustc_session::parse::ParseSess;
15-
use rustc_span::{sym, Span, Symbol};
15+
use rustc_span::{sym, BytePos, Span, Symbol};
1616

1717
pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
1818
if attr.is_doc_comment() {
1919
return;
2020
}
2121

2222
let attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
23+
let attr_item = attr.get_normal_item();
24+
25+
let is_unsafe_attr =
26+
attr_info.map(|attr| attr.safety == AttributeSafety::Unsafe).unwrap_or(false);
27+
28+
if is_unsafe_attr {
29+
if let ast::Safety::Default = attr_item.unsafety {
30+
let path_span = attr_item.path.span;
31+
32+
// If the `attr_item`'s span is not from a macro, then just suggest
33+
// wrapping it in `unsafe(...)`. Otherwise, we suggest putting the
34+
// `unsafe(`, `)` right after and right before the opening and closing
35+
// square bracket respectively.
36+
let diag_span = if attr_item.span().can_be_used_for_suggestions() {
37+
attr_item.span()
38+
} else {
39+
attr.span.with_lo(attr.span.lo() + BytePos(2)).with_hi(attr.span.hi() - BytePos(1))
40+
};
41+
42+
if attr.span.at_least_rust_2024() {
43+
psess.dcx.emit_err(errors::UnsafeAttrOutsideUnsafe {
44+
span: path_span,
45+
suggestion: errors::UnsafeAttrOutsideUnsafeSuggestion {
46+
left: diag_span.shrink_to_lo(),
47+
right: diag_span.shrink_to_hi(),
48+
},
49+
});
50+
} else {
51+
psess.buffer_lint(
52+
UNSAFE_ATTR_OUTSIDE_UNSAFE,
53+
path_span,
54+
ast::CRATE_NODE_ID,
55+
BuiltinLintDiag::UnsafeAttrOutsideUnsafe {
56+
attribute_name_span: path_span,
57+
sugg_spans: (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()),
58+
},
59+
);
60+
}
61+
}
62+
} else {
63+
if let Safety::Unsafe(unsafe_span) = attr_item.unsafety {
64+
psess.dcx.emit_err(errors::InvalidAttrUnsafe {
65+
span: unsafe_span,
66+
name: attr_item.path.clone(),
67+
});
68+
}
69+
}
2370

2471
// Check input tokens for built-in and key-value attributes.
2572
match attr_info {
@@ -32,7 +79,7 @@ pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
3279
}
3380
}
3481
}
35-
_ if let AttrArgs::Eq(..) = attr.get_normal_item().args => {
82+
_ if let AttrArgs::Eq(..) = attr_item.args => {
3683
// All key-value attributes are restricted to meta-item syntax.
3784
match parse_meta(psess, attr) {
3885
Ok(_) => {}

‎compiler/rustc_passes/messages.ftl

-4
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,6 @@ passes_invalid_attr_at_crate_level =
384384
passes_invalid_attr_at_crate_level_item =
385385
the inner attribute doesn't annotate this {$kind}
386386
387-
passes_invalid_attr_unsafe = `{$name}` is not an unsafe attribute
388-
.suggestion = remove the `unsafe(...)`
389-
.note = extraneous unsafe is not allowed in attributes
390-
391387
passes_invalid_macro_export_arguments = `{$name}` isn't a valid `#[macro_export]` argument
392388
393389
passes_invalid_macro_export_arguments_too_many_items = `#[macro_export]` can only take 1 or 0 arguments

‎compiler/rustc_passes/src/check_attr.rs

+1-20
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ use rustc_ast::{MetaItemKind, MetaItemLit, NestedMetaItem};
1010
use rustc_data_structures::fx::FxHashMap;
1111
use rustc_errors::StashKey;
1212
use rustc_errors::{Applicability, DiagCtxt, IntoDiagArg, MultiSpan};
13-
use rustc_feature::{
14-
is_unsafe_attr, AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP,
15-
};
13+
use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
1614
use rustc_hir::def_id::LocalModDefId;
1715
use rustc_hir::intravisit::{self, Visitor};
1816
use rustc_hir::{self as hir};
@@ -116,8 +114,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
116114
let mut seen = FxHashMap::default();
117115
let attrs = self.tcx.hir().attrs(hir_id);
118116
for attr in attrs {
119-
self.check_unsafe_attr(attr);
120-
121117
match attr.path().as_slice() {
122118
[sym::diagnostic, sym::do_not_recommend] => {
123119
self.check_do_not_recommend(attr.span, hir_id, target)
@@ -312,21 +308,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
312308
true
313309
}
314310

315-
/// Checks if `unsafe()` is applied to an invalid attribute.
316-
fn check_unsafe_attr(&self, attr: &Attribute) {
317-
if !attr.is_doc_comment() {
318-
let attr_item = attr.get_normal_item();
319-
if let ast::Safety::Unsafe(unsafe_span) = attr_item.unsafety {
320-
if !is_unsafe_attr(attr.name_or_empty()) {
321-
self.dcx().emit_err(errors::InvalidAttrUnsafe {
322-
span: unsafe_span,
323-
name: attr_item.path.clone(),
324-
});
325-
}
326-
}
327-
}
328-
}
329-
330311
/// Checks if `#[diagnostic::on_unimplemented]` is applied to a trait definition
331312
fn check_diagnostic_on_unimplemented(
332313
&self,

‎compiler/rustc_passes/src/errors.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
};
55

66
use crate::fluent_generated as fluent;
7-
use rustc_ast::{ast, Label};
7+
use rustc_ast::Label;
88
use rustc_errors::{
99
codes::*, Applicability, Diag, DiagCtxt, DiagSymbolList, Diagnostic, EmissionGuarantee, Level,
1010
MultiSpan, SubdiagMessageOp, Subdiagnostic,
@@ -863,15 +863,6 @@ pub struct InvalidAttrAtCrateLevel {
863863
pub item: Option<ItemFollowingInnerAttr>,
864864
}
865865

866-
#[derive(Diagnostic)]
867-
#[diag(passes_invalid_attr_unsafe)]
868-
#[note]
869-
pub struct InvalidAttrUnsafe {
870-
#[primary_span]
871-
pub span: Span,
872-
pub name: ast::Path,
873-
}
874-
875866
#[derive(Clone, Copy)]
876867
pub struct ItemFollowingInnerAttr {
877868
pub span: Span,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![deny(rust_2024_compatibility)]
2+
3+
#[no_mangle]
4+
//~^ ERROR: unsafe attribute used without unsafe
5+
//~| WARN this is accepted in the current edition
6+
extern "C" fn foo() {}
7+
8+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error: unsafe attribute used without unsafe
2+
--> $DIR/in_2024_compatibility.rs:3:3
3+
|
4+
LL | #[no_mangle]
5+
| ^^^^^^^^^ usage of unsafe attribute
6+
|
7+
= warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024!
8+
= note: for more information, see issue #123757 <https://github.com/rust-lang/rust/issues/123757>
9+
note: the lint level is defined here
10+
--> $DIR/in_2024_compatibility.rs:1:9
11+
|
12+
LL | #![deny(rust_2024_compatibility)]
13+
| ^^^^^^^^^^^^^^^^^^^^^^^
14+
= note: `#[deny(unsafe_attr_outside_unsafe)]` implied by `#[deny(rust_2024_compatibility)]`
15+
help: wrap the attribute in `unsafe(...)`
16+
|
17+
LL | #[unsafe(no_mangle)]
18+
| +++++++ +
19+
20+
error: aborting due to 1 previous error
21+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ revisions: edition2021 edition2024
2+
//@[edition2021] edition:2021
3+
//@[edition2024] edition:2024
4+
//@[edition2024] compile-flags: -Zunstable-options
5+
//@ check-pass
6+
7+
#![feature(unsafe_attributes)]
8+
9+
#[unsafe(no_mangle)]
10+
extern "C" fn foo() {}
11+
12+
fn main() {}
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.