about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_builtin_macros/messages.ftl3
-rw-r--r--compiler/rustc_builtin_macros/src/cfg.rs3
-rw-r--r--compiler/rustc_builtin_macros/src/cfg_accessible.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/derive.rs15
-rw-r--r--compiler/rustc_builtin_macros/src/errors.rs7
-rw-r--r--compiler/rustc_builtin_macros/src/util.rs2
-rw-r--r--compiler/rustc_expand/src/config.rs18
-rw-r--r--compiler/rustc_interface/src/interface.rs5
-rw-r--r--compiler/rustc_parse/messages.ftl1
-rw-r--r--compiler/rustc_parse/src/errors.rs1
-rw-r--r--compiler/rustc_parse/src/parser/attr.rs21
-rw-r--r--compiler/rustc_parse/src/validate_attr.rs148
-rw-r--r--tests/ui/attributes/unsafe/derive-unsafe-attributes.rs11
-rw-r--r--tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr61
-rw-r--r--tests/ui/attributes/unsafe/double-unsafe-attributes.stderr2
-rw-r--r--tests/ui/attributes/unsafe/extraneous-unsafe-attributes.rs31
-rw-r--r--tests/ui/attributes/unsafe/extraneous-unsafe-attributes.stderr66
-rw-r--r--tests/ui/attributes/unsafe/proc-unsafe-attributes.rs37
-rw-r--r--tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr119
-rw-r--r--tests/ui/attributes/unsafe/unsafe-attributes.rs6
-rw-r--r--tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr2
-rw-r--r--tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr2
22 files changed, 471 insertions, 92 deletions
diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl
index a30ab236213..ae3cce40f5d 100644
--- a/compiler/rustc_builtin_macros/messages.ftl
+++ b/compiler/rustc_builtin_macros/messages.ftl
@@ -115,9 +115,6 @@ builtin_macros_derive_path_args_list = traits in `#[derive(...)]` don't accept a
 builtin_macros_derive_path_args_value = traits in `#[derive(...)]` don't accept values
     .suggestion = remove the value
 
-builtin_macros_derive_unsafe_path = traits in `#[derive(...)]` don't accept `unsafe(...)`
-    .suggestion = remove the `unsafe(...)`
-
 builtin_macros_env_not_defined = environment variable `{$var}` not defined at compile time
     .cargo = Cargo sets build script variables at run time. Use `std::env::var({$var_expr})` instead
     .custom = use `std::env::var({$var_expr})` to read the variable at run time
diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs
index ceb62230ece..de198115fa0 100644
--- a/compiler/rustc_builtin_macros/src/cfg.rs
+++ b/compiler/rustc_builtin_macros/src/cfg.rs
@@ -6,6 +6,7 @@ use rustc_ast::token;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_errors::PResult;
 use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
+use rustc_parse::parser::attr::AllowLeadingUnsafe;
 use rustc_span::Span;
 use {rustc_ast as ast, rustc_attr as attr};
 
@@ -42,7 +43,7 @@ fn parse_cfg<'a>(cx: &ExtCtxt<'a>, span: Span, tts: TokenStream) -> PResult<'a,
         return Err(cx.dcx().create_err(errors::RequiresCfgPattern { span }));
     }
 
-    let cfg = p.parse_meta_item()?;
+    let cfg = p.parse_meta_item(AllowLeadingUnsafe::Yes)?;
 
     let _ = p.eat(&token::Comma);
 
diff --git a/compiler/rustc_builtin_macros/src/cfg_accessible.rs b/compiler/rustc_builtin_macros/src/cfg_accessible.rs
index 2d4a93776bb..006b6aa823f 100644
--- a/compiler/rustc_builtin_macros/src/cfg_accessible.rs
+++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs
@@ -47,11 +47,13 @@ impl MultiItemModifier for Expander {
     ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
         let template = AttributeTemplate { list: Some("path"), ..Default::default() };
         validate_attr::check_builtin_meta_item(
+            &ecx.ecfg.features,
             &ecx.sess.psess,
             meta_item,
             ast::AttrStyle::Outer,
             sym::cfg_accessible,
             template,
+            true,
         );
 
         let Some(path) = validate_input(ecx, meta_item) else {
diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs
index 03970a48638..57bddf0ab60 100644
--- a/compiler/rustc_builtin_macros/src/derive.rs
+++ b/compiler/rustc_builtin_macros/src/derive.rs
@@ -1,5 +1,5 @@
 use rustc_ast as ast;
-use rustc_ast::{GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, Safety, StmtKind};
+use rustc_ast::{GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
 use rustc_expand::base::{
     Annotatable, DeriveResolution, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier,
 };
@@ -38,11 +38,13 @@ impl MultiItemModifier for Expander {
                 let template =
                     AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
                 validate_attr::check_builtin_meta_item(
+                    features,
                     &sess.psess,
                     meta_item,
                     ast::AttrStyle::Outer,
                     sym::derive,
                     template,
+                    true,
                 );
 
                 let mut resolutions = match &meta_item.kind {
@@ -60,7 +62,6 @@ impl MultiItemModifier for Expander {
                                 // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the
                                 // paths.
                                 report_path_args(sess, meta);
-                                report_unsafe_args(sess, meta);
                                 meta.path.clone()
                             })
                             .map(|path| DeriveResolution {
@@ -160,13 +161,3 @@ fn report_path_args(sess: &Session, meta: &ast::MetaItem) {
         }
     }
 }
-
-fn report_unsafe_args(sess: &Session, meta: &ast::MetaItem) {
-    match meta.unsafety {
-        Safety::Unsafe(span) => {
-            sess.dcx().emit_err(errors::DeriveUnsafePath { span });
-        }
-        Safety::Default => {}
-        Safety::Safe(_) => unreachable!(),
-    }
-}
diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs
index 0a4f07709c7..93fc9bcb9d2 100644
--- a/compiler/rustc_builtin_macros/src/errors.rs
+++ b/compiler/rustc_builtin_macros/src/errors.rs
@@ -298,13 +298,6 @@ pub(crate) struct DerivePathArgsValue {
 }
 
 #[derive(Diagnostic)]
-#[diag(builtin_macros_derive_unsafe_path)]
-pub(crate) struct DeriveUnsafePath {
-    #[primary_span]
-    pub(crate) span: Span,
-}
-
-#[derive(Diagnostic)]
 #[diag(builtin_macros_no_default_variant)]
 #[help]
 pub(crate) struct NoDefaultVariant {
diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs
index 65b50736c55..73cc8ff547d 100644
--- a/compiler/rustc_builtin_macros/src/util.rs
+++ b/compiler/rustc_builtin_macros/src/util.rs
@@ -17,11 +17,13 @@ pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaI
     // All the built-in macro attributes are "words" at the moment.
     let template = AttributeTemplate { word: true, ..Default::default() };
     validate_attr::check_builtin_meta_item(
+        &ecx.ecfg.features,
         &ecx.sess.psess,
         meta_item,
         AttrStyle::Outer,
         name,
         template,
+        true,
     );
 }
 
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 756290be0a8..f6bf9f5e89f 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -8,7 +8,9 @@ use rustc_ast::tokenstream::{
 use rustc_ast::{self as ast, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem, NodeId};
 use rustc_attr as attr;
 use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
-use rustc_feature::{Features, ACCEPTED_FEATURES, REMOVED_FEATURES, UNSTABLE_FEATURES};
+use rustc_feature::{
+    AttributeSafety, Features, ACCEPTED_FEATURES, REMOVED_FEATURES, UNSTABLE_FEATURES,
+};
 use rustc_lint_defs::BuiltinLintDiag;
 use rustc_parse::validate_attr;
 use rustc_session::parse::feature_err;
@@ -263,6 +265,13 @@ impl<'a> StripUnconfigured<'a> {
     /// is in the original source file. Gives a compiler error if the syntax of
     /// the attribute is incorrect.
     pub(crate) fn expand_cfg_attr(&self, cfg_attr: &Attribute, recursive: bool) -> Vec<Attribute> {
+        validate_attr::check_attribute_safety(
+            self.features.unwrap_or(&Features::default()),
+            &self.sess.psess,
+            AttributeSafety::Normal,
+            &cfg_attr,
+        );
+
         let Some((cfg_predicate, expanded_attrs)) =
             rustc_parse::parse_cfg_attr(cfg_attr, &self.sess.psess)
         else {
@@ -385,6 +394,13 @@ impl<'a> StripUnconfigured<'a> {
                 return (true, None);
             }
         };
+
+        validate_attr::deny_builtin_meta_unsafety(
+            self.features.unwrap_or(&Features::default()),
+            &self.sess.psess,
+            &meta_item,
+        );
+
         (
             parse_cfg(&meta_item, self.sess).map_or(true, |meta_item| {
                 attr::cfg_matches(meta_item, &self.sess, self.lint_node_id, self.features)
diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs
index 886b043af24..04e2b7d45dc 100644
--- a/compiler/rustc_interface/src/interface.rs
+++ b/compiler/rustc_interface/src/interface.rs
@@ -15,6 +15,7 @@ use rustc_middle::ty;
 use rustc_middle::ty::CurrentGcx;
 use rustc_middle::util::Providers;
 use rustc_parse::new_parser_from_source_str;
+use rustc_parse::parser::attr::AllowLeadingUnsafe;
 use rustc_query_impl::QueryCtxt;
 use rustc_query_system::query::print_query_stack;
 use rustc_session::config::{self, Cfg, CheckCfg, ExpectedValues, Input, OutFileName};
@@ -67,7 +68,7 @@ pub(crate) fn parse_cfg(dcx: DiagCtxtHandle<'_>, cfgs: Vec<String>) -> Cfg {
             }
 
             match new_parser_from_source_str(&psess, filename, s.to_string()) {
-                Ok(mut parser) => match parser.parse_meta_item() {
+                Ok(mut parser) => match parser.parse_meta_item(AllowLeadingUnsafe::No) {
                     Ok(meta_item) if parser.token == token::Eof => {
                         if meta_item.path.segments.len() != 1 {
                             error!("argument key must be an identifier");
@@ -173,7 +174,7 @@ pub(crate) fn parse_check_cfg(dcx: DiagCtxtHandle<'_>, specs: Vec<String>) -> Ch
             }
         };
 
-        let meta_item = match parser.parse_meta_item() {
+        let meta_item = match parser.parse_meta_item(AllowLeadingUnsafe::Yes) {
             Ok(meta_item) if parser.token == token::Eof => meta_item,
             Ok(..) => expected_error(),
             Err(err) => {
diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl
index 391a5791776..e13306b9d9f 100644
--- a/compiler/rustc_parse/messages.ftl
+++ b/compiler/rustc_parse/messages.ftl
@@ -365,6 +365,7 @@ parse_inner_doc_comment_not_permitted = expected outer doc comment
     .sugg_change_inner_to_outer = to annotate the {$item}, change the doc comment from inner to outer style
 
 parse_invalid_attr_unsafe = `{$name}` is not an unsafe attribute
+    .label = this is not an unsafe attribute
     .suggestion = remove the `unsafe(...)`
     .note = extraneous unsafe is not allowed in attributes
 
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index c5e7e0df631..99f2f26b8cb 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -3183,6 +3183,7 @@ pub(crate) struct DotDotRangeAttribute {
 #[note]
 pub struct InvalidAttrUnsafe {
     #[primary_span]
+    #[label]
     pub span: Span,
     pub name: Path,
 }
diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs
index 12b9414d1f7..f34ef071e21 100644
--- a/compiler/rustc_parse/src/parser/attr.rs
+++ b/compiler/rustc_parse/src/parser/attr.rs
@@ -31,6 +31,12 @@ enum OuterAttributeType {
     Attribute,
 }
 
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum AllowLeadingUnsafe {
+    Yes,
+    No,
+}
+
 impl<'a> Parser<'a> {
     /// Parses attributes that appear before an item.
     pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, AttrWrapper> {
@@ -332,7 +338,7 @@ impl<'a> Parser<'a> {
 
     /// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
     pub fn parse_cfg_attr(&mut self) -> PResult<'a, (ast::MetaItem, Vec<(ast::AttrItem, Span)>)> {
-        let cfg_predicate = self.parse_meta_item()?;
+        let cfg_predicate = self.parse_meta_item(AllowLeadingUnsafe::No)?;
         self.expect(&token::Comma)?;
 
         // Presumably, the majority of the time there will only be one attr.
@@ -368,7 +374,10 @@ impl<'a> Parser<'a> {
     /// MetaItem = SimplePath ( '=' UNSUFFIXED_LIT | '(' MetaSeq? ')' )? ;
     /// MetaSeq = MetaItemInner (',' MetaItemInner)* ','? ;
     /// ```
-    pub fn parse_meta_item(&mut self) -> PResult<'a, ast::MetaItem> {
+    pub fn parse_meta_item(
+        &mut self,
+        unsafe_allowed: AllowLeadingUnsafe,
+    ) -> PResult<'a, ast::MetaItem> {
         // We can't use `maybe_whole` here because it would bump in the `None`
         // case, which we don't want.
         if let token::Interpolated(nt) = &self.token.kind
@@ -384,7 +393,11 @@ impl<'a> Parser<'a> {
         }
 
         let lo = self.token.span;
-        let is_unsafe = self.eat_keyword(kw::Unsafe);
+        let is_unsafe = if unsafe_allowed == AllowLeadingUnsafe::Yes {
+            self.eat_keyword(kw::Unsafe)
+        } else {
+            false
+        };
         let unsafety = if is_unsafe {
             let unsafe_span = self.prev_token.span;
             self.psess.gated_spans.gate(sym::unsafe_attributes, unsafe_span);
@@ -427,7 +440,7 @@ impl<'a> Parser<'a> {
             Err(err) => err.cancel(), // we provide a better error below
         }
 
-        match self.parse_meta_item() {
+        match self.parse_meta_item(AllowLeadingUnsafe::No) {
             Ok(mi) => return Ok(ast::NestedMetaItem::MetaItem(mi)),
             Err(err) => err.cancel(), // we provide a better error below
         }
diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs
index 356bc9a410d..a64c00f3b6c 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -26,76 +26,35 @@ pub fn check_attr(features: &Features, psess: &ParseSess, attr: &Attribute) {
     let attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
     let attr_item = attr.get_normal_item();
 
-    let is_unsafe_attr = attr_info.is_some_and(|attr| attr.safety == AttributeSafety::Unsafe);
-
-    if features.unsafe_attributes {
-        if is_unsafe_attr {
-            if let ast::Safety::Default = attr_item.unsafety {
-                let path_span = attr_item.path.span;
-
-                // If the `attr_item`'s span is not from a macro, then just suggest
-                // wrapping it in `unsafe(...)`. Otherwise, we suggest putting the
-                // `unsafe(`, `)` right after and right before the opening and closing
-                // square bracket respectively.
-                let diag_span = if attr_item.span().can_be_used_for_suggestions() {
-                    attr_item.span()
-                } else {
-                    attr.span
-                        .with_lo(attr.span.lo() + BytePos(2))
-                        .with_hi(attr.span.hi() - BytePos(1))
-                };
-
-                if attr.span.at_least_rust_2024() {
-                    psess.dcx().emit_err(errors::UnsafeAttrOutsideUnsafe {
-                        span: path_span,
-                        suggestion: errors::UnsafeAttrOutsideUnsafeSuggestion {
-                            left: diag_span.shrink_to_lo(),
-                            right: diag_span.shrink_to_hi(),
-                        },
-                    });
-                } else {
-                    psess.buffer_lint(
-                        UNSAFE_ATTR_OUTSIDE_UNSAFE,
-                        path_span,
-                        ast::CRATE_NODE_ID,
-                        BuiltinLintDiag::UnsafeAttrOutsideUnsafe {
-                            attribute_name_span: path_span,
-                            sugg_spans: (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()),
-                        },
-                    );
-                }
-            }
-        } else {
-            if let Safety::Unsafe(unsafe_span) = attr_item.unsafety {
-                psess.dcx().emit_err(errors::InvalidAttrUnsafe {
-                    span: unsafe_span,
-                    name: attr_item.path.clone(),
-                });
-            }
-        }
-    }
+    // All non-builtin attributes are considered safe
+    let safety = attr_info.map(|x| x.safety).unwrap_or(AttributeSafety::Normal);
+    check_attribute_safety(features, psess, safety, attr);
 
     // Check input tokens for built-in and key-value attributes.
     match attr_info {
         // `rustc_dummy` doesn't have any restrictions specific to built-in attributes.
         Some(BuiltinAttribute { name, template, .. }) if *name != sym::rustc_dummy => {
             match parse_meta(psess, attr) {
-                Ok(meta) => check_builtin_meta_item(psess, &meta, attr.style, *name, *template),
+                // Don't check safety again, we just did that
+                Ok(meta) => check_builtin_meta_item(
+                    features, psess, &meta, attr.style, *name, *template, false,
+                ),
                 Err(err) => {
                     err.emit();
                 }
             }
         }
-        _ if let AttrArgs::Eq(..) = attr_item.args => {
-            // All key-value attributes are restricted to meta-item syntax.
-            match parse_meta(psess, attr) {
-                Ok(_) => {}
-                Err(err) => {
-                    err.emit();
+        _ => {
+            if let AttrArgs::Eq(..) = attr_item.args {
+                // All key-value attributes are restricted to meta-item syntax.
+                match parse_meta(psess, attr) {
+                    Ok(_) => {}
+                    Err(err) => {
+                        err.emit();
+                    }
                 }
             }
         }
-        _ => {}
     }
 }
 
@@ -198,12 +157,85 @@ fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaIte
     }
 }
 
+pub fn check_attribute_safety(
+    features: &Features,
+    psess: &ParseSess,
+    safety: AttributeSafety,
+    attr: &Attribute,
+) {
+    if !features.unsafe_attributes {
+        return;
+    }
+
+    let attr_item = attr.get_normal_item();
+
+    if safety == AttributeSafety::Unsafe {
+        if let ast::Safety::Default = attr_item.unsafety {
+            let path_span = attr_item.path.span;
+
+            // If the `attr_item`'s span is not from a macro, then just suggest
+            // wrapping it in `unsafe(...)`. Otherwise, we suggest putting the
+            // `unsafe(`, `)` right after and right before the opening and closing
+            // square bracket respectively.
+            let diag_span = if attr_item.span().can_be_used_for_suggestions() {
+                attr_item.span()
+            } else {
+                attr.span.with_lo(attr.span.lo() + BytePos(2)).with_hi(attr.span.hi() - BytePos(1))
+            };
+
+            if attr.span.at_least_rust_2024() {
+                psess.dcx().emit_err(errors::UnsafeAttrOutsideUnsafe {
+                    span: path_span,
+                    suggestion: errors::UnsafeAttrOutsideUnsafeSuggestion {
+                        left: diag_span.shrink_to_lo(),
+                        right: diag_span.shrink_to_hi(),
+                    },
+                });
+            } else {
+                psess.buffer_lint(
+                    UNSAFE_ATTR_OUTSIDE_UNSAFE,
+                    path_span,
+                    ast::CRATE_NODE_ID,
+                    BuiltinLintDiag::UnsafeAttrOutsideUnsafe {
+                        attribute_name_span: path_span,
+                        sugg_spans: (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()),
+                    },
+                );
+            }
+        }
+    } else {
+        if let Safety::Unsafe(unsafe_span) = attr_item.unsafety {
+            psess.dcx().emit_err(errors::InvalidAttrUnsafe {
+                span: unsafe_span,
+                name: attr_item.path.clone(),
+            });
+        }
+    }
+}
+
+// Called by `check_builtin_meta_item` and code that manually denies
+// `unsafe(...)` in `cfg`
+pub fn deny_builtin_meta_unsafety(features: &Features, psess: &ParseSess, meta: &MetaItem) {
+    // This only supports denying unsafety right now - making builtin attributes
+    // support unsafety will requite us to thread the actual `Attribute` through
+    // for the nice diagnostics.
+    if features.unsafe_attributes {
+        if let Safety::Unsafe(unsafe_span) = meta.unsafety {
+            psess
+                .dcx()
+                .emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: meta.path.clone() });
+        }
+    }
+}
+
 pub fn check_builtin_meta_item(
+    features: &Features,
     psess: &ParseSess,
     meta: &MetaItem,
     style: ast::AttrStyle,
     name: Symbol,
     template: AttributeTemplate,
+    deny_unsafety: bool,
 ) {
     // Some special attributes like `cfg` must be checked
     // before the generic check, so we skip them here.
@@ -212,6 +244,10 @@ pub fn check_builtin_meta_item(
     if !should_skip(name) && !is_attr_template_compatible(&template, &meta.kind) {
         emit_malformed_attribute(psess, style, meta.span, name, template);
     }
+
+    if deny_unsafety {
+        deny_builtin_meta_unsafety(features, psess, meta);
+    }
 }
 
 fn emit_malformed_attribute(
diff --git a/tests/ui/attributes/unsafe/derive-unsafe-attributes.rs b/tests/ui/attributes/unsafe/derive-unsafe-attributes.rs
index 774ce86c096..b8edb4aab90 100644
--- a/tests/ui/attributes/unsafe/derive-unsafe-attributes.rs
+++ b/tests/ui/attributes/unsafe/derive-unsafe-attributes.rs
@@ -1,6 +1,15 @@
 #![feature(unsafe_attributes)]
 
-#[derive(unsafe(Debug))] //~ ERROR: traits in `#[derive(...)]` don't accept `unsafe(...)`
+#[derive(unsafe(Debug))]
+//~^ ERROR: expected identifier, found keyword `unsafe`
+//~| ERROR: traits in `#[derive(...)]` don't accept arguments
+//~| ERROR: expected identifier, found keyword `unsafe`
+//~| ERROR: expected identifier, found keyword `unsafe`
+//~| ERROR: cannot find derive macro `r#unsafe` in this scope
+//~| ERROR: cannot find derive macro `r#unsafe` in this scope
 struct Foo;
 
+#[unsafe(derive(Debug))] //~ ERROR: is not an unsafe attribute
+struct Bar;
+
 fn main() {}
diff --git a/tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr
index fc0daf16790..c40a5512fd5 100644
--- a/tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr
+++ b/tests/ui/attributes/unsafe/derive-unsafe-attributes.stderr
@@ -1,8 +1,65 @@
-error: traits in `#[derive(...)]` don't accept `unsafe(...)`
+error: expected identifier, found keyword `unsafe`
+  --> $DIR/derive-unsafe-attributes.rs:3:10
+   |
+LL | #[derive(unsafe(Debug))]
+   |          ^^^^^^ expected identifier, found keyword
+   |
+help: escape `unsafe` to use it as an identifier
+   |
+LL | #[derive(r#unsafe(Debug))]
+   |          ++
+
+error: traits in `#[derive(...)]` don't accept arguments
+  --> $DIR/derive-unsafe-attributes.rs:3:16
+   |
+LL | #[derive(unsafe(Debug))]
+   |                ^^^^^^^ help: remove the arguments
+
+error: `derive` is not an unsafe attribute
+  --> $DIR/derive-unsafe-attributes.rs:12:3
+   |
+LL | #[unsafe(derive(Debug))]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: expected identifier, found keyword `unsafe`
+  --> $DIR/derive-unsafe-attributes.rs:3:10
+   |
+LL | #[derive(unsafe(Debug))]
+   |          ^^^^^^ expected identifier, found keyword
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+help: escape `unsafe` to use it as an identifier
+   |
+LL | #[derive(r#unsafe(Debug))]
+   |          ++
+
+error: expected identifier, found keyword `unsafe`
+  --> $DIR/derive-unsafe-attributes.rs:3:10
+   |
+LL | #[derive(unsafe(Debug))]
+   |          ^^^^^^ expected identifier, found keyword
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+help: escape `unsafe` to use it as an identifier
+   |
+LL | #[derive(r#unsafe(Debug))]
+   |          ++
+
+error: cannot find derive macro `r#unsafe` in this scope
   --> $DIR/derive-unsafe-attributes.rs:3:10
    |
 LL | #[derive(unsafe(Debug))]
    |          ^^^^^^
 
-error: aborting due to 1 previous error
+error: cannot find derive macro `r#unsafe` in this scope
+  --> $DIR/derive-unsafe-attributes.rs:3:10
+   |
+LL | #[derive(unsafe(Debug))]
+   |          ^^^^^^
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: aborting due to 7 previous errors
 
diff --git a/tests/ui/attributes/unsafe/double-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/double-unsafe-attributes.stderr
index ea82bac6df0..950b2636993 100644
--- a/tests/ui/attributes/unsafe/double-unsafe-attributes.stderr
+++ b/tests/ui/attributes/unsafe/double-unsafe-attributes.stderr
@@ -13,7 +13,7 @@ error: `r#unsafe` is not an unsafe attribute
   --> $DIR/double-unsafe-attributes.rs:3:3
    |
 LL | #[unsafe(unsafe(no_mangle))]
-   |   ^^^^^^
+   |   ^^^^^^ this is not an unsafe attribute
    |
    = note: extraneous unsafe is not allowed in attributes
 
diff --git a/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.rs b/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.rs
new file mode 100644
index 00000000000..0181add843b
--- /dev/null
+++ b/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.rs
@@ -0,0 +1,31 @@
+//@ edition: 2024
+//@ compile-flags: -Zunstable-options
+#![feature(unsafe_attributes)]
+
+#[unsafe(cfg(any()))] //~ ERROR: is not an unsafe attribute
+fn a() {}
+
+#[unsafe(cfg_attr(any(), allow(dead_code)))] //~ ERROR: is not an unsafe attribute
+fn b() {}
+
+#[unsafe(test)] //~ ERROR: is not an unsafe attribute
+fn aa() {}
+
+#[unsafe(ignore = "test")] //~ ERROR: is not an unsafe attribute
+fn bb() {}
+
+#[unsafe(should_panic(expected = "test"))] //~ ERROR: is not an unsafe attribute
+fn cc() {}
+
+#[unsafe(macro_use)] //~ ERROR: is not an unsafe attribute
+mod inner {
+    #[unsafe(macro_export)] //~ ERROR: is not an unsafe attribute
+    macro_rules! m {
+        () => {};
+    }
+}
+
+#[unsafe(used)] //~ ERROR: is not an unsafe attribute
+static FOO: usize = 0;
+
+fn main() {}
diff --git a/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.stderr
new file mode 100644
index 00000000000..f39074b613d
--- /dev/null
+++ b/tests/ui/attributes/unsafe/extraneous-unsafe-attributes.stderr
@@ -0,0 +1,66 @@
+error: `cfg` is not an unsafe attribute
+  --> $DIR/extraneous-unsafe-attributes.rs:5:3
+   |
+LL | #[unsafe(cfg(any()))]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `cfg_attr` is not an unsafe attribute
+  --> $DIR/extraneous-unsafe-attributes.rs:8:3
+   |
+LL | #[unsafe(cfg_attr(any(), allow(dead_code)))]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `test` is not an unsafe attribute
+  --> $DIR/extraneous-unsafe-attributes.rs:11:3
+   |
+LL | #[unsafe(test)]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `ignore` is not an unsafe attribute
+  --> $DIR/extraneous-unsafe-attributes.rs:14:3
+   |
+LL | #[unsafe(ignore = "test")]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `should_panic` is not an unsafe attribute
+  --> $DIR/extraneous-unsafe-attributes.rs:17:3
+   |
+LL | #[unsafe(should_panic(expected = "test"))]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `macro_use` is not an unsafe attribute
+  --> $DIR/extraneous-unsafe-attributes.rs:20:3
+   |
+LL | #[unsafe(macro_use)]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `macro_export` is not an unsafe attribute
+  --> $DIR/extraneous-unsafe-attributes.rs:22:7
+   |
+LL |     #[unsafe(macro_export)]
+   |       ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `used` is not an unsafe attribute
+  --> $DIR/extraneous-unsafe-attributes.rs:28:3
+   |
+LL | #[unsafe(used)]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: aborting due to 8 previous errors
+
diff --git a/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs b/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs
new file mode 100644
index 00000000000..f29a5b3252b
--- /dev/null
+++ b/tests/ui/attributes/unsafe/proc-unsafe-attributes.rs
@@ -0,0 +1,37 @@
+#![feature(unsafe_attributes)]
+
+#[unsafe(proc_macro)]
+//~^ ERROR: is not an unsafe attribute
+//~| ERROR attribute is only usable with crates of the `proc-macro` crate type
+pub fn a() {}
+
+
+#[unsafe(proc_macro_derive(Foo))]
+//~^ ERROR: is not an unsafe attribute
+//~| ERROR attribute is only usable with crates of the `proc-macro` crate type
+pub fn b() {}
+
+#[proc_macro_derive(unsafe(Foo))]
+//~^ ERROR attribute is only usable with crates of the `proc-macro` crate type
+//~| ERROR: expected identifier, found keyword `unsafe`
+pub fn c() {}
+
+#[unsafe(proc_macro_attribute)]
+//~^ ERROR: is not an unsafe attribute
+//~| ERROR attribute is only usable with crates of the `proc-macro` crate type
+pub fn d() {}
+
+#[unsafe(allow(dead_code))]
+//~^ ERROR: is not an unsafe attribute
+pub fn e() {}
+
+#[unsafe(allow(unsafe(dead_code)))]
+//~^ ERROR: is not an unsafe attribute
+//~| ERROR: malformed lint attribute input
+//~| ERROR: malformed lint attribute input
+//~| ERROR: expected identifier, found keyword `unsafe`
+//~| ERROR: malformed lint attribute input
+//~| ERROR: malformed lint attribute input
+pub fn f() {}
+
+fn main() {}
diff --git a/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr
new file mode 100644
index 00000000000..79d34d458bd
--- /dev/null
+++ b/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr
@@ -0,0 +1,119 @@
+error[E0452]: malformed lint attribute input
+  --> $DIR/proc-unsafe-attributes.rs:28:16
+   |
+LL | #[unsafe(allow(unsafe(dead_code)))]
+   |                ^^^^^^^^^^^^^^^^^ bad attribute argument
+
+error[E0452]: malformed lint attribute input
+  --> $DIR/proc-unsafe-attributes.rs:28:16
+   |
+LL | #[unsafe(allow(unsafe(dead_code)))]
+   |                ^^^^^^^^^^^^^^^^^ bad attribute argument
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: `proc_macro` is not an unsafe attribute
+  --> $DIR/proc-unsafe-attributes.rs:3:3
+   |
+LL | #[unsafe(proc_macro)]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `proc_macro_derive` is not an unsafe attribute
+  --> $DIR/proc-unsafe-attributes.rs:9:3
+   |
+LL | #[unsafe(proc_macro_derive(Foo))]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: expected identifier, found keyword `unsafe`
+  --> $DIR/proc-unsafe-attributes.rs:14:21
+   |
+LL | #[proc_macro_derive(unsafe(Foo))]
+   |                     ^^^^^^ expected identifier, found keyword
+   |
+help: escape `unsafe` to use it as an identifier
+   |
+LL | #[proc_macro_derive(r#unsafe(Foo))]
+   |                     ++
+
+error: `proc_macro_attribute` is not an unsafe attribute
+  --> $DIR/proc-unsafe-attributes.rs:19:3
+   |
+LL | #[unsafe(proc_macro_attribute)]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `allow` is not an unsafe attribute
+  --> $DIR/proc-unsafe-attributes.rs:24:3
+   |
+LL | #[unsafe(allow(dead_code))]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: `allow` is not an unsafe attribute
+  --> $DIR/proc-unsafe-attributes.rs:28:3
+   |
+LL | #[unsafe(allow(unsafe(dead_code)))]
+   |   ^^^^^^ this is not an unsafe attribute
+   |
+   = note: extraneous unsafe is not allowed in attributes
+
+error: expected identifier, found keyword `unsafe`
+  --> $DIR/proc-unsafe-attributes.rs:28:16
+   |
+LL | #[unsafe(allow(unsafe(dead_code)))]
+   |                ^^^^^^ expected identifier, found keyword
+   |
+help: escape `unsafe` to use it as an identifier
+   |
+LL | #[unsafe(allow(r#unsafe(dead_code)))]
+   |                ++
+
+error: the `#[proc_macro]` attribute is only usable with crates of the `proc-macro` crate type
+  --> $DIR/proc-unsafe-attributes.rs:3:1
+   |
+LL | #[unsafe(proc_macro)]
+   | ^^^^^^^^^^^^^^^^^^^^^
+
+error: the `#[proc_macro_derive]` attribute is only usable with crates of the `proc-macro` crate type
+  --> $DIR/proc-unsafe-attributes.rs:9:1
+   |
+LL | #[unsafe(proc_macro_derive(Foo))]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: the `#[proc_macro_derive]` attribute is only usable with crates of the `proc-macro` crate type
+  --> $DIR/proc-unsafe-attributes.rs:14:1
+   |
+LL | #[proc_macro_derive(unsafe(Foo))]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: the `#[proc_macro_attribute]` attribute is only usable with crates of the `proc-macro` crate type
+  --> $DIR/proc-unsafe-attributes.rs:19:1
+   |
+LL | #[unsafe(proc_macro_attribute)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0452]: malformed lint attribute input
+  --> $DIR/proc-unsafe-attributes.rs:28:16
+   |
+LL | #[unsafe(allow(unsafe(dead_code)))]
+   |                ^^^^^^^^^^^^^^^^^ bad attribute argument
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error[E0452]: malformed lint attribute input
+  --> $DIR/proc-unsafe-attributes.rs:28:16
+   |
+LL | #[unsafe(allow(unsafe(dead_code)))]
+   |                ^^^^^^^^^^^^^^^^^ bad attribute argument
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+error: aborting due to 15 previous errors
+
+For more information about this error, try `rustc --explain E0452`.
diff --git a/tests/ui/attributes/unsafe/unsafe-attributes.rs b/tests/ui/attributes/unsafe/unsafe-attributes.rs
index e7620a18048..33a412add50 100644
--- a/tests/ui/attributes/unsafe/unsafe-attributes.rs
+++ b/tests/ui/attributes/unsafe/unsafe-attributes.rs
@@ -4,4 +4,10 @@
 #[unsafe(no_mangle)]
 fn a() {}
 
+#[unsafe(export_name = "foo")]
+fn b() {}
+
+#[cfg_attr(any(), unsafe(no_mangle))]
+static VAR2: u32 = 1;
+
 fn main() {}
diff --git a/tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr b/tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr
index 0602af34e4f..584b0ea797d 100644
--- a/tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr
+++ b/tests/ui/attributes/unsafe/unsafe-safe-attribute.stderr
@@ -2,7 +2,7 @@ error: `repr` is not an unsafe attribute
   --> $DIR/unsafe-safe-attribute.rs:3:3
    |
 LL | #[unsafe(repr(C))]
-   |   ^^^^^^
+   |   ^^^^^^ this is not an unsafe attribute
    |
    = note: extraneous unsafe is not allowed in attributes
 
diff --git a/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr b/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr
index 584dacf4d8c..26b5e4e37b9 100644
--- a/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr
+++ b/tests/ui/attributes/unsafe/unsafe-safe-attribute_diagnostic.stderr
@@ -2,7 +2,7 @@ error: `diagnostic::on_unimplemented` is not an unsafe attribute
   --> $DIR/unsafe-safe-attribute_diagnostic.rs:3:3
    |
 LL | #[unsafe(diagnostic::on_unimplemented(
-   |   ^^^^^^
+   |   ^^^^^^ this is not an unsafe attribute
    |
    = note: extraneous unsafe is not allowed in attributes