Skip to content

Commit a23917c

Browse files
committed
Add hard error and migration lint for unsafe attrs
1 parent 33422e7 commit a23917c

24 files changed

+473
-53
lines changed

compiler/rustc_ast_passes/src/ast_validation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ fn validate_generic_param_order(dcx: DiagCtxtHandle<'_>, generics: &[GenericPara
899899

900900
impl<'a> Visitor<'a> for AstValidator<'a> {
901901
fn visit_attribute(&mut self, attr: &Attribute) {
902-
validate_attr::check_attr(&self.session.psess, attr);
902+
validate_attr::check_attr(&self.features, &self.session.psess, attr);
903903
}
904904

905905
fn visit_ty(&mut self, ty: &'a Ty) {

compiler/rustc_expand/src/expand.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1882,7 +1882,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
18821882
let mut span: Option<Span> = None;
18831883
while let Some(attr) = attrs.next() {
18841884
rustc_ast_passes::feature_gate::check_attribute(attr, self.cx.sess, features);
1885-
validate_attr::check_attr(&self.cx.sess.psess, attr);
1885+
validate_attr::check_attr(features, &self.cx.sess.psess, attr);
18861886

18871887
let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span };
18881888
span = Some(current_span);

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
@@ -125,7 +125,7 @@ pub use accepted::ACCEPTED_FEATURES;
125125
pub use builtin_attrs::AttributeDuplicates;
126126
pub use builtin_attrs::{
127127
deprecated_attributes, encode_cross_crate, find_gated_cfg, is_builtin_attr_name,
128-
is_unsafe_attr, is_valid_for_get_attr, AttributeGate, AttributeTemplate, AttributeType,
128+
is_valid_for_get_attr, AttributeGate, AttributeSafety, AttributeTemplate, AttributeType,
129129
BuiltinAttribute, GatedCfg, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
130130
};
131131
pub use removed::REMOVED_FEATURES;

compiler/rustc_lint/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,10 @@ lint_unnameable_test_items = cannot test inner items
825825
lint_unnecessary_qualification = unnecessary qualification
826826
.suggestion = remove the unnecessary path segments
827827
828+
lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
829+
.label = usage of unsafe attribute
830+
lint_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`
831+
828832
lint_unsupported_group = `{$lint_group}` lint group is not supported with ´--force-warn´
829833
830834
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 {
323333
elided,
324334
span: lt_span,

compiler/rustc_lint/src/lints.rs

+21
Original file line numberDiff line numberDiff line change
@@ -2890,3 +2890,24 @@ pub struct RedundantImportVisibility {
28902890
pub import_vis: String,
28912891
pub max_vis: String,
28922892
}
2893+
2894+
#[derive(LintDiagnostic)]
2895+
#[diag(lint_unsafe_attr_outside_unsafe)]
2896+
pub struct UnsafeAttrOutsideUnsafe {
2897+
#[label]
2898+
pub span: Span,
2899+
#[subdiagnostic]
2900+
pub suggestion: UnsafeAttrOutsideUnsafeSuggestion,
2901+
}
2902+
2903+
#[derive(Subdiagnostic)]
2904+
#[multipart_suggestion(
2905+
lint_unsafe_attr_outside_unsafe_suggestion,
2906+
applicability = "machine-applicable"
2907+
)]
2908+
pub struct UnsafeAttrOutsideUnsafeSuggestion {
2909+
#[suggestion_part(code = "unsafe(")]
2910+
pub left: Span,
2911+
#[suggestion_part(code = ")")]
2912+
pub right: Span,
2913+
}

compiler/rustc_lint_defs/src/builtin.rs

+43
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ declare_lint_pass! {
115115
UNNAMEABLE_TYPES,
116116
UNREACHABLE_CODE,
117117
UNREACHABLE_PATTERNS,
118+
UNSAFE_ATTR_OUTSIDE_UNSAFE,
118119
UNSAFE_OP_IN_UNSAFE_FN,
119120
UNSTABLE_NAME_COLLISIONS,
120121
UNSTABLE_SYNTAX_PRE_EXPANSION,
@@ -4902,3 +4903,45 @@ declare_lint! {
49024903
reference: "issue #123743 <https://github.com/rust-lang/rust/issues/123743>",
49034904
};
49044905
}
4906+
4907+
declare_lint! {
4908+
/// The `unsafe_attr_outside_unsafe` lint detects a missing unsafe keyword
4909+
/// on attributes considered unsafe.
4910+
///
4911+
/// ### Example
4912+
///
4913+
/// ```rust
4914+
/// #![feature(unsafe_attributes)]
4915+
/// #![warn(unsafe_attr_outside_unsafe)]
4916+
///
4917+
/// #[no_mangle]
4918+
/// extern "C" fn foo() {}
4919+
///
4920+
/// fn main() {}
4921+
/// ```
4922+
///
4923+
/// {{produces}}
4924+
///
4925+
/// ### Explanation
4926+
///
4927+
/// Some attributes (e.g. `no_mangle`, `export_name`, `link_section` -- see
4928+
/// [issue #82499] for a more complete list) are considered "unsafe" attributes.
4929+
/// An unsafe attribute must only be used inside unsafe(...).
4930+
///
4931+
/// This lint can automatically wrap the attributes in `unsafe(...)` , but this
4932+
/// obviously cannot verify that the preconditions of the `unsafe`
4933+
/// attributes are fulfilled, so that is still up to the user.
4934+
///
4935+
/// The lint is currently "allow" by default, but that might change in the
4936+
/// future.
4937+
///
4938+
/// [editions]: https://doc.rust-lang.org/edition-guide/
4939+
/// [issue #82499]: https://github.com/rust-lang/rust/issues/82499
4940+
pub UNSAFE_ATTR_OUTSIDE_UNSAFE,
4941+
Allow,
4942+
"detects unsafe attributes outside of unsafe",
4943+
@future_incompatible = FutureIncompatibleInfo {
4944+
reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024),
4945+
reference: "issue #123757 <https://github.com/rust-lang/rust/issues/123757>",
4946+
};
4947+
}

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
@@ -366,6 +366,10 @@ parse_inner_doc_comment_not_permitted = expected outer doc comment
366366
.label_does_not_annotate_this = the inner doc comment doesn't annotate this {$item}
367367
.sugg_change_inner_to_outer = to annotate the {$item}, change the doc comment from inner to outer style
368368
369+
parse_invalid_attr_unsafe = `{$name}` is not an unsafe attribute
370+
.suggestion = remove the `unsafe(...)`
371+
.note = extraneous unsafe is not allowed in attributes
372+
369373
parse_invalid_block_macro_segment = cannot use a `block` macro fragment here
370374
.label = the `block` fragment is within this context
371375
.suggestion = wrap this in another block
@@ -866,6 +870,11 @@ parse_unmatched_angle_brackets = {$num_extra_brackets ->
866870
*[other] remove extra angle brackets
867871
}
868872
873+
parse_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
874+
.label = usage of unsafe attribute
875+
parse_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`
876+
877+
869878
parse_unskipped_whitespace = whitespace symbol '{$ch}' is not skipped
870879
.label = {parse_unskipped_whitespace}
871880

compiler/rustc_parse/src/errors.rs

+31
Original file line numberDiff line numberDiff line change
@@ -2997,3 +2997,34 @@ pub(crate) struct DotDotRangeAttribute {
29972997
#[primary_span]
29982998
pub span: Span,
29992999
}
3000+
3001+
#[derive(Diagnostic)]
3002+
#[diag(parse_invalid_attr_unsafe)]
3003+
#[note]
3004+
pub struct InvalidAttrUnsafe {
3005+
#[primary_span]
3006+
pub span: Span,
3007+
pub name: Path,
3008+
}
3009+
3010+
#[derive(Diagnostic)]
3011+
#[diag(parse_unsafe_attr_outside_unsafe)]
3012+
pub struct UnsafeAttrOutsideUnsafe {
3013+
#[primary_span]
3014+
#[label]
3015+
pub span: Span,
3016+
#[subdiagnostic]
3017+
pub suggestion: UnsafeAttrOutsideUnsafeSuggestion,
3018+
}
3019+
3020+
#[derive(Subdiagnostic)]
3021+
#[multipart_suggestion(
3022+
parse_unsafe_attr_outside_unsafe_suggestion,
3023+
applicability = "machine-applicable"
3024+
)]
3025+
pub struct UnsafeAttrOutsideUnsafeSuggestion {
3026+
#[suggestion_part(code = "unsafe(")]
3027+
pub left: Span,
3028+
#[suggestion_part(code = ")")]
3029+
pub right: Span,
3030+
}

compiler/rustc_parse/src/validate_attr.rs

+58-6
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,73 @@ 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::{
11+
AttributeSafety, AttributeTemplate, BuiltinAttribute, Features, BUILTIN_ATTRIBUTE_MAP,
12+
};
1113
use rustc_session::errors::report_lit_error;
12-
use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
14+
use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE};
1315
use rustc_session::lint::BuiltinLintDiag;
1416
use rustc_session::parse::ParseSess;
15-
use rustc_span::{sym, Span, Symbol};
17+
use rustc_span::{sym, BytePos, Span, Symbol};
1618

17-
pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
19+
pub fn check_attr(features: &Features, psess: &ParseSess, attr: &Attribute) {
1820
if attr.is_doc_comment() {
1921
return;
2022
}
2123

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

2476
// Check input tokens for built-in and key-value attributes.
2577
match attr_info {
@@ -32,7 +84,7 @@ pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
3284
}
3385
}
3486
}
35-
_ if let AttrArgs::Eq(..) = attr.get_normal_item().args => {
87+
_ if let AttrArgs::Eq(..) = attr_item.args => {
3688
// All key-value attributes are restricted to meta-item syntax.
3789
match parse_meta(psess, attr) {
3890
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::{Applicability, IntoDiagArg, MultiSpan};
1212
use rustc_errors::{DiagCtxtHandle, StashKey};
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, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee,
1010
Level, 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,

0 commit comments

Comments
 (0)