about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorBastian Kersting <bkersting@google.com>2025-06-18 12:53:34 +0000
committerBastian Kersting <bkersting@google.com>2025-08-18 08:30:00 +0000
commit3ef065bf872ce62a18336dca0daf47b3e9f7da64 (patch)
treeb4dccd0b519642cac3b2f057995f5b378c8abc01 /compiler
parent425a9c0a0e365c0b8c6cfd00c2ded83a73bed9a0 (diff)
downloadrust-3ef065bf872ce62a18336dca0daf47b3e9f7da64.tar.gz
rust-3ef065bf872ce62a18336dca0daf47b3e9f7da64.zip
Implement the #[sanitize(..)] attribute
This change implements the #[sanitize(..)] attribute, which opts to
replace the currently unstable #[no_sanitize]. Essentially the new
attribute works similar as #[no_sanitize], just with more flexible
options regarding where it is applied. E.g. it is possible to turn
a certain sanitizer either on or off:
`#[sanitize(address = "on|off")]`

This attribute now also applies to more places, e.g. it is possible
to turn off a sanitizer for an entire module or impl block:
```rust
\#[sanitize(address = "off")]
mod foo {
    fn unsanitized(..) {}

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

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

This attribute is enabled behind the unstable `sanitize` feature.
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_codegen_ssa/messages.ftl3
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs102
-rw-r--r--compiler/rustc_codegen_ssa/src/errors.rs8
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs4
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-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.ftl6
-rw-r--r--compiler/rustc_passes/src/check_attr.rs44
-rw-r--r--compiler/rustc_passes/src/errors.rs17
10 files changed, 195 insertions, 4 deletions
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index b6cfea88363..75cc60587b8 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -174,6 +174,9 @@ codegen_ssa_invalid_monomorphization_unsupported_symbol_of_size = invalid monomo
 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`, `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 af70f0deb07..068559497dd 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -162,6 +162,7 @@ fn parse_patchable_function_entry(
 struct InterestingAttributeDiagnosticSpans {
     link_ordinal: Option<Span>,
     no_sanitize: Option<Span>,
+    sanitize: Option<Span>,
     inline: Option<Span>,
     no_mangle: Option<Span>,
 }
@@ -335,6 +336,7 @@ fn process_builtin_attrs(
                 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 +360,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));
 
@@ -463,6 +467,17 @@ fn check_result(
             lint.span_note(inline_span, "inlining requested here");
         })
     }
+    if !codegen_fn_attrs.no_sanitize.is_empty()
+        && codegen_fn_attrs.inline.always()
+        && let (Some(sanitize_span), Some(inline_span)) =
+            (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, sanitize_span, |lint| {
+            lint.primary_message("setting `sanitize` off will have no effect after inlining");
+            lint.span_note(inline_span, "inlining requested here");
+        })
+    }
 
     // error when specifying link_name together with link_ordinal
     if let Some(_) = codegen_fn_attrs.link_name
@@ -585,6 +600,84 @@ 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.
+fn parse_sanitize_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> SanitizerSet {
+    let mut result = SanitizerSet::empty();
+    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() {
+                [sym::address] if set.value_str() == Some(sym::off) => {
+                    result |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
+                }
+                [sym::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 {
+    // 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);
+    }
+
+    // Otherwise backtrack.
+    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(),
+    }
+}
+
 /// 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 +802,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..913e0025f2e 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -1129,6 +1129,14 @@ pub(crate) struct InvalidNoSanitize {
 }
 
 #[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_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 6fbedaf5b10..14fbf12f150 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -746,6 +746,10 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         EncodeCrossCrate::No, experimental!(no_sanitize)
     ),
     gated!(
+        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]),
         ErrorPreceding, EncodeCrossCrate::No,
         coverage_attribute, experimental!(coverage)
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index 07f928b8c88..4fb4b7fc8b7 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -626,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_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 7481b0ea960..0e9d556afdd 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -542,6 +542,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 0e28c51e981..e51f3657eaa 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -261,6 +261,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         [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(
                             attr,
@@ -518,6 +521,46 @@ 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) {
+        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);
+                    }
+
+                    _ => {
+                        not_fn_impl_mod = Some(target_span);
+                    }
+                }
+            }
+            self.dcx().emit_err(errors::SanitizeAttributeNotAllowed {
+                attr_span: attr.span(),
+                not_fn_impl_mod,
+                no_body,
+                help: (),
+            });
+        }
+    }
+
     /// Checks if `#[naked]` is applied to a function definition.
     fn check_naked(&self, hir_id: HirId, target: Target) {
         match target {
@@ -561,7 +604,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 c5d5155d0e5..bd608dc2fee 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1499,6 +1499,23 @@ pub(crate) struct NoSanitize<'a> {
     pub attr_str: &'a str,
 }
 
+/// "sanitize attribute not allowed here"
+#[derive(Diagnostic)]
+#[diag(passes_sanitize_attribute_not_allowed)]
+pub(crate) struct SanitizeAttributeNotAllowed {
+    #[primary_span]
+    pub attr_span: Span,
+    /// "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
 #[derive(Diagnostic)]
 #[diag(passes_rustc_const_stable_indirect_pairing)]