about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJana Dönszelmann <jana@donsz.nl>2025-08-20 16:49:11 +0200
committerJana Dönszelmann <jana@donsz.nl>2025-08-23 12:31:07 +0200
commit1c03ae19dba16ffa95394927a4d91d30fc284910 (patch)
treeb375fb6d28716e2b27b3bcbfb755b714e166ff1f
parent8df154bffddcb6bbb543ad69aff971795c5adbc2 (diff)
downloadrust-1c03ae19dba16ffa95394927a4d91d30fc284910.tar.gz
rust-1c03ae19dba16ffa95394927a4d91d30fc284910.zip
port attribute to the new parsing infrastructure
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs113
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs4
-rw-r--r--compiler/rustc_codegen_ssa/messages.ftl3
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs89
-rw-r--r--compiler/rustc_codegen_ssa/src/errors.rs8
-rw-r--r--compiler/rustc_hir/src/attrs/data_structures.rs7
-rw-r--r--compiler/rustc_hir/src/attrs/encode_cross_crate.rs1
-rw-r--r--compiler/rustc_hir/src/attrs/pretty_printing.rs13
-rw-r--r--compiler/rustc_passes/src/check_attr.rs74
-rw-r--r--tests/ui/attributes/malformed-attrs.stderr36
-rw-r--r--tests/ui/sanitize-attr/invalid-sanitize.rs9
-rw-r--r--tests/ui/sanitize-attr/invalid-sanitize.stderr115
12 files changed, 281 insertions, 191 deletions
diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
index 91053811a0b..b884f8f3832 100644
--- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
@@ -1,4 +1,4 @@
-use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, UsedBy};
+use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, SanitizerSet, UsedBy};
 use rustc_session::parse::feature_err;
 
 use super::prelude::*;
@@ -464,6 +464,12 @@ impl<S: Stage> CombineAttributeParser<S> for ForceTargetFeatureParser {
         was_forced: true,
     };
     const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]);
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
+        Allow(Target::Fn),
+        Allow(Target::Method(MethodKind::Inherent)),
+        Allow(Target::Method(MethodKind::Trait { body: true })),
+        Allow(Target::Method(MethodKind::TraitImpl)),
+    ]);
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
@@ -471,11 +477,106 @@ impl<S: Stage> CombineAttributeParser<S> for ForceTargetFeatureParser {
     ) -> impl IntoIterator<Item = Self::Item> + 'c {
         parse_tf_attribute(cx, args)
     }
+}
 
-    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
-        Allow(Target::Fn),
-        Allow(Target::Method(MethodKind::Inherent)),
-        Allow(Target::Method(MethodKind::Trait { body: true })),
-        Allow(Target::Method(MethodKind::TraitImpl)),
+pub(crate) struct SanitizeParser;
+
+impl<S: Stage> SingleAttributeParser<S> for SanitizeParser {
+    const PATH: &[Symbol] = &[sym::sanitize];
+
+    // FIXME: still checked in check_attrs.rs
+    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
+
+    const TEMPLATE: AttributeTemplate = template!(List: &[
+        r#"address = "on|off""#,
+        r#"kernel_address = "on|off""#,
+        r#"cfi = "on|off""#,
+        r#"hwaddress = "on|off""#,
+        r#"kcfi = "on|off""#,
+        r#"memory = "on|off""#,
+        r#"memtag = "on|off""#,
+        r#"shadow_call_stack = "on|off""#,
+        r#"thread = "on|off""#
     ]);
+
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(list) = args.list() else {
+            cx.expected_list(cx.attr_span);
+            return None;
+        };
+
+        let mut on_set = SanitizerSet::empty();
+        let mut off_set = SanitizerSet::empty();
+
+        for item in list.mixed() {
+            let Some(item) = item.meta_item() else {
+                cx.expected_name_value(item.span(), None);
+                continue;
+            };
+
+            let path = item.path().word_sym();
+            let Some(value) = item.args().name_value() else {
+                cx.expected_name_value(item.span(), path);
+                continue;
+            };
+
+            let mut apply = |s: SanitizerSet| {
+                let is_on = match value.value_as_str() {
+                    Some(sym::on) => true,
+                    Some(sym::off) => false,
+                    Some(_) => {
+                        cx.expected_specific_argument_strings(
+                            value.value_span,
+                            &[sym::on, sym::off],
+                        );
+                        return;
+                    }
+                    None => {
+                        cx.expected_string_literal(value.value_span, Some(value.value_as_lit()));
+                        return;
+                    }
+                };
+
+                if is_on {
+                    on_set |= s;
+                } else {
+                    off_set |= s;
+                }
+            };
+
+            match path {
+                Some(sym::address) | Some(sym::kernel_address) => {
+                    apply(SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS)
+                }
+                Some(sym::cfi) => apply(SanitizerSet::CFI),
+                Some(sym::kcfi) => apply(SanitizerSet::KCFI),
+                Some(sym::memory) => apply(SanitizerSet::MEMORY),
+                Some(sym::memtag) => apply(SanitizerSet::MEMTAG),
+                Some(sym::shadow_call_stack) => apply(SanitizerSet::SHADOWCALLSTACK),
+                Some(sym::thread) => apply(SanitizerSet::THREAD),
+                Some(sym::hwaddress) => apply(SanitizerSet::HWADDRESS),
+                _ => {
+                    cx.expected_specific_argument_strings(
+                        item.path().span(),
+                        &[
+                            sym::address,
+                            sym::cfi,
+                            sym::kcfi,
+                            sym::memory,
+                            sym::memtag,
+                            sym::shadow_call_stack,
+                            sym::thread,
+                            sym::hwaddress,
+                        ],
+                    );
+                    continue;
+                }
+            }
+        }
+
+        Some(AttributeKind::Sanitize { on_set, off_set, span: cx.attr_span })
+    }
 }
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index b9c415e8085..bb701df6053 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -20,7 +20,8 @@ use crate::attributes::allow_unstable::{
 use crate::attributes::body::CoroutineParser;
 use crate::attributes::codegen_attrs::{
     ColdParser, CoverageParser, ExportNameParser, ForceTargetFeatureParser, NakedParser,
-    NoMangleParser, OptimizeParser, TargetFeatureParser, TrackCallerParser, UsedParser,
+    NoMangleParser, OptimizeParser, SanitizeParser, TargetFeatureParser, TrackCallerParser,
+    UsedParser,
 };
 use crate::attributes::confusables::ConfusablesParser;
 use crate::attributes::deprecation::DeprecationParser;
@@ -184,6 +185,7 @@ attribute_parsers!(
         Single<RustcLayoutScalarValidRangeEnd>,
         Single<RustcLayoutScalarValidRangeStart>,
         Single<RustcObjectLifetimeDefaultParser>,
+        Single<SanitizeParser>,
         Single<ShouldPanicParser>,
         Single<SkipDuringMethodDispatchParser>,
         Single<TransparencyParser>,
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index 42ba0154192..44b9941691a 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -171,9 +171,6 @@ codegen_ssa_invalid_monomorphization_unsupported_symbol = invalid monomorphizati
 
 codegen_ssa_invalid_monomorphization_unsupported_symbol_of_size = invalid monomorphization of `{$name}` intrinsic: unsupported {$symbol} from `{$in_ty}` with element `{$in_elem}` of size `{$size}` to `{$ret_ty}`
 
-codegen_ssa_invalid_sanitize = invalid argument for `sanitize`
-    .note = expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread`
-
 codegen_ssa_invalid_windows_subsystem = invalid windows subsystem `{$subsystem}`, only `windows` and `console` are allowed
 
 codegen_ssa_ld64_unimplemented_modifier = `as-needed` modifier not implemented yet for ld64
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index 23e2abd6de3..6b0bd64102c 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -293,6 +293,9 @@ fn process_builtin_attrs(
                         codegen_fn_attrs.linkage = linkage;
                     }
                 }
+                AttributeKind::Sanitize { span, .. } => {
+                    interesting_spans.sanitize = Some(*span);
+                }
                 _ => {}
             }
         }
@@ -310,7 +313,6 @@ fn process_builtin_attrs(
                 codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR_ZEROED
             }
             sym::thread_local => codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL,
-            sym::sanitize => interesting_spans.sanitize = Some(attr.span()),
             sym::instruction_set => {
                 codegen_fn_attrs.instruction_set = parse_instruction_set_attr(tcx, attr)
             }
@@ -560,79 +562,9 @@ fn opt_trait_item(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
     }
 }
 
-/// For an attr that has the `sanitize` attribute, read the list of
-/// disabled sanitizers. `current_attr` holds the information about
-/// previously parsed attributes.
-fn parse_sanitize_attr(
-    tcx: TyCtxt<'_>,
-    attr: &Attribute,
-    current_attr: SanitizerSet,
-) -> SanitizerSet {
-    let mut result = current_attr;
-    if let Some(list) = attr.meta_item_list() {
-        for item in list.iter() {
-            let MetaItemInner::MetaItem(set) = item else {
-                tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() });
-                break;
-            };
-            let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
-            match segments.as_slice() {
-                // Similar to clang, sanitize(address = ..) and
-                // sanitize(kernel_address = ..) control both ASan and KASan
-                // Source: https://reviews.llvm.org/D44981.
-                [sym::address] | [sym::kernel_address] if set.value_str() == Some(sym::off) => {
-                    result |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
-                }
-                [sym::address] | [sym::kernel_address] if set.value_str() == Some(sym::on) => {
-                    result &= !SanitizerSet::ADDRESS;
-                    result &= !SanitizerSet::KERNELADDRESS;
-                }
-                [sym::cfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::CFI,
-                [sym::cfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::CFI,
-                [sym::kcfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::KCFI,
-                [sym::kcfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::KCFI,
-                [sym::memory] if set.value_str() == Some(sym::off) => {
-                    result |= SanitizerSet::MEMORY
-                }
-                [sym::memory] if set.value_str() == Some(sym::on) => {
-                    result &= !SanitizerSet::MEMORY
-                }
-                [sym::memtag] if set.value_str() == Some(sym::off) => {
-                    result |= SanitizerSet::MEMTAG
-                }
-                [sym::memtag] if set.value_str() == Some(sym::on) => {
-                    result &= !SanitizerSet::MEMTAG
-                }
-                [sym::shadow_call_stack] if set.value_str() == Some(sym::off) => {
-                    result |= SanitizerSet::SHADOWCALLSTACK
-                }
-                [sym::shadow_call_stack] if set.value_str() == Some(sym::on) => {
-                    result &= !SanitizerSet::SHADOWCALLSTACK
-                }
-                [sym::thread] if set.value_str() == Some(sym::off) => {
-                    result |= SanitizerSet::THREAD
-                }
-                [sym::thread] if set.value_str() == Some(sym::on) => {
-                    result &= !SanitizerSet::THREAD
-                }
-                [sym::hwaddress] if set.value_str() == Some(sym::off) => {
-                    result |= SanitizerSet::HWADDRESS
-                }
-                [sym::hwaddress] if set.value_str() == Some(sym::on) => {
-                    result &= !SanitizerSet::HWADDRESS
-                }
-                _ => {
-                    tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() });
-                }
-            }
-        }
-    }
-    result
-}
-
 fn disabled_sanitizers_for(tcx: TyCtxt<'_>, did: LocalDefId) -> SanitizerSet {
     // Backtrack to the crate root.
-    let disabled = match tcx.opt_local_parent(did) {
+    let mut disabled = match tcx.opt_local_parent(did) {
         // Check the parent (recursively).
         Some(parent) => tcx.disabled_sanitizers_for(parent),
         // We reached the crate root without seeing an attribute, so
@@ -641,8 +573,17 @@ fn disabled_sanitizers_for(tcx: TyCtxt<'_>, did: LocalDefId) -> SanitizerSet {
     };
 
     // Check for a sanitize annotation directly on this def.
-    if let Some(attr) = tcx.get_attr(did, sym::sanitize) {
-        return parse_sanitize_attr(tcx, attr, disabled);
+    if let Some((on_set, off_set)) = find_attr!(tcx.get_all_attrs(did), AttributeKind::Sanitize {on_set, off_set, ..} => (on_set, off_set))
+    {
+        // the on set is the set of sanitizers explicitly enabled.
+        // we mask those out since we want the set of disabled sanitizers here
+        disabled &= !*on_set;
+        // the off set is the set of sanitizers explicitly disabled.
+        // we or those in here.
+        disabled |= *off_set;
+        // the on set and off set are distjoint since there's a third option: unset.
+        // a node may not set the sanitizer setting in which case it inherits from parents.
+        // the code above in this function does this backtracking
     }
     disabled
 }
diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index 209c78ddeda..fb5a8205140 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -1121,14 +1121,6 @@ impl IntoDiagArg for ExpectedPointerMutability {
 }
 
 #[derive(Diagnostic)]
-#[diag(codegen_ssa_invalid_sanitize)]
-#[note]
-pub(crate) struct InvalidSanitize {
-    #[primary_span]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
 #[diag(codegen_ssa_target_feature_safe_trait)]
 pub(crate) struct TargetFeatureSafeTrait {
     #[primary_span]
diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs
index 2209b18df3f..1e73bb6f5fd 100644
--- a/compiler/rustc_hir/src/attrs/data_structures.rs
+++ b/compiler/rustc_hir/src/attrs/data_structures.rs
@@ -10,6 +10,7 @@ use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute};
 use rustc_span::def_id::DefId;
 use rustc_span::hygiene::Transparency;
 use rustc_span::{Ident, Span, Symbol};
+pub use rustc_target::spec::SanitizerSet;
 use thin_vec::ThinVec;
 
 use crate::attrs::pretty_printing::PrintAttribute;
@@ -505,6 +506,12 @@ pub enum AttributeKind {
     /// Represents `#[rustc_object_lifetime_default]`.
     RustcObjectLifetimeDefault,
 
+    /// Represents `#[sanitize]`
+    ///
+    /// the on set and off set are distjoint since there's a third option: unset.
+    /// a node may not set the sanitizer setting in which case it inherits from parents.
+    Sanitize { on_set: SanitizerSet, off_set: SanitizerSet, span: Span },
+
     /// Represents `#[should_panic]`
     ShouldPanic { reason: Option<Symbol>, span: Span },
 
diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
index 485ded3981f..77c6faa7acd 100644
--- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
+++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs
@@ -73,6 +73,7 @@ impl AttributeKind {
             RustcLayoutScalarValidRangeEnd(..) => Yes,
             RustcLayoutScalarValidRangeStart(..) => Yes,
             RustcObjectLifetimeDefault => No,
+            Sanitize { .. } => No,
             ShouldPanic { .. } => No,
             SkipDuringMethodDispatch { .. } => No,
             SpecializationTrait(..) => No,
diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs
index e44b29141da..e65de25b451 100644
--- a/compiler/rustc_hir/src/attrs/pretty_printing.rs
+++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs
@@ -6,6 +6,7 @@ use rustc_ast::{AttrStyle, IntTy, UintTy};
 use rustc_ast_pretty::pp::Printer;
 use rustc_span::hygiene::Transparency;
 use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
+use rustc_target::spec::SanitizerSet;
 use thin_vec::ThinVec;
 
 /// This trait is used to print attributes in `rustc_hir_pretty`.
@@ -146,4 +147,14 @@ macro_rules! print_tup {
 print_tup!(A B C D E F G H);
 print_skip!(Span, (), ErrorGuaranteed);
 print_disp!(u16, bool, NonZero<u32>);
-print_debug!(Symbol, Ident, UintTy, IntTy, Align, AttrStyle, CommentKind, Transparency);
+print_debug!(
+    Symbol,
+    Ident,
+    UintTy,
+    IntTy,
+    Align,
+    AttrStyle,
+    CommentKind,
+    Transparency,
+    SanitizerSet,
+);
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 077afd6dd23..c610ce4fc85 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -18,7 +18,7 @@ use rustc_feature::{
     ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP,
     BuiltinAttribute,
 };
-use rustc_hir::attrs::{AttributeKind, InlineAttr, MirDialect, MirPhase, ReprAttr};
+use rustc_hir::attrs::{AttributeKind, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet};
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::LocalModDefId;
 use rustc_hir::intravisit::{self, Visitor};
@@ -197,6 +197,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 &Attribute::Parsed(AttributeKind::CustomMir(dialect, phase, attr_span)) => {
                     self.check_custom_mir(dialect, phase, attr_span)
                 }
+                &Attribute::Parsed(AttributeKind::Sanitize { on_set, off_set, span: attr_span}) => {
+                    self.check_sanitize(attr_span, on_set | off_set, span, target);
+                },
                 Attribute::Parsed(
                     AttributeKind::BodyStability { .. }
                     | AttributeKind::ConstStabilityIndirect
@@ -258,9 +261,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         [sym::diagnostic, sym::on_unimplemented, ..] => {
                             self.check_diagnostic_on_unimplemented(attr.span(), hir_id, target)
                         }
-                        [sym::sanitize, ..] => {
-                            self.check_sanitize(attr, span, target)
-                        }
                         [sym::thread_local, ..] => self.check_thread_local(attr, span, target),
                         [sym::doc, ..] => self.check_doc_attrs(
                             attr,
@@ -483,42 +483,48 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
 
     /// Checks that the `#[sanitize(..)]` attribute is applied to a
     /// function/closure/method, or to an impl block or module.
-    fn check_sanitize(&self, attr: &Attribute, target_span: Span, target: Target) {
+    fn check_sanitize(
+        &self,
+        attr_span: Span,
+        set: SanitizerSet,
+        target_span: Span,
+        target: Target,
+    ) {
         let mut not_fn_impl_mod = None;
         let mut no_body = None;
 
-        if let Some(list) = attr.meta_item_list() {
-            for item in list.iter() {
-                let MetaItemInner::MetaItem(set) = item else {
-                    return;
-                };
-                let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
-                match target {
-                    Target::Fn
-                    | Target::Closure
-                    | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent)
-                    | Target::Impl { .. }
-                    | Target::Mod => return,
-                    Target::Static if matches!(segments.as_slice(), [sym::address]) => return,
-
-                    // These are "functions", but they aren't allowed because they don't
-                    // have a body, so the usual explanation would be confusing.
-                    Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
-                        no_body = Some(target_span);
-                    }
+        match target {
+            Target::Fn
+            | Target::Closure
+            | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent)
+            | Target::Impl { .. }
+            | Target::Mod => return,
+            Target::Static
+                // if we mask out the address bits, i.e. *only* address was set,
+                // we allow it
+                if set & !(SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS)
+                    == SanitizerSet::empty() =>
+            {
+                return;
+            }
 
-                    _ => {
-                        not_fn_impl_mod = Some(target_span);
-                    }
-                }
+            // These are "functions", but they aren't allowed because they don't
+            // have a body, so the usual explanation would be confusing.
+            Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
+                no_body = Some(target_span);
+            }
+
+            _ => {
+                not_fn_impl_mod = Some(target_span);
             }
-            self.dcx().emit_err(errors::SanitizeAttributeNotAllowed {
-                attr_span: attr.span(),
-                not_fn_impl_mod,
-                no_body,
-                help: (),
-            });
         }
+
+        self.dcx().emit_err(errors::SanitizeAttributeNotAllowed {
+            attr_span,
+            not_fn_impl_mod,
+            no_body,
+            help: (),
+        });
     }
 
     /// Checks if `#[naked]` is applied to a function definition.
diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr
index 98ff578918b..11e01ac29be 100644
--- a/tests/ui/attributes/malformed-attrs.stderr
+++ b/tests/ui/attributes/malformed-attrs.stderr
@@ -49,24 +49,6 @@ LL | #[crate_name]
    |
    = note: for more information, visit <https://doc.rust-lang.org/reference/crates-and-source-files.html#the-crate_name-attribute>
 
-error: malformed `sanitize` attribute input
-  --> $DIR/malformed-attrs.rs:92:1
-   |
-LL | #[sanitize]
-   | ^^^^^^^^^^^
-   |
-help: the following are the possible correct uses
-   |
-LL | #[sanitize(address = "on|off")]
-   |           ++++++++++++++++++++
-LL | #[sanitize(cfi = "on|off")]
-   |           ++++++++++++++++
-LL | #[sanitize(hwaddress = "on|off")]
-   |           ++++++++++++++++++++++
-LL | #[sanitize(kcfi = "on|off")]
-   |           +++++++++++++++++
-   = and 5 other candidates
-
 error: malformed `instruction_set` attribute input
   --> $DIR/malformed-attrs.rs:106:1
    |
@@ -543,6 +525,24 @@ LL | #[coverage(off)]
 LL | #[coverage(on)]
    |           ++++
 
+error[E0539]: malformed `sanitize` attribute input
+  --> $DIR/malformed-attrs.rs:92:1
+   |
+LL | #[sanitize]
+   | ^^^^^^^^^^^ expected this to be a list
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL | #[sanitize(address = "on|off")]
+   |           ++++++++++++++++++++
+LL | #[sanitize(cfi = "on|off")]
+   |           ++++++++++++++++
+LL | #[sanitize(hwaddress = "on|off")]
+   |           ++++++++++++++++++++++
+LL | #[sanitize(kcfi = "on|off")]
+   |           +++++++++++++++++
+   = and 5 other candidates
+
 error[E0565]: malformed `no_implicit_prelude` attribute input
   --> $DIR/malformed-attrs.rs:97:1
    |
diff --git a/tests/ui/sanitize-attr/invalid-sanitize.rs b/tests/ui/sanitize-attr/invalid-sanitize.rs
index 49dc01c8daa..957ce780ad0 100644
--- a/tests/ui/sanitize-attr/invalid-sanitize.rs
+++ b/tests/ui/sanitize-attr/invalid-sanitize.rs
@@ -1,8 +1,7 @@
 #![feature(sanitize)]
 
-#[sanitize(brontosaurus = "off")] //~ ERROR invalid argument
-fn main() {
-}
+#[sanitize(brontosaurus = "off")] //~ ERROR malformed `sanitize` attribute input
+fn main() {}
 
 #[sanitize(address = "off")] //~ ERROR multiple `sanitize` attributes
 #[sanitize(address = "off")]
@@ -12,11 +11,11 @@ fn multiple_consistent() {}
 #[sanitize(address = "off")]
 fn multiple_inconsistent() {}
 
-#[sanitize(address = "bogus")] //~ ERROR invalid argument for `sanitize`
+#[sanitize(address = "bogus")] //~ ERROR malformed `sanitize` attribute input
 fn wrong_value() {}
 
 #[sanitize = "off"] //~ ERROR malformed `sanitize` attribute input
-fn name_value () {}
+fn name_value() {}
 
 #[sanitize] //~ ERROR malformed `sanitize` attribute input
 fn just_word() {}
diff --git a/tests/ui/sanitize-attr/invalid-sanitize.stderr b/tests/ui/sanitize-attr/invalid-sanitize.stderr
index 4bf81770b89..ec0a93be142 100644
--- a/tests/ui/sanitize-attr/invalid-sanitize.stderr
+++ b/tests/ui/sanitize-attr/invalid-sanitize.stderr
@@ -1,82 +1,115 @@
-error: malformed `sanitize` attribute input
-  --> $DIR/invalid-sanitize.rs:18:1
+error[E0539]: malformed `sanitize` attribute input
+  --> $DIR/invalid-sanitize.rs:3:1
    |
-LL | #[sanitize = "off"]
-   | ^^^^^^^^^^^^^^^^^^^
+LL | #[sanitize(brontosaurus = "off")]
+   | ^^^^^^^^^^^------------^^^^^^^^^^
+   |            |
+   |            valid arguments are "address", "cfi", "kcfi", "memory", "memtag", "shadow_call_stack", "thread" or "hwaddress"
    |
-help: the following are the possible correct uses
+help: try changing it to one of the following valid forms of the attribute
    |
-LL - #[sanitize = "off"]
+LL - #[sanitize(brontosaurus = "off")]
 LL + #[sanitize(address = "on|off")]
    |
-LL - #[sanitize = "off"]
+LL - #[sanitize(brontosaurus = "off")]
 LL + #[sanitize(cfi = "on|off")]
    |
-LL - #[sanitize = "off"]
+LL - #[sanitize(brontosaurus = "off")]
 LL + #[sanitize(hwaddress = "on|off")]
    |
-LL - #[sanitize = "off"]
+LL - #[sanitize(brontosaurus = "off")]
 LL + #[sanitize(kcfi = "on|off")]
    |
    = and 5 other candidates
 
-error: malformed `sanitize` attribute input
-  --> $DIR/invalid-sanitize.rs:21:1
-   |
-LL | #[sanitize]
-   | ^^^^^^^^^^^
-   |
-help: the following are the possible correct uses
-   |
-LL | #[sanitize(address = "on|off")]
-   |           ++++++++++++++++++++
-LL | #[sanitize(cfi = "on|off")]
-   |           ++++++++++++++++
-LL | #[sanitize(hwaddress = "on|off")]
-   |           ++++++++++++++++++++++
-LL | #[sanitize(kcfi = "on|off")]
-   |           +++++++++++++++++
-   = and 5 other candidates
-
 error: multiple `sanitize` attributes
-  --> $DIR/invalid-sanitize.rs:7:1
+  --> $DIR/invalid-sanitize.rs:6:1
    |
 LL | #[sanitize(address = "off")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
    |
 note: attribute also specified here
-  --> $DIR/invalid-sanitize.rs:8:1
+  --> $DIR/invalid-sanitize.rs:7:1
    |
 LL | #[sanitize(address = "off")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: multiple `sanitize` attributes
-  --> $DIR/invalid-sanitize.rs:11:1
+  --> $DIR/invalid-sanitize.rs:10:1
    |
 LL | #[sanitize(address = "on")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
    |
 note: attribute also specified here
-  --> $DIR/invalid-sanitize.rs:12:1
+  --> $DIR/invalid-sanitize.rs:11:1
    |
 LL | #[sanitize(address = "off")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: invalid argument for `sanitize`
-  --> $DIR/invalid-sanitize.rs:3:1
+error[E0539]: malformed `sanitize` attribute input
+  --> $DIR/invalid-sanitize.rs:14:1
    |
-LL | #[sanitize(brontosaurus = "off")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[sanitize(address = "bogus")]
+   | ^^^^^^^^^^^^^^^^^^^^^-------^^
+   |                      |
+   |                      valid arguments are "on" or "off"
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL - #[sanitize(address = "bogus")]
+LL + #[sanitize(address = "on|off")]
+   |
+LL - #[sanitize(address = "bogus")]
+LL + #[sanitize(cfi = "on|off")]
+   |
+LL - #[sanitize(address = "bogus")]
+LL + #[sanitize(hwaddress = "on|off")]
+   |
+LL - #[sanitize(address = "bogus")]
+LL + #[sanitize(kcfi = "on|off")]
    |
-   = note: expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread`
+   = and 5 other candidates
 
-error: invalid argument for `sanitize`
-  --> $DIR/invalid-sanitize.rs:15:1
+error[E0539]: malformed `sanitize` attribute input
+  --> $DIR/invalid-sanitize.rs:17:1
    |
-LL | #[sanitize(address = "bogus")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[sanitize = "off"]
+   | ^^^^^^^^^^^^^^^^^^^ expected this to be a list
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL - #[sanitize = "off"]
+LL + #[sanitize(address = "on|off")]
+   |
+LL - #[sanitize = "off"]
+LL + #[sanitize(cfi = "on|off")]
+   |
+LL - #[sanitize = "off"]
+LL + #[sanitize(hwaddress = "on|off")]
+   |
+LL - #[sanitize = "off"]
+LL + #[sanitize(kcfi = "on|off")]
+   |
+   = and 5 other candidates
+
+error[E0539]: malformed `sanitize` attribute input
+  --> $DIR/invalid-sanitize.rs:20:1
    |
-   = note: expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread`
+LL | #[sanitize]
+   | ^^^^^^^^^^^ expected this to be a list
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL | #[sanitize(address = "on|off")]
+   |           ++++++++++++++++++++
+LL | #[sanitize(cfi = "on|off")]
+   |           ++++++++++++++++
+LL | #[sanitize(hwaddress = "on|off")]
+   |           ++++++++++++++++++++++
+LL | #[sanitize(kcfi = "on|off")]
+   |           +++++++++++++++++
+   = and 5 other candidates
 
 error: aborting due to 6 previous errors
 
+For more information about this error, try `rustc --explain E0539`.