Add hard error and migration lint for unsafe attrs · rust-lang/rust@a23917c

24 files changed

lines changed

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) {

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);

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();

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;

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

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,

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+

}

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+

}

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,

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