about summary refs log tree commit diff
diff options
context:
space:
mode:
authorStuart Cook <Zalathar@users.noreply.github.com>2025-08-19 14:18:16 +1000
committerGitHub <noreply@github.com>2025-08-19 14:18:16 +1000
commit633cc0cc6cfc6b77da14864dbefb1dab25dcb8d0 (patch)
tree145a2b42a2741b0f113868938d76e060d6988292
parent027c7a5d857ff9692ce40bfd3bc9a90ef3764232 (diff)
parent95bdb34494ad795f552cab7a0eb7bfd2e98ef033 (diff)
downloadrust-633cc0cc6cfc6b77da14864dbefb1dab25dcb8d0.tar.gz
rust-633cc0cc6cfc6b77da14864dbefb1dab25dcb8d0.zip
Rollup merge of #142681 - 1c3t3a:sanitize-off-on, r=rcvalle
Remove the `#[no_sanitize]` attribute in favor of `#[sanitize(xyz = "on|off")]`

This came up during the sanitizer stabilization (rust-lang/rust#123617). Instead of a `#[no_sanitize(xyz)]` attribute, we would like to have a `#[sanitize(xyz = "on|off")]` attribute, which is more powerful and allows to be extended in the future (instead
of just focusing on turning sanitizers off). The implementation is done according to what was [discussed on Zulip](https://rust-lang.zulipchat.com/#narrow/channel/343119-project-exploit-mitigations/topic/Stabilize.20the.20.60no_sanitize.60.20attribute/with/495377292)).

The new attribute also works on modules, traits and impl items and thus enables usage as the following:
```rust
#[sanitize(address = "off")]
mod foo {
    fn unsanitized(..) {}

    #[sanitize(address = "on")]
    fn sanitized(..) {}
}

trait MyTrait {
  #[sanitize(address = "off")]
  fn unsanitized_default(..) {}
}

#[sanitize(thread = "off")]
impl MyTrait for () {
    ...
}
```

r? ```@rcvalle```
-rw-r--r--compiler/rustc_codegen_ssa/messages.ftl4
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs136
-rw-r--r--compiler/rustc_codegen_ssa/src/errors.rs4
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs5
-rw-r--r--compiler/rustc_feature/src/removed.rs3
-rw-r--r--compiler/rustc_feature/src/unstable.rs4
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs12
-rw-r--r--compiler/rustc_middle/src/middle/codegen_fn_attrs.rs4
-rw-r--r--compiler/rustc_middle/src/query/erase.rs1
-rw-r--r--compiler/rustc_middle/src/query/mod.rs12
-rw-r--r--compiler/rustc_passes/messages.ftl10
-rw-r--r--compiler/rustc_passes/src/check_attr.rs61
-rw-r--r--compiler/rustc_passes/src/errors.rs18
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--src/doc/rustc-dev-guide/src/sanitizers.md2
-rw-r--r--src/doc/unstable-book/src/language-features/no-sanitize.md29
-rw-r--r--src/doc/unstable-book/src/language-features/sanitize.md73
-rw-r--r--tests/codegen-llvm/sanitizer/cfi/emit-type-checks-attr-sanitize-off.rs (renamed from tests/codegen-llvm/sanitizer/cfi/emit-type-checks-attr-no-sanitize.rs)6
-rw-r--r--tests/codegen-llvm/sanitizer/kasan-emits-instrumentation.rs4
-rw-r--r--tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-sanitize-off.rs (renamed from tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs)6
-rw-r--r--tests/codegen-llvm/sanitizer/sanitize-off-asan-kasan.rs42
-rw-r--r--tests/codegen-llvm/sanitizer/sanitize-off-inlining.rs (renamed from tests/codegen-llvm/sanitizer/no-sanitize-inlining.rs)6
-rw-r--r--tests/codegen-llvm/sanitizer/sanitize-off-kasan-asan.rs (renamed from tests/codegen-llvm/sanitizer/no-sanitize.rs)22
-rw-r--r--tests/codegen-llvm/sanitizer/sanitize-off.rs138
-rw-r--r--tests/codegen-llvm/sanitizer/scs-attr-check.rs4
-rw-r--r--tests/mir-opt/inline/inline_compatibility.rs22
-rw-r--r--tests/ui/attributes/malformed-attrs.rs4
-rw-r--r--tests/ui/attributes/malformed-attrs.stderr18
-rw-r--r--tests/ui/attributes/no-sanitize.rs45
-rw-r--r--tests/ui/attributes/no-sanitize.stderr80
-rw-r--r--tests/ui/feature-gates/feature-gate-no_sanitize.rs4
-rw-r--r--tests/ui/feature-gates/feature-gate-no_sanitize.stderr13
-rw-r--r--tests/ui/feature-gates/feature-gate-sanitize.rs7
-rw-r--r--tests/ui/feature-gates/feature-gate-sanitize.stderr23
-rw-r--r--tests/ui/invalid/invalid-no-sanitize.rs5
-rw-r--r--tests/ui/invalid/invalid-no-sanitize.stderr10
-rw-r--r--tests/ui/sanitize-attr/invalid-sanitize.rs22
-rw-r--r--tests/ui/sanitize-attr/invalid-sanitize.stderr82
-rw-r--r--tests/ui/sanitize-attr/valid-sanitize.rs115
-rw-r--r--tests/ui/sanitize-attr/valid-sanitize.stderr190
-rw-r--r--tests/ui/sanitizer/inline-always-sanitize.rs (renamed from tests/ui/sanitizer/inline-always.rs)6
-rw-r--r--tests/ui/sanitizer/inline-always-sanitize.stderr15
-rw-r--r--tests/ui/sanitizer/inline-always.stderr15
-rw-r--r--triagebot.toml4
44 files changed, 942 insertions, 345 deletions
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index b6cfea88363..42ba0154192 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -171,8 +171,8 @@ 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_no_sanitize = invalid argument for `no_sanitize`
-    .note = expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`
+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
 
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index af70f0deb07..bf14c02a09c 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -77,32 +77,6 @@ fn parse_instruction_set_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option<Instr
     }
 }
 
-// FIXME(jdonszelmann): remove when no_sanitize becomes a parsed attr
-fn parse_no_sanitize_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option<SanitizerSet> {
-    let list = attr.meta_item_list()?;
-    let mut sanitizer_set = SanitizerSet::empty();
-
-    for item in list.iter() {
-        match item.name() {
-            Some(sym::address) => {
-                sanitizer_set |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
-            }
-            Some(sym::cfi) => sanitizer_set |= SanitizerSet::CFI,
-            Some(sym::kcfi) => sanitizer_set |= SanitizerSet::KCFI,
-            Some(sym::memory) => sanitizer_set |= SanitizerSet::MEMORY,
-            Some(sym::memtag) => sanitizer_set |= SanitizerSet::MEMTAG,
-            Some(sym::shadow_call_stack) => sanitizer_set |= SanitizerSet::SHADOWCALLSTACK,
-            Some(sym::thread) => sanitizer_set |= SanitizerSet::THREAD,
-            Some(sym::hwaddress) => sanitizer_set |= SanitizerSet::HWADDRESS,
-            _ => {
-                tcx.dcx().emit_err(errors::InvalidNoSanitize { span: item.span() });
-            }
-        }
-    }
-
-    Some(sanitizer_set)
-}
-
 // FIXME(jdonszelmann): remove when patchable_function_entry becomes a parsed attr
 fn parse_patchable_function_entry(
     tcx: TyCtxt<'_>,
@@ -161,7 +135,7 @@ fn parse_patchable_function_entry(
 #[derive(Default)]
 struct InterestingAttributeDiagnosticSpans {
     link_ordinal: Option<Span>,
-    no_sanitize: Option<Span>,
+    sanitize: Option<Span>,
     inline: Option<Span>,
     no_mangle: Option<Span>,
 }
@@ -330,11 +304,7 @@ fn process_builtin_attrs(
                 codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR_ZEROED
             }
             sym::thread_local => codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL,
-            sym::no_sanitize => {
-                interesting_spans.no_sanitize = Some(attr.span());
-                codegen_fn_attrs.no_sanitize |=
-                    parse_no_sanitize_attr(tcx, attr).unwrap_or_default();
-            }
+            sym::sanitize => interesting_spans.sanitize = Some(attr.span()),
             sym::instruction_set => {
                 codegen_fn_attrs.instruction_set = parse_instruction_set_attr(tcx, attr)
             }
@@ -358,6 +328,8 @@ fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut Code
     codegen_fn_attrs.alignment =
         Ord::max(codegen_fn_attrs.alignment, tcx.sess.opts.unstable_opts.min_function_alignment);
 
+    // Compute the disabled sanitizers.
+    codegen_fn_attrs.no_sanitize |= tcx.disabled_sanitizers_for(did);
     // On trait methods, inherit the `#[align]` of the trait's method prototype.
     codegen_fn_attrs.alignment = Ord::max(codegen_fn_attrs.alignment, tcx.inherited_align(did));
 
@@ -455,11 +427,11 @@ fn check_result(
     if !codegen_fn_attrs.no_sanitize.is_empty()
         && codegen_fn_attrs.inline.always()
         && let (Some(no_sanitize_span), Some(inline_span)) =
-            (interesting_spans.no_sanitize, interesting_spans.inline)
+            (interesting_spans.sanitize, interesting_spans.inline)
     {
         let hir_id = tcx.local_def_id_to_hir_id(did);
         tcx.node_span_lint(lint::builtin::INLINE_NO_SANITIZE, hir_id, no_sanitize_span, |lint| {
-            lint.primary_message("`no_sanitize` will have no effect after inlining");
+            lint.primary_message("setting `sanitize` off will have no effect after inlining");
             lint.span_note(inline_span, "inlining requested here");
         })
     }
@@ -585,6 +557,93 @@ 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) {
+        // Check the parent (recursively).
+        Some(parent) => tcx.disabled_sanitizers_for(parent),
+        // We reached the crate root without seeing an attribute, so
+        // there is no sanitizers to exclude.
+        None => SanitizerSet::empty(),
+    };
+
+    // 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);
+    }
+    disabled
+}
+
 /// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller
 /// applied to the method prototype.
 fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
@@ -709,6 +768,11 @@ pub fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option<AutoDiffAttrs> {
 }
 
 pub(crate) fn provide(providers: &mut Providers) {
-    *providers =
-        Providers { codegen_fn_attrs, should_inherit_track_caller, inherited_align, ..*providers };
+    *providers = Providers {
+        codegen_fn_attrs,
+        should_inherit_track_caller,
+        inherited_align,
+        disabled_sanitizers_for,
+        ..*providers
+    };
 }
diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index 7ac830bcda9..209c78ddeda 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -1121,9 +1121,9 @@ impl IntoDiagArg for ExpectedPointerMutability {
 }
 
 #[derive(Diagnostic)]
-#[diag(codegen_ssa_invalid_no_sanitize)]
+#[diag(codegen_ssa_invalid_sanitize)]
 #[note]
-pub(crate) struct InvalidNoSanitize {
+pub(crate) struct InvalidSanitize {
     #[primary_span]
     pub span: Span,
 }
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 6fbedaf5b10..9749028adb0 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -741,9 +741,8 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         ErrorPreceding, EncodeCrossCrate::No
     ),
     gated!(
-        no_sanitize, Normal,
-        template!(List: &["address, kcfi, memory, thread"]), DuplicatesOk,
-        EncodeCrossCrate::No, experimental!(no_sanitize)
+        sanitize, Normal, 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""#]), ErrorPreceding,
+        EncodeCrossCrate::No, sanitize, experimental!(sanitize),
     ),
     gated!(
         coverage, Normal, template!(OneOf: &[sym::off, sym::on]),
diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs
index 04f261ada06..e37fc6b7bfc 100644
--- a/compiler/rustc_feature/src/removed.rs
+++ b/compiler/rustc_feature/src/removed.rs
@@ -190,6 +190,9 @@ declare_features! (
     (removed, no_coverage, "1.74.0", Some(84605), Some("renamed to `coverage_attribute`"), 114656),
     /// Allows `#[no_debug]`.
     (removed, no_debug, "1.43.0", Some(29721), Some("removed due to lack of demand"), 69667),
+    // Allows the use of `no_sanitize` attribute.
+    /// The feature was renamed to `sanitize` and the attribute to `#[sanitize(xyz = "on|off")]`
+    (removed, no_sanitize, "CURRENT_RUSTC_VERSION", Some(39699), Some(r#"renamed to sanitize(xyz = "on|off")"#), 142681),
     /// Note: this feature was previously recorded in a separate
     /// `STABLE_REMOVED` list because it, uniquely, was once stable but was
     /// then removed. But there was no utility storing it separately, so now
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index f2b9121ffa7..746871982ce 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -594,8 +594,6 @@ declare_features! (
     (unstable, new_range, "1.86.0", Some(123741)),
     /// Allows `#![no_core]`.
     (unstable, no_core, "1.3.0", Some(29639)),
-    /// Allows the use of `no_sanitize` attribute.
-    (unstable, no_sanitize, "1.42.0", Some(39699)),
     /// Allows using the `non_exhaustive_omitted_patterns` lint.
     (unstable, non_exhaustive_omitted_patterns_lint, "1.57.0", Some(89554)),
     /// Allows `for<T>` binders in where-clauses
@@ -628,6 +626,8 @@ declare_features! (
     (unstable, return_type_notation, "1.70.0", Some(109417)),
     /// Allows `extern "rust-cold"`.
     (unstable, rust_cold_cc, "1.63.0", Some(97544)),
+    /// Allows the use of the `sanitize` attribute.
+    (unstable, sanitize, "CURRENT_RUSTC_VERSION", Some(39699)),
     /// Allows the use of SIMD types in functions declared in `extern` blocks.
     (unstable, simd_ffi, "1.0.0", Some(27731)),
     /// Allows specialization of implementations (RFC 1210).
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index a0083a7df3d..97aa1065967 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -2301,18 +2301,18 @@ declare_lint! {
 
 declare_lint! {
     /// The `inline_no_sanitize` lint detects incompatible use of
-    /// [`#[inline(always)]`][inline] and [`#[no_sanitize(...)]`][no_sanitize].
+    /// [`#[inline(always)]`][inline] and [`#[sanitize(xyz = "off")]`][sanitize].
     ///
     /// [inline]: https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute
-    /// [no_sanitize]: https://doc.rust-lang.org/nightly/unstable-book/language-features/no-sanitize.html
+    /// [sanitize]: https://doc.rust-lang.org/nightly/unstable-book/language-features/no-sanitize.html
     ///
     /// ### Example
     ///
     /// ```rust
-    /// #![feature(no_sanitize)]
+    /// #![feature(sanitize)]
     ///
     /// #[inline(always)]
-    /// #[no_sanitize(address)]
+    /// #[sanitize(address = "off")]
     /// fn x() {}
     ///
     /// fn main() {
@@ -2325,11 +2325,11 @@ declare_lint! {
     /// ### Explanation
     ///
     /// The use of the [`#[inline(always)]`][inline] attribute prevents the
-    /// the [`#[no_sanitize(...)]`][no_sanitize] attribute from working.
+    /// the [`#[sanitize(xyz = "off")]`][sanitize] attribute from working.
     /// Consider temporarily removing `inline` attribute.
     pub INLINE_NO_SANITIZE,
     Warn,
-    "detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`",
+    r#"detects incompatible use of `#[inline(always)]` and `#[sanitize(... = "off")]`"#,
 }
 
 declare_lint! {
diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
index 7d2fc0995aa..da17ac04c76 100644
--- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
+++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
@@ -61,8 +61,8 @@ pub struct CodegenFnAttrs {
     /// The `#[link_section = "..."]` attribute, or what executable section this
     /// should be placed in.
     pub link_section: Option<Symbol>,
-    /// The `#[no_sanitize(...)]` attribute. Indicates sanitizers for which
-    /// instrumentation should be disabled inside the annotated function.
+    /// The `#[sanitize(xyz = "off")]` attribute. Indicates sanitizers for which
+    /// instrumentation should be disabled inside the function.
     pub no_sanitize: SanitizerSet,
     /// The `#[instruction_set(set)]` attribute. Indicates if the generated code should
     /// be generated against a specific instruction set. Only usable on architectures which allow
diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs
index a8b357bf105..ea62461ebeb 100644
--- a/compiler/rustc_middle/src/query/erase.rs
+++ b/compiler/rustc_middle/src/query/erase.rs
@@ -343,6 +343,7 @@ trivial! {
     rustc_span::Symbol,
     rustc_span::Ident,
     rustc_target::spec::PanicStrategy,
+    rustc_target::spec::SanitizerSet,
     rustc_type_ir::Variance,
     u32,
     usize,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index f4d0120a2e7..3bb8353f49e 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -100,7 +100,7 @@ use rustc_session::lint::LintExpectationId;
 use rustc_span::def_id::LOCAL_CRATE;
 use rustc_span::source_map::Spanned;
 use rustc_span::{DUMMY_SP, Span, Symbol};
-use rustc_target::spec::PanicStrategy;
+use rustc_target::spec::{PanicStrategy, SanitizerSet};
 use {rustc_abi as abi, rustc_ast as ast, rustc_hir as hir};
 
 use crate::infer::canonical::{self, Canonical};
@@ -2686,6 +2686,16 @@ rustc_queries! {
         desc { |tcx| "looking up anon const kind of `{}`", tcx.def_path_str(def_id) }
         separate_provide_extern
     }
+
+    /// Checks for the nearest `#[sanitize(xyz = "off")]` or
+    /// `#[sanitize(xyz = "on")]` on this def and any enclosing defs, up to the
+    /// crate root.
+    ///
+    /// Returns the set of sanitizers that is explicitly disabled for this def.
+    query disabled_sanitizers_for(key: LocalDefId) -> SanitizerSet {
+        desc { |tcx| "checking what set of sanitizers are enabled on `{}`", tcx.def_path_str(key) }
+        feedable
+    }
 }
 
 rustc_with_all_queries! { define_callbacks! }
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index f7a5ba8194b..03096f205c6 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -454,10 +454,6 @@ passes_no_main_function =
     .teach_note = If you don't know the basics of Rust, you can go look to the Rust Book to get started: https://doc.rust-lang.org/book/
     .non_function_main = non-function item at `crate::main` is found
 
-passes_no_sanitize =
-    `#[no_sanitize({$attr_str})]` should be applied to {$accepted_kind}
-    .label = not {$accepted_kind}
-
 passes_non_exhaustive_with_default_field_values =
     `#[non_exhaustive]` can't be used to annotate items with default field values
     .label = this struct has default field values
@@ -552,6 +548,12 @@ passes_rustc_pub_transparent =
     attribute should be applied to `#[repr(transparent)]` types
     .label = not a `#[repr(transparent)]` type
 
+passes_sanitize_attribute_not_allowed =
+    sanitize attribute not allowed here
+    .not_fn_impl_mod = not a function, impl block, or module
+    .no_body = function has no body
+    .help = sanitize attribute can be applied to a function (with body), impl block, or module
+
 passes_should_be_applied_to_fn =
     attribute should be applied to a function definition
     .label = {$on_crate ->
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 3c329d20700..c1a212d7ab5 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -260,8 +260,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         [sym::diagnostic, sym::on_unimplemented, ..] => {
                             self.check_diagnostic_on_unimplemented(attr.span(), hir_id, target)
                         }
-                        [sym::no_sanitize, ..] => {
-                            self.check_no_sanitize(attr, span, 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(
@@ -483,39 +483,43 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         }
     }
 
-    fn check_no_sanitize(&self, attr: &Attribute, span: Span, target: Target) {
+    /// 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) {
+        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 sym = item.name();
-                match sym {
-                    Some(s @ sym::address | s @ sym::hwaddress) => {
-                        let is_valid =
-                            matches!(target, Target::Fn | Target::Method(..) | Target::Static);
-                        if !is_valid {
-                            self.dcx().emit_err(errors::NoSanitize {
-                                attr_span: item.span(),
-                                defn_span: span,
-                                accepted_kind: "a function or static",
-                                attr_str: s.as_str(),
-                            });
-                        }
+                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);
                     }
+
                     _ => {
-                        let is_valid = matches!(target, Target::Fn | Target::Method(..));
-                        if !is_valid {
-                            self.dcx().emit_err(errors::NoSanitize {
-                                attr_span: item.span(),
-                                defn_span: span,
-                                accepted_kind: "a function",
-                                attr_str: &match sym {
-                                    Some(name) => name.to_string(),
-                                    None => "...".to_string(),
-                                },
-                            });
-                        }
+                        not_fn_impl_mod = Some(target_span);
                     }
                 }
             }
+            self.dcx().emit_err(errors::SanitizeAttributeNotAllowed {
+                attr_span: attr.span(),
+                not_fn_impl_mod,
+                no_body,
+                help: (),
+            });
         }
     }
 
@@ -562,7 +566,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             }
         }
     }
-
     /// Checks if `#[collapse_debuginfo]` is applied to a macro.
     fn check_collapse_debuginfo(&self, attr: &Attribute, span: Span, target: Target) {
         match target {
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index f8ecf10714a..ab46ac2dd8b 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1489,15 +1489,21 @@ pub(crate) struct AttrCrateLevelOnlySugg {
     pub attr: Span,
 }
 
+/// "sanitize attribute not allowed here"
 #[derive(Diagnostic)]
-#[diag(passes_no_sanitize)]
-pub(crate) struct NoSanitize<'a> {
+#[diag(passes_sanitize_attribute_not_allowed)]
+pub(crate) struct SanitizeAttributeNotAllowed {
     #[primary_span]
     pub attr_span: Span,
-    #[label]
-    pub defn_span: Span,
-    pub accepted_kind: &'a str,
-    pub attr_str: &'a str,
+    /// "not a function, impl block, or module"
+    #[label(passes_not_fn_impl_mod)]
+    pub not_fn_impl_mod: Option<Span>,
+    /// "function has no body"
+    #[label(passes_no_body)]
+    pub no_body: Option<Span>,
+    /// "sanitize attribute can be applied to a function (with body), impl block, or module"
+    #[help]
+    pub help: (),
 }
 
 // FIXME(jdonszelmann): move back to rustc_attr
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 3ed483db3c4..65a52b27d3b 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1261,6 +1261,7 @@ symbols! {
         iterator,
         iterator_collect_fn,
         kcfi,
+        kernel_address,
         keylocker_x86,
         keyword,
         kind,
diff --git a/src/doc/rustc-dev-guide/src/sanitizers.md b/src/doc/rustc-dev-guide/src/sanitizers.md
index 29d9056c15d..34c78d4d952 100644
--- a/src/doc/rustc-dev-guide/src/sanitizers.md
+++ b/src/doc/rustc-dev-guide/src/sanitizers.md
@@ -45,7 +45,7 @@ implementation:
    [marked][sanitizer-attribute] with appropriate LLVM attribute:
    `SanitizeAddress`, `SanitizeHWAddress`, `SanitizeMemory`, or
    `SanitizeThread`. By default all functions are instrumented, but this
-   behaviour can be changed with `#[no_sanitize(...)]`.
+   behaviour can be changed with `#[sanitize(xyz = "on|off")]`.
 
 *  The decision whether to perform instrumentation or not is possible only at a
    function granularity. In the cases were those decision differ between
diff --git a/src/doc/unstable-book/src/language-features/no-sanitize.md b/src/doc/unstable-book/src/language-features/no-sanitize.md
deleted file mode 100644
index 28c683934d4..00000000000
--- a/src/doc/unstable-book/src/language-features/no-sanitize.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# `no_sanitize`
-
-The tracking issue for this feature is: [#39699]
-
-[#39699]: https://github.com/rust-lang/rust/issues/39699
-
-------------------------
-
-The `no_sanitize` attribute can be used to selectively disable sanitizer
-instrumentation in an annotated function. This might be useful to: avoid
-instrumentation overhead in a performance critical function, or avoid
-instrumenting code that contains constructs unsupported by given sanitizer.
-
-The precise effect of this annotation depends on particular sanitizer in use.
-For example, with `no_sanitize(thread)`, the thread sanitizer will no longer
-instrument non-atomic store / load operations, but it will instrument atomic
-operations to avoid reporting false positives and provide meaning full stack
-traces.
-
-## Examples
-
-``` rust
-#![feature(no_sanitize)]
-
-#[no_sanitize(address)]
-fn foo() {
-  // ...
-}
-```
diff --git a/src/doc/unstable-book/src/language-features/sanitize.md b/src/doc/unstable-book/src/language-features/sanitize.md
new file mode 100644
index 00000000000..fcd099cb36e
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/sanitize.md
@@ -0,0 +1,73 @@
+# `sanitize`
+
+The tracking issue for this feature is: [#39699]
+
+[#39699]: https://github.com/rust-lang/rust/issues/39699
+
+------------------------
+
+The `sanitize` attribute can be used to selectively disable or enable sanitizer
+instrumentation in an annotated function. This might be useful to: avoid
+instrumentation overhead in a performance critical function, or avoid
+instrumenting code that contains constructs unsupported by given sanitizer.
+
+The precise effect of this annotation depends on particular sanitizer in use.
+For example, with `sanitize(thread = "off")`, the thread sanitizer will no
+longer instrument non-atomic store / load operations, but it will instrument
+atomic operations to avoid reporting false positives and provide meaning full
+stack traces.
+
+This attribute was previously named `no_sanitize`.
+
+## Examples
+
+``` rust
+#![feature(sanitize)]
+
+#[sanitize(address = "off")]
+fn foo() {
+  // ...
+}
+```
+
+It is also possible to disable sanitizers for entire modules and enable them
+for single items or functions.
+
+```rust
+#![feature(sanitize)]
+
+#[sanitize(address = "off")]
+mod foo {
+  fn unsanitized() {
+    // ...
+  }
+
+  #[sanitize(address = "on")]
+  fn sanitized() {
+    // ...
+  }
+}
+```
+
+It's also applicable to impl blocks.
+
+```rust
+#![feature(sanitize)]
+
+trait MyTrait {
+  fn foo(&self);
+  fn bar(&self);
+}
+
+#[sanitize(address = "off")]
+impl MyTrait for () {
+  fn foo(&self) {
+    // ...
+  }
+
+  #[sanitize(address = "on")]
+  fn bar(&self) {
+    // ...
+  }
+}
+```
diff --git a/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-attr-no-sanitize.rs b/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-attr-sanitize-off.rs
index 71ccdc8ca62..651afb33228 100644
--- a/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-attr-no-sanitize.rs
+++ b/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-attr-sanitize-off.rs
@@ -4,11 +4,11 @@
 //@ compile-flags: -Clto -Cno-prepopulate-passes -Ctarget-feature=-crt-static -Zsanitizer=cfi -Copt-level=0
 
 #![crate_type = "lib"]
-#![feature(no_sanitize)]
+#![feature(sanitize)]
 
-#[no_sanitize(cfi)]
+#[sanitize(cfi = "off")]
 pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 {
-    // CHECK-LABEL: emit_type_checks_attr_no_sanitize::foo
+    // CHECK-LABEL: emit_type_checks_attr_sanitize_off::foo
     // CHECK:       Function Attrs: {{.*}}
     // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}}
     // CHECK:       start:
diff --git a/tests/codegen-llvm/sanitizer/kasan-emits-instrumentation.rs b/tests/codegen-llvm/sanitizer/kasan-emits-instrumentation.rs
index 774c9ab53f1..c70aae1703e 100644
--- a/tests/codegen-llvm/sanitizer/kasan-emits-instrumentation.rs
+++ b/tests/codegen-llvm/sanitizer/kasan-emits-instrumentation.rs
@@ -13,7 +13,7 @@
 //@[x86_64] needs-llvm-components: x86
 
 #![crate_type = "rlib"]
-#![feature(no_core, no_sanitize, lang_items)]
+#![feature(no_core, sanitize, lang_items)]
 #![no_core]
 
 extern crate minicore;
@@ -25,7 +25,7 @@ use minicore::*;
 // CHECK:       start:
 // CHECK-NOT:   call void @__asan_report_load
 // CHECK:       }
-#[no_sanitize(address)]
+#[sanitize(address = "off")]
 pub fn unsanitized(b: &mut u8) -> u8 {
     *b
 }
diff --git a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-sanitize-off.rs
index 02c31fb8e9b..2581784ce3e 100644
--- a/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs
+++ b/tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-sanitize-off.rs
@@ -9,15 +9,15 @@
 //@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Copt-level=0
 
 #![crate_type = "lib"]
-#![feature(no_core, no_sanitize, lang_items)]
+#![feature(no_core, sanitize, lang_items)]
 #![no_core]
 
 extern crate minicore;
 use minicore::*;
 
-#[no_sanitize(kcfi)]
+#[sanitize(kcfi = "off")]
 pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 {
-    // CHECK-LABEL: emit_kcfi_operand_bundle_attr_no_sanitize::foo
+    // CHECK-LABEL: emit_kcfi_operand_bundle_attr_sanitize_off::foo
     // CHECK:       Function Attrs: {{.*}}
     // CHECK-LABEL: define{{.*}}foo{{.*}}!{{<unknown kind #36>|kcfi_type}} !{{[0-9]+}}
     // CHECK:       start:
diff --git a/tests/codegen-llvm/sanitizer/sanitize-off-asan-kasan.rs b/tests/codegen-llvm/sanitizer/sanitize-off-asan-kasan.rs
new file mode 100644
index 00000000000..37549aba447
--- /dev/null
+++ b/tests/codegen-llvm/sanitizer/sanitize-off-asan-kasan.rs
@@ -0,0 +1,42 @@
+// Verifies that the `#[sanitize(address = "off")]` attribute also turns off
+// the kernel address sanitizer.
+//
+//@ add-core-stubs
+//@ compile-flags: -Zsanitizer=kernel-address -Ctarget-feature=-crt-static -Copt-level=0
+//@ revisions: aarch64 riscv64imac riscv64gc x86_64
+//@[aarch64] compile-flags: --target aarch64-unknown-none
+//@[aarch64] needs-llvm-components: aarch64
+//@[riscv64imac] compile-flags: --target riscv64imac-unknown-none-elf
+//@[riscv64imac] needs-llvm-components: riscv
+//@[riscv64gc] compile-flags: --target riscv64gc-unknown-none-elf
+//@[riscv64gc] needs-llvm-components: riscv
+//@[x86_64] compile-flags: --target x86_64-unknown-none
+//@[x86_64] needs-llvm-components: x86
+
+#![crate_type = "rlib"]
+#![feature(no_core, sanitize, lang_items)]
+#![no_core]
+
+extern crate minicore;
+use minicore::*;
+
+// CHECK-LABEL: ; sanitize_off_asan_kasan::unsanitized
+// CHECK-NEXT:  ; Function Attrs:
+// CHECK-NOT:   sanitize_address
+// CHECK:       start:
+// CHECK-NOT:   call void @__asan_report_load
+// CHECK:       }
+#[sanitize(address = "off")]
+pub fn unsanitized(b: &mut u8) -> u8 {
+    *b
+}
+
+// CHECK-LABEL: ; sanitize_off_asan_kasan::sanitized
+// CHECK-NEXT:  ; Function Attrs:
+// CHECK:       sanitize_address
+// CHECK:       start:
+// CHECK:       call void @__asan_report_load
+// CHECK:       }
+pub fn sanitized(b: &mut u8) -> u8 {
+    *b
+}
diff --git a/tests/codegen-llvm/sanitizer/no-sanitize-inlining.rs b/tests/codegen-llvm/sanitizer/sanitize-off-inlining.rs
index 4bd832d2ab1..69771827c3a 100644
--- a/tests/codegen-llvm/sanitizer/no-sanitize-inlining.rs
+++ b/tests/codegen-llvm/sanitizer/sanitize-off-inlining.rs
@@ -1,4 +1,4 @@
-// Verifies that no_sanitize attribute prevents inlining when
+// Verifies that sanitize(xyz = "off") attribute prevents inlining when
 // given sanitizer is enabled, but has no effect on inlining otherwise.
 //
 //@ needs-sanitizer-address
@@ -9,7 +9,7 @@
 //@[LSAN] compile-flags: -Zsanitizer=leak
 
 #![crate_type = "lib"]
-#![feature(no_sanitize)]
+#![feature(sanitize)]
 
 // ASAN-LABEL: define void @test
 // ASAN:         call {{.*}} @random_inline
@@ -23,7 +23,7 @@ pub fn test(n: &mut u32) {
     random_inline(n);
 }
 
-#[no_sanitize(address)]
+#[sanitize(address = "off")]
 #[inline]
 #[no_mangle]
 pub fn random_inline(n: &mut u32) {
diff --git a/tests/codegen-llvm/sanitizer/no-sanitize.rs b/tests/codegen-llvm/sanitizer/sanitize-off-kasan-asan.rs
index 2a309f6b9c6..94945f2b2e6 100644
--- a/tests/codegen-llvm/sanitizer/no-sanitize.rs
+++ b/tests/codegen-llvm/sanitizer/sanitize-off-kasan-asan.rs
@@ -1,34 +1,24 @@
-// Verifies that no_sanitize attribute can be used to
-// selectively disable sanitizer instrumentation.
+// Verifies that the `#[sanitize(kernel_address = "off")]` attribute also turns off
+// the address sanitizer.
 //
 //@ needs-sanitizer-address
 //@ compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static -Copt-level=0
 
 #![crate_type = "lib"]
-#![feature(no_sanitize)]
+#![feature(sanitize)]
 
-// CHECK:     @UNSANITIZED = constant{{.*}} no_sanitize_address
-// CHECK-NOT: @__asan_global_UNSANITIZED
-#[no_mangle]
-#[no_sanitize(address)]
-pub static UNSANITIZED: u32 = 0;
-
-// CHECK: @__asan_global_SANITIZED
-#[no_mangle]
-pub static SANITIZED: u32 = 0;
-
-// CHECK-LABEL: ; no_sanitize::unsanitized
+// CHECK-LABEL: ; sanitize_off_kasan_asan::unsanitized
 // CHECK-NEXT:  ; Function Attrs:
 // CHECK-NOT:   sanitize_address
 // CHECK:       start:
 // CHECK-NOT:   call void @__asan_report_load
 // CHECK:       }
-#[no_sanitize(address)]
+#[sanitize(kernel_address = "off")]
 pub fn unsanitized(b: &mut u8) -> u8 {
     *b
 }
 
-// CHECK-LABEL: ; no_sanitize::sanitized
+// CHECK-LABEL: ; sanitize_off_kasan_asan::sanitized
 // CHECK-NEXT:  ; Function Attrs:
 // CHECK:       sanitize_address
 // CHECK:       start:
diff --git a/tests/codegen-llvm/sanitizer/sanitize-off.rs b/tests/codegen-llvm/sanitizer/sanitize-off.rs
new file mode 100644
index 00000000000..9f3f7cd9df7
--- /dev/null
+++ b/tests/codegen-llvm/sanitizer/sanitize-off.rs
@@ -0,0 +1,138 @@
+// Verifies that the `#[sanitize(address = "off")]` attribute can be used to
+// selectively disable sanitizer instrumentation.
+//
+//@ needs-sanitizer-address
+//@ compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static -Copt-level=0
+
+#![crate_type = "lib"]
+#![feature(sanitize)]
+
+// CHECK:     @UNSANITIZED = constant{{.*}} no_sanitize_address
+// CHECK-NOT: @__asan_global_SANITIZED
+#[no_mangle]
+#[sanitize(address = "off")]
+pub static UNSANITIZED: u32 = 0;
+
+// CHECK: @__asan_global_SANITIZED
+#[no_mangle]
+pub static SANITIZED: u32 = 0;
+
+// CHECK-LABEL: ; sanitize_off::unsanitized
+// CHECK-NEXT:  ; Function Attrs:
+// CHECK-NOT:   sanitize_address
+// CHECK:       start:
+// CHECK-NOT:   call void @__asan_report_load
+// CHECK:       }
+#[sanitize(address = "off")]
+pub fn unsanitized(b: &mut u8) -> u8 {
+    *b
+}
+
+// CHECK-LABEL: ; sanitize_off::sanitized
+// CHECK-NEXT:  ; Function Attrs:
+// CHECK:       sanitize_address
+// CHECK:       start:
+// CHECK:       call void @__asan_report_load
+// CHECK:       }
+pub fn sanitized(b: &mut u8) -> u8 {
+    *b
+}
+
+#[sanitize(address = "off")]
+pub mod foo {
+    // CHECK-LABEL: ; sanitize_off::foo::unsanitized
+    // CHECK-NEXT:  ; Function Attrs:
+    // CHECK-NOT:   sanitize_address
+    // CHECK:       start:
+    // CHECK-NOT:   call void @__asan_report_load
+    // CHECK:       }
+    pub fn unsanitized(b: &mut u8) -> u8 {
+        *b
+    }
+
+    // CHECK-LABEL: ; sanitize_off::foo::sanitized
+    // CHECK-NEXT:  ; Function Attrs:
+    // CHECK:       sanitize_address
+    // CHECK:       start:
+    // CHECK:       call void @__asan_report_load
+    // CHECK:       }
+    #[sanitize(address = "on")]
+    pub fn sanitized(b: &mut u8) -> u8 {
+        *b
+    }
+}
+
+pub trait MyTrait {
+    fn unsanitized(&self, b: &mut u8) -> u8;
+    fn sanitized(&self, b: &mut u8) -> u8;
+
+    // CHECK-LABEL: ; sanitize_off::MyTrait::unsanitized_default
+    // CHECK-NEXT:  ; Function Attrs:
+    // CHECK-NOT:   sanitize_address
+    // CHECK:       start:
+    // CHECK-NOT:   call void @__asan_report_load
+    // CHECK:       }
+    #[sanitize(address = "off")]
+    fn unsanitized_default(&self, b: &mut u8) -> u8 {
+        *b
+    }
+
+    // CHECK-LABEL: ; sanitize_off::MyTrait::sanitized_default
+    // CHECK-NEXT:  ; Function Attrs:
+    // CHECK:       sanitize_address
+    // CHECK:       start:
+    // CHECK:       call void @__asan_report_load
+    // CHECK:       }
+    fn sanitized_default(&self, b: &mut u8) -> u8 {
+        *b
+    }
+}
+
+#[sanitize(address = "off")]
+impl MyTrait for () {
+    // CHECK-LABEL: ; <() as sanitize_off::MyTrait>::unsanitized
+    // CHECK-NEXT:  ; Function Attrs:
+    // CHECK-NOT:   sanitize_address
+    // CHECK:       start:
+    // CHECK-NOT:   call void @__asan_report_load
+    // CHECK:       }
+    fn unsanitized(&self, b: &mut u8) -> u8 {
+        *b
+    }
+
+    // CHECK-LABEL: ; <() as sanitize_off::MyTrait>::sanitized
+    // CHECK-NEXT:  ; Function Attrs:
+    // CHECK:       sanitize_address
+    // CHECK:       start:
+    // CHECK:       call void @__asan_report_load
+    // CHECK:       }
+    #[sanitize(address = "on")]
+    fn sanitized(&self, b: &mut u8) -> u8 {
+        *b
+    }
+}
+
+pub fn expose_trait(b: &mut u8) -> u8 {
+    <() as MyTrait>::unsanitized_default(&(), b);
+    <() as MyTrait>::sanitized_default(&(), b)
+}
+
+#[sanitize(address = "off")]
+pub mod outer {
+    #[sanitize(thread = "off")]
+    pub mod inner {
+        // CHECK-LABEL: ; sanitize_off::outer::inner::unsanitized
+        // CHECK-NEXT:  ; Function Attrs:
+        // CHECK-NOT:   sanitize_address
+        // CHECK:       start:
+        // CHECK-NOT:   call void @__asan_report_load
+        // CHECK:       }
+        pub fn unsanitized() {
+            let xs = [0, 1, 2, 3];
+            // Avoid optimizing everything out.
+            let xs = std::hint::black_box(xs.as_ptr());
+            let code = unsafe { *xs.offset(4) };
+            std::process::exit(code);
+        }
+    }
+}
diff --git a/tests/codegen-llvm/sanitizer/scs-attr-check.rs b/tests/codegen-llvm/sanitizer/scs-attr-check.rs
index 6f4cbc2c0a6..f726503503c 100644
--- a/tests/codegen-llvm/sanitizer/scs-attr-check.rs
+++ b/tests/codegen-llvm/sanitizer/scs-attr-check.rs
@@ -5,7 +5,7 @@
 //@ compile-flags: -Zsanitizer=shadow-call-stack
 
 #![crate_type = "lib"]
-#![feature(no_sanitize)]
+#![feature(sanitize)]
 
 // CHECK: ; sanitizer_scs_attr_check::scs
 // CHECK-NEXT: ; Function Attrs:{{.*}}shadowcallstack
@@ -13,5 +13,5 @@ pub fn scs() {}
 
 // CHECK: ; sanitizer_scs_attr_check::no_scs
 // CHECK-NOT: ; Function Attrs:{{.*}}shadowcallstack
-#[no_sanitize(shadow_call_stack)]
+#[sanitize(shadow_call_stack = "off")]
 pub fn no_scs() {}
diff --git a/tests/mir-opt/inline/inline_compatibility.rs b/tests/mir-opt/inline/inline_compatibility.rs
index 1bb102ccda5..a31153dedc9 100644
--- a/tests/mir-opt/inline/inline_compatibility.rs
+++ b/tests/mir-opt/inline/inline_compatibility.rs
@@ -3,7 +3,7 @@
 //@ compile-flags: -Cpanic=abort
 
 #![crate_type = "lib"]
-#![feature(no_sanitize)]
+#![feature(sanitize)]
 #![feature(c_variadic)]
 
 #[inline]
@@ -37,22 +37,22 @@ pub unsafe fn f2() {
 }
 
 #[inline]
-#[no_sanitize(address)]
-pub unsafe fn no_sanitize() {}
+#[sanitize(address = "off")]
+pub unsafe fn sanitize_off() {}
 
-// CHECK-LABEL: fn inlined_no_sanitize()
+// CHECK-LABEL: fn inlined_sanitize_off()
 // CHECK:       bb0: {
 // CHECK-NEXT:  return;
-#[no_sanitize(address)]
-pub unsafe fn inlined_no_sanitize() {
-    no_sanitize();
+#[sanitize(address = "off")]
+pub unsafe fn inlined_sanitize_off() {
+    sanitize_off();
 }
 
-// CHECK-LABEL: fn not_inlined_no_sanitize()
+// CHECK-LABEL: fn not_inlined_sanitize_off()
 // CHECK:       bb0: {
-// CHECK-NEXT:  no_sanitize()
-pub unsafe fn not_inlined_no_sanitize() {
-    no_sanitize();
+// CHECK-NEXT:  sanitize_off()
+pub unsafe fn not_inlined_sanitize_off() {
+    sanitize_off();
 }
 
 // CHECK-LABEL: fn not_inlined_c_variadic()
diff --git a/tests/ui/attributes/malformed-attrs.rs b/tests/ui/attributes/malformed-attrs.rs
index 3293f75fba9..90ca007451e 100644
--- a/tests/ui/attributes/malformed-attrs.rs
+++ b/tests/ui/attributes/malformed-attrs.rs
@@ -12,7 +12,7 @@
 #![feature(min_generic_const_args)]
 #![feature(ffi_const, ffi_pure)]
 #![feature(coverage_attribute)]
-#![feature(no_sanitize)]
+#![feature(sanitize)]
 #![feature(marker_trait_attr)]
 #![feature(thread_local)]
 #![feature(must_not_suspend)]
@@ -89,7 +89,7 @@
 //~^ ERROR malformed
 #[coverage]
 //~^ ERROR malformed `coverage` attribute input
-#[no_sanitize]
+#[sanitize]
 //~^ ERROR malformed
 #[ignore()]
 //~^ ERROR valid forms for the attribute are
diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr
index 9c31765149b..d1d9fef2a40 100644
--- a/tests/ui/attributes/malformed-attrs.stderr
+++ b/tests/ui/attributes/malformed-attrs.stderr
@@ -49,11 +49,23 @@ 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 `no_sanitize` attribute input
+error: malformed `sanitize` attribute input
   --> $DIR/malformed-attrs.rs:92:1
    |
-LL | #[no_sanitize]
-   | ^^^^^^^^^^^^^^ help: must be of the form: `#[no_sanitize(address, kcfi, memory, thread)]`
+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
diff --git a/tests/ui/attributes/no-sanitize.rs b/tests/ui/attributes/no-sanitize.rs
deleted file mode 100644
index ddf909be63a..00000000000
--- a/tests/ui/attributes/no-sanitize.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-#![feature(no_sanitize)]
-#![feature(stmt_expr_attributes)]
-#![deny(unused_attributes)]
-#![allow(dead_code)]
-
-fn invalid() {
-    #[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
-    {
-        1
-    };
-}
-
-#[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
-type InvalidTy = ();
-
-#[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
-mod invalid_module {}
-
-fn main() {
-    let _ = #[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
-    (|| 1);
-}
-
-#[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
-struct F;
-
-#[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
-impl F {
-    #[no_sanitize(memory)]
-    fn valid(&self) {}
-}
-
-#[no_sanitize(address, memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
-static INVALID : i32 = 0;
-
-#[no_sanitize(memory)]
-fn valid() {}
-
-#[no_sanitize(address)]
-static VALID : i32 = 0;
-
-#[no_sanitize("address")]
-//~^ ERROR `#[no_sanitize(...)]` should be applied to a function
-//~| ERROR invalid argument for `no_sanitize`
-static VALID2 : i32 = 0;
diff --git a/tests/ui/attributes/no-sanitize.stderr b/tests/ui/attributes/no-sanitize.stderr
deleted file mode 100644
index 8d5fbb109ea..00000000000
--- a/tests/ui/attributes/no-sanitize.stderr
+++ /dev/null
@@ -1,80 +0,0 @@
-error: `#[no_sanitize(memory)]` should be applied to a function
-  --> $DIR/no-sanitize.rs:7:19
-   |
-LL |       #[no_sanitize(memory)]
-   |                     ^^^^^^
-LL | /     {
-LL | |         1
-LL | |     };
-   | |_____- not a function
-
-error: `#[no_sanitize(memory)]` should be applied to a function
-  --> $DIR/no-sanitize.rs:13:15
-   |
-LL | #[no_sanitize(memory)]
-   |               ^^^^^^
-LL | type InvalidTy = ();
-   | -------------------- not a function
-
-error: `#[no_sanitize(memory)]` should be applied to a function
-  --> $DIR/no-sanitize.rs:16:15
-   |
-LL | #[no_sanitize(memory)]
-   |               ^^^^^^
-LL | mod invalid_module {}
-   | --------------------- not a function
-
-error: `#[no_sanitize(memory)]` should be applied to a function
-  --> $DIR/no-sanitize.rs:20:27
-   |
-LL |     let _ = #[no_sanitize(memory)]
-   |                           ^^^^^^
-LL |     (|| 1);
-   |     ------ not a function
-
-error: `#[no_sanitize(memory)]` should be applied to a function
-  --> $DIR/no-sanitize.rs:24:15
-   |
-LL | #[no_sanitize(memory)]
-   |               ^^^^^^
-LL | struct F;
-   | --------- not a function
-
-error: `#[no_sanitize(memory)]` should be applied to a function
-  --> $DIR/no-sanitize.rs:27:15
-   |
-LL |   #[no_sanitize(memory)]
-   |                 ^^^^^^
-LL | / impl F {
-LL | |     #[no_sanitize(memory)]
-LL | |     fn valid(&self) {}
-LL | | }
-   | |_- not a function
-
-error: `#[no_sanitize(memory)]` should be applied to a function
-  --> $DIR/no-sanitize.rs:33:24
-   |
-LL | #[no_sanitize(address, memory)]
-   |                        ^^^^^^
-LL | static INVALID : i32 = 0;
-   | ------------------------- not a function
-
-error: `#[no_sanitize(...)]` should be applied to a function
-  --> $DIR/no-sanitize.rs:42:15
-   |
-LL | #[no_sanitize("address")]
-   |               ^^^^^^^^^
-...
-LL | static VALID2 : i32 = 0;
-   | ------------------------ not a function
-
-error: invalid argument for `no_sanitize`
-  --> $DIR/no-sanitize.rs:42:15
-   |
-LL | #[no_sanitize("address")]
-   |               ^^^^^^^^^
-   |
-   = note: expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`
-
-error: aborting due to 9 previous errors
-
diff --git a/tests/ui/feature-gates/feature-gate-no_sanitize.rs b/tests/ui/feature-gates/feature-gate-no_sanitize.rs
deleted file mode 100644
index 5ac014f1c5b..00000000000
--- a/tests/ui/feature-gates/feature-gate-no_sanitize.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-#[no_sanitize(address)]
-//~^ ERROR the `#[no_sanitize]` attribute is an experimental feature
-fn main() {
-}
diff --git a/tests/ui/feature-gates/feature-gate-no_sanitize.stderr b/tests/ui/feature-gates/feature-gate-no_sanitize.stderr
deleted file mode 100644
index a33bf6a9e40..00000000000
--- a/tests/ui/feature-gates/feature-gate-no_sanitize.stderr
+++ /dev/null
@@ -1,13 +0,0 @@
-error[E0658]: the `#[no_sanitize]` attribute is an experimental feature
-  --> $DIR/feature-gate-no_sanitize.rs:1:1
-   |
-LL | #[no_sanitize(address)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: see issue #39699 <https://github.com/rust-lang/rust/issues/39699> for more information
-   = help: add `#![feature(no_sanitize)]` to the crate attributes to enable
-   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/feature-gates/feature-gate-sanitize.rs b/tests/ui/feature-gates/feature-gate-sanitize.rs
new file mode 100644
index 00000000000..40098d93272
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-sanitize.rs
@@ -0,0 +1,7 @@
+//@ normalize-stderr: "you are using [0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?( \([^)]*\))?" -> "you are using $$RUSTC_VERSION"
+#![feature(no_sanitize)] //~ ERROR feature has been removed
+
+#[sanitize(address = "on")]
+//~^ ERROR the `#[sanitize]` attribute is an experimental feature
+fn main() {
+}
diff --git a/tests/ui/feature-gates/feature-gate-sanitize.stderr b/tests/ui/feature-gates/feature-gate-sanitize.stderr
new file mode 100644
index 00000000000..7c38b351916
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-sanitize.stderr
@@ -0,0 +1,23 @@
+error[E0557]: feature has been removed
+  --> $DIR/feature-gate-sanitize.rs:2:12
+   |
+LL | #![feature(no_sanitize)]
+   |            ^^^^^^^^^^^ feature has been removed
+   |
+   = note: removed in CURRENT_RUSTC_VERSION; see <https://github.com/rust-lang/rust/pull/142681> for more information
+   = note: renamed to sanitize(xyz = "on|off")
+
+error[E0658]: the `#[sanitize]` attribute is an experimental feature
+  --> $DIR/feature-gate-sanitize.rs:4:1
+   |
+LL | #[sanitize(address = "on")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #39699 <https://github.com/rust-lang/rust/issues/39699> for more information
+   = help: add `#![feature(sanitize)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error: aborting due to 2 previous errors
+
+Some errors have detailed explanations: E0557, E0658.
+For more information about an error, try `rustc --explain E0557`.
diff --git a/tests/ui/invalid/invalid-no-sanitize.rs b/tests/ui/invalid/invalid-no-sanitize.rs
deleted file mode 100644
index b52e3cc83fa..00000000000
--- a/tests/ui/invalid/invalid-no-sanitize.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-#![feature(no_sanitize)]
-
-#[no_sanitize(brontosaurus)] //~ ERROR invalid argument
-fn main() {
-}
diff --git a/tests/ui/invalid/invalid-no-sanitize.stderr b/tests/ui/invalid/invalid-no-sanitize.stderr
deleted file mode 100644
index b1c80438b31..00000000000
--- a/tests/ui/invalid/invalid-no-sanitize.stderr
+++ /dev/null
@@ -1,10 +0,0 @@
-error: invalid argument for `no_sanitize`
-  --> $DIR/invalid-no-sanitize.rs:3:15
-   |
-LL | #[no_sanitize(brontosaurus)]
-   |               ^^^^^^^^^^^^
-   |
-   = note: expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`
-
-error: aborting due to 1 previous error
-
diff --git a/tests/ui/sanitize-attr/invalid-sanitize.rs b/tests/ui/sanitize-attr/invalid-sanitize.rs
new file mode 100644
index 00000000000..49dc01c8daa
--- /dev/null
+++ b/tests/ui/sanitize-attr/invalid-sanitize.rs
@@ -0,0 +1,22 @@
+#![feature(sanitize)]
+
+#[sanitize(brontosaurus = "off")] //~ ERROR invalid argument
+fn main() {
+}
+
+#[sanitize(address = "off")] //~ ERROR multiple `sanitize` attributes
+#[sanitize(address = "off")]
+fn multiple_consistent() {}
+
+#[sanitize(address = "on")] //~ ERROR multiple `sanitize` attributes
+#[sanitize(address = "off")]
+fn multiple_inconsistent() {}
+
+#[sanitize(address = "bogus")] //~ ERROR invalid argument for `sanitize`
+fn wrong_value() {}
+
+#[sanitize = "off"] //~ ERROR malformed `sanitize` attribute input
+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
new file mode 100644
index 00000000000..4bf81770b89
--- /dev/null
+++ b/tests/ui/sanitize-attr/invalid-sanitize.stderr
@@ -0,0 +1,82 @@
+error: malformed `sanitize` attribute input
+  --> $DIR/invalid-sanitize.rs:18:1
+   |
+LL | #[sanitize = "off"]
+   | ^^^^^^^^^^^^^^^^^^^
+   |
+help: the following are the possible correct uses
+   |
+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: 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
+   |
+LL | #[sanitize(address = "off")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
+   |
+note: attribute also specified here
+  --> $DIR/invalid-sanitize.rs:8:1
+   |
+LL | #[sanitize(address = "off")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: multiple `sanitize` attributes
+  --> $DIR/invalid-sanitize.rs:11:1
+   |
+LL | #[sanitize(address = "on")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
+   |
+note: attribute also specified here
+  --> $DIR/invalid-sanitize.rs:12:1
+   |
+LL | #[sanitize(address = "off")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: invalid argument for `sanitize`
+  --> $DIR/invalid-sanitize.rs:3:1
+   |
+LL | #[sanitize(brontosaurus = "off")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread`
+
+error: invalid argument for `sanitize`
+  --> $DIR/invalid-sanitize.rs:15:1
+   |
+LL | #[sanitize(address = "bogus")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread`
+
+error: aborting due to 6 previous errors
+
diff --git a/tests/ui/sanitize-attr/valid-sanitize.rs b/tests/ui/sanitize-attr/valid-sanitize.rs
new file mode 100644
index 00000000000..ebe76fcba04
--- /dev/null
+++ b/tests/ui/sanitize-attr/valid-sanitize.rs
@@ -0,0 +1,115 @@
+//! Tests where the `#[sanitize(..)]` attribute can and cannot be used.
+
+#![feature(sanitize)]
+#![feature(extern_types)]
+#![feature(impl_trait_in_assoc_type)]
+#![warn(unused_attributes)]
+#![sanitize(address = "off", thread = "on")]
+
+#[sanitize(address = "off", thread = "on")]
+mod submod {}
+
+#[sanitize(address = "off")]
+static FOO: u32 = 0;
+
+#[sanitize(thread = "off")] //~ ERROR sanitize attribute not allowed here
+static BAR: u32 = 0;
+
+#[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+type MyTypeAlias = ();
+
+#[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+trait MyTrait {
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    const TRAIT_ASSOC_CONST: u32;
+
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    type TraitAssocType;
+
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    fn trait_method(&self);
+
+    #[sanitize(address = "off", thread = "on")]
+    fn trait_method_with_default(&self) {}
+
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    fn trait_assoc_fn();
+}
+
+#[sanitize(address = "off")]
+impl MyTrait for () {
+    const TRAIT_ASSOC_CONST: u32 = 0;
+
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    type TraitAssocType = Self;
+
+    #[sanitize(address = "off", thread = "on")]
+    fn trait_method(&self) {}
+    #[sanitize(address = "off", thread = "on")]
+    fn trait_method_with_default(&self) {}
+    #[sanitize(address = "off", thread = "on")]
+    fn trait_assoc_fn() {}
+}
+
+trait HasAssocType {
+    type T;
+    fn constrain_assoc_type() -> Self::T;
+}
+
+impl HasAssocType for () {
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    type T = impl Copy;
+    fn constrain_assoc_type() -> Self::T {}
+}
+
+#[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+struct MyStruct {
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    field: u32,
+}
+
+#[sanitize(address = "off", thread = "on")]
+impl MyStruct {
+    #[sanitize(address = "off", thread = "on")]
+    fn method(&self) {}
+    #[sanitize(address = "off", thread = "on")]
+    fn assoc_fn() {}
+}
+
+extern "C" {
+    #[sanitize(address = "off", thread = "on")] //~ ERROR sanitize attribute not allowed here
+    static X: u32;
+
+    #[sanitize(address = "off", thread = "on")] //~ ERROR sanitize attribute not allowed here
+    type T;
+
+    #[sanitize(address = "off", thread = "on")] //~ ERROR sanitize attribute not allowed here
+    fn foreign_fn();
+}
+
+#[sanitize(address = "off", thread = "on")]
+fn main() {
+    #[sanitize(address = "off", thread = "on")] //~ ERROR sanitize attribute not allowed here
+    let _ = ();
+
+    // Currently not allowed on let statements, even if they bind to a closure.
+    // It might be nice to support this as a special case someday, but trying
+    // to define the precise boundaries of that special case might be tricky.
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    let _let_closure = || ();
+
+    // In situations where attributes can already be applied to expressions,
+    // the sanitize attribute is allowed on closure expressions.
+    let _closure_tail_expr = {
+        #[sanitize(address = "off", thread = "on")]
+        || ()
+    };
+
+    match () {
+        #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+        () => (),
+    }
+
+    #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
+    return ();
+}
diff --git a/tests/ui/sanitize-attr/valid-sanitize.stderr b/tests/ui/sanitize-attr/valid-sanitize.stderr
new file mode 100644
index 00000000000..ff9fe63eaf5
--- /dev/null
+++ b/tests/ui/sanitize-attr/valid-sanitize.stderr
@@ -0,0 +1,190 @@
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:15:1
+   |
+LL | #[sanitize(thread = "off")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | static BAR: u32 = 0;
+   | -------------------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:18:1
+   |
+LL | #[sanitize(address = "off")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | type MyTypeAlias = ();
+   | ---------------------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:21:1
+   |
+LL |   #[sanitize(address = "off")]
+   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | / trait MyTrait {
+LL | |     #[sanitize(address = "off")]
+LL | |     const TRAIT_ASSOC_CONST: u32;
+...  |
+LL | |     fn trait_assoc_fn();
+LL | | }
+   | |_- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:65:1
+   |
+LL |   #[sanitize(address = "off")]
+   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | / struct MyStruct {
+LL | |     #[sanitize(address = "off")]
+LL | |     field: u32,
+LL | | }
+   | |_- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:67:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     field: u32,
+   |     ---------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:92:5
+   |
+LL |     #[sanitize(address = "off", thread = "on")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     let _ = ();
+   |     ----------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:98:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     let _let_closure = || ();
+   |     ------------------------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:109:9
+   |
+LL |         #[sanitize(address = "off")]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         () => (),
+   |         -------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:113:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     return ();
+   |     --------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:23:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     const TRAIT_ASSOC_CONST: u32;
+   |     ----------------------------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:26:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     type TraitAssocType;
+   |     -------------------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:29:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     fn trait_method(&self);
+   |     ----------------------- function has no body
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:35:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     fn trait_assoc_fn();
+   |     -------------------- function has no body
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:43:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     type TraitAssocType = Self;
+   |     --------------------------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:60:5
+   |
+LL |     #[sanitize(address = "off")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     type T = impl Copy;
+   |     ------------------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:80:5
+   |
+LL |     #[sanitize(address = "off", thread = "on")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     static X: u32;
+   |     -------------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:83:5
+   |
+LL |     #[sanitize(address = "off", thread = "on")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     type T;
+   |     ------- not a function, impl block, or module
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: sanitize attribute not allowed here
+  --> $DIR/valid-sanitize.rs:86:5
+   |
+LL |     #[sanitize(address = "off", thread = "on")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     fn foreign_fn();
+   |     ---------------- function has no body
+   |
+   = help: sanitize attribute can be applied to a function (with body), impl block, or module
+
+error: aborting due to 18 previous errors
+
diff --git a/tests/ui/sanitizer/inline-always.rs b/tests/ui/sanitizer/inline-always-sanitize.rs
index d92daee3026..d6ee214e9b3 100644
--- a/tests/ui/sanitizer/inline-always.rs
+++ b/tests/ui/sanitizer/inline-always-sanitize.rs
@@ -1,11 +1,11 @@
 //@ check-pass
 
-#![feature(no_sanitize)]
+#![feature(sanitize)]
 
 #[inline(always)]
 //~^ NOTE inlining requested here
-#[no_sanitize(address)]
-//~^ WARN will have no effect after inlining
+#[sanitize(address = "off")]
+//~^ WARN  setting `sanitize` off will have no effect after inlining
 //~| NOTE on by default
 fn x() {
 }
diff --git a/tests/ui/sanitizer/inline-always-sanitize.stderr b/tests/ui/sanitizer/inline-always-sanitize.stderr
new file mode 100644
index 00000000000..ed479472169
--- /dev/null
+++ b/tests/ui/sanitizer/inline-always-sanitize.stderr
@@ -0,0 +1,15 @@
+warning: setting `sanitize` off will have no effect after inlining
+  --> $DIR/inline-always-sanitize.rs:7:1
+   |
+LL | #[sanitize(address = "off")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: inlining requested here
+  --> $DIR/inline-always-sanitize.rs:5:1
+   |
+LL | #[inline(always)]
+   | ^^^^^^^^^^^^^^^^^
+   = note: `#[warn(inline_no_sanitize)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/sanitizer/inline-always.stderr b/tests/ui/sanitizer/inline-always.stderr
deleted file mode 100644
index 74fba3c0e0e..00000000000
--- a/tests/ui/sanitizer/inline-always.stderr
+++ /dev/null
@@ -1,15 +0,0 @@
-warning: `no_sanitize` will have no effect after inlining
-  --> $DIR/inline-always.rs:7:1
-   |
-LL | #[no_sanitize(address)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^
-   |
-note: inlining requested here
-  --> $DIR/inline-always.rs:5:1
-   |
-LL | #[inline(always)]
-   | ^^^^^^^^^^^^^^^^^
-   = note: `#[warn(inline_no_sanitize)]` on by default
-
-warning: 1 warning emitted
-
diff --git a/triagebot.toml b/triagebot.toml
index 2f31a30019b..777ef928e5d 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -579,7 +579,7 @@ trigger_files = [
     "src/doc/unstable-book/src/compiler-flags/sanitizer.md",
     "src/doc/unstable-book/src/language-features/cfg-sanitize.md",
     "src/doc/unstable-book/src/language-features/cfi-encoding.md",
-    "src/doc/unstable-book/src/language-features/no-sanitize.md",
+    "src/doc/unstable-book/src/language-features/sanitize.md",
     "tests/codegen-llvm/sanitizer",
     "tests/codegen-llvm/split-lto-unit.rs",
     "tests/codegen-llvm/stack-probes-inline.rs",
@@ -1209,7 +1209,7 @@ cc = ["@rust-lang/project-exploit-mitigations", "@rcvalle"]
 [mentions."src/doc/unstable-book/src/language-features/cfi-encoding.md"]
 cc = ["@rust-lang/project-exploit-mitigations", "@rcvalle"]
 
-[mentions."src/doc/unstable-book/src/language-features/no-sanitize.md"]
+[mentions."src/doc/unstable-book/src/language-features/sanitize.md"]
 cc = ["@rust-lang/project-exploit-mitigations", "@rcvalle"]
 
 [mentions."src/doc/rustc/src/check-cfg.md"]