Skip to content

Commit 3ef065b

Browse files
committed
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.
1 parent 425a9c0 commit 3ef065b

File tree

21 files changed

+771
-7
lines changed

21 files changed

+771
-7
lines changed

compiler/rustc_codegen_ssa/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ codegen_ssa_invalid_monomorphization_unsupported_symbol_of_size = invalid monomo
174174
codegen_ssa_invalid_no_sanitize = invalid argument for `no_sanitize`
175175
.note = expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`
176176
177+
codegen_ssa_invalid_sanitize = invalid argument for `sanitize`
178+
.note = expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`
179+
177180
codegen_ssa_invalid_windows_subsystem = invalid windows subsystem `{$subsystem}`, only `windows` and `console` are allowed
178181
179182
codegen_ssa_ld64_unimplemented_modifier = `as-needed` modifier not implemented yet for ld64

compiler/rustc_codegen_ssa/src/codegen_attrs.rs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ fn parse_patchable_function_entry(
162162
struct InterestingAttributeDiagnosticSpans {
163163
link_ordinal: Option<Span>,
164164
no_sanitize: Option<Span>,
165+
sanitize: Option<Span>,
165166
inline: Option<Span>,
166167
no_mangle: Option<Span>,
167168
}
@@ -335,6 +336,7 @@ fn process_builtin_attrs(
335336
codegen_fn_attrs.no_sanitize |=
336337
parse_no_sanitize_attr(tcx, attr).unwrap_or_default();
337338
}
339+
sym::sanitize => interesting_spans.sanitize = Some(attr.span()),
338340
sym::instruction_set => {
339341
codegen_fn_attrs.instruction_set = parse_instruction_set_attr(tcx, attr)
340342
}
@@ -358,6 +360,8 @@ fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut Code
358360
codegen_fn_attrs.alignment =
359361
Ord::max(codegen_fn_attrs.alignment, tcx.sess.opts.unstable_opts.min_function_alignment);
360362

363+
// Compute the disabled sanitizers.
364+
codegen_fn_attrs.no_sanitize |= tcx.disabled_sanitizers_for(did);
361365
// On trait methods, inherit the `#[align]` of the trait's method prototype.
362366
codegen_fn_attrs.alignment = Ord::max(codegen_fn_attrs.alignment, tcx.inherited_align(did));
363367

@@ -463,6 +467,17 @@ fn check_result(
463467
lint.span_note(inline_span, "inlining requested here");
464468
})
465469
}
470+
if !codegen_fn_attrs.no_sanitize.is_empty()
471+
&& codegen_fn_attrs.inline.always()
472+
&& let (Some(sanitize_span), Some(inline_span)) =
473+
(interesting_spans.sanitize, interesting_spans.inline)
474+
{
475+
let hir_id = tcx.local_def_id_to_hir_id(did);
476+
tcx.node_span_lint(lint::builtin::INLINE_NO_SANITIZE, hir_id, sanitize_span, |lint| {
477+
lint.primary_message("setting `sanitize` off will have no effect after inlining");
478+
lint.span_note(inline_span, "inlining requested here");
479+
})
480+
}
466481

467482
// error when specifying link_name together with link_ordinal
468483
if let Some(_) = codegen_fn_attrs.link_name
@@ -585,6 +600,84 @@ fn opt_trait_item(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
585600
}
586601
}
587602

603+
/// For an attr that has the `sanitize` attribute, read the list of
604+
/// disabled sanitizers.
605+
fn parse_sanitize_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> SanitizerSet {
606+
let mut result = SanitizerSet::empty();
607+
if let Some(list) = attr.meta_item_list() {
608+
for item in list.iter() {
609+
let MetaItemInner::MetaItem(set) = item else {
610+
tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() });
611+
break;
612+
};
613+
let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
614+
match segments.as_slice() {
615+
[sym::address] if set.value_str() == Some(sym::off) => {
616+
result |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
617+
}
618+
[sym::address] if set.value_str() == Some(sym::on) => {
619+
result &= !SanitizerSet::ADDRESS;
620+
result &= !SanitizerSet::KERNELADDRESS;
621+
}
622+
[sym::cfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::CFI,
623+
[sym::cfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::CFI,
624+
[sym::kcfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::KCFI,
625+
[sym::kcfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::KCFI,
626+
[sym::memory] if set.value_str() == Some(sym::off) => {
627+
result |= SanitizerSet::MEMORY
628+
}
629+
[sym::memory] if set.value_str() == Some(sym::on) => {
630+
result &= !SanitizerSet::MEMORY
631+
}
632+
[sym::memtag] if set.value_str() == Some(sym::off) => {
633+
result |= SanitizerSet::MEMTAG
634+
}
635+
[sym::memtag] if set.value_str() == Some(sym::on) => {
636+
result &= !SanitizerSet::MEMTAG
637+
}
638+
[sym::shadow_call_stack] if set.value_str() == Some(sym::off) => {
639+
result |= SanitizerSet::SHADOWCALLSTACK
640+
}
641+
[sym::shadow_call_stack] if set.value_str() == Some(sym::on) => {
642+
result &= !SanitizerSet::SHADOWCALLSTACK
643+
}
644+
[sym::thread] if set.value_str() == Some(sym::off) => {
645+
result |= SanitizerSet::THREAD
646+
}
647+
[sym::thread] if set.value_str() == Some(sym::on) => {
648+
result &= !SanitizerSet::THREAD
649+
}
650+
[sym::hwaddress] if set.value_str() == Some(sym::off) => {
651+
result |= SanitizerSet::HWADDRESS
652+
}
653+
[sym::hwaddress] if set.value_str() == Some(sym::on) => {
654+
result &= !SanitizerSet::HWADDRESS
655+
}
656+
_ => {
657+
tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() });
658+
}
659+
}
660+
}
661+
}
662+
result
663+
}
664+
665+
fn disabled_sanitizers_for(tcx: TyCtxt<'_>, did: LocalDefId) -> SanitizerSet {
666+
// Check for a sanitize annotation directly on this def.
667+
if let Some(attr) = tcx.get_attr(did, sym::sanitize) {
668+
return parse_sanitize_attr(tcx, attr);
669+
}
670+
671+
// Otherwise backtrack.
672+
match tcx.opt_local_parent(did) {
673+
// Check the parent (recursively).
674+
Some(parent) => tcx.disabled_sanitizers_for(parent),
675+
// We reached the crate root without seeing an attribute, so
676+
// there is no sanitizers to exclude.
677+
None => SanitizerSet::empty(),
678+
}
679+
}
680+
588681
/// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller
589682
/// applied to the method prototype.
590683
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> {
709802
}
710803

711804
pub(crate) fn provide(providers: &mut Providers) {
712-
*providers =
713-
Providers { codegen_fn_attrs, should_inherit_track_caller, inherited_align, ..*providers };
805+
*providers = Providers {
806+
codegen_fn_attrs,
807+
should_inherit_track_caller,
808+
inherited_align,
809+
disabled_sanitizers_for,
810+
..*providers
811+
};
714812
}

compiler/rustc_codegen_ssa/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,14 @@ pub(crate) struct InvalidNoSanitize {
11281128
pub span: Span,
11291129
}
11301130

1131+
#[derive(Diagnostic)]
1132+
#[diag(codegen_ssa_invalid_sanitize)]
1133+
#[note]
1134+
pub(crate) struct InvalidSanitize {
1135+
#[primary_span]
1136+
pub span: Span,
1137+
}
1138+
11311139
#[derive(Diagnostic)]
11321140
#[diag(codegen_ssa_target_feature_safe_trait)]
11331141
pub(crate) struct TargetFeatureSafeTrait {

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,10 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
745745
template!(List: &["address, kcfi, memory, thread"]), DuplicatesOk,
746746
EncodeCrossCrate::No, experimental!(no_sanitize)
747747
),
748+
gated!(
749+
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,
750+
EncodeCrossCrate::No, sanitize, experimental!(sanitize),
751+
),
748752
gated!(
749753
coverage, Normal, template!(OneOf: &[sym::off, sym::on]),
750754
ErrorPreceding, EncodeCrossCrate::No,

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,8 @@ declare_features! (
626626
(unstable, return_type_notation, "1.70.0", Some(109417)),
627627
/// Allows `extern "rust-cold"`.
628628
(unstable, rust_cold_cc, "1.63.0", Some(97544)),
629+
/// Allows the use of the `sanitize` attribute.
630+
(unstable, sanitize, "CURRENT_RUSTC_VERSION", Some(39699)),
629631
/// Allows the use of SIMD types in functions declared in `extern` blocks.
630632
(unstable, simd_ffi, "1.0.0", Some(27731)),
631633
/// Allows specialization of implementations (RFC 1210).

compiler/rustc_middle/src/query/erase.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ trivial! {
343343
rustc_span::Symbol,
344344
rustc_span::Ident,
345345
rustc_target::spec::PanicStrategy,
346+
rustc_target::spec::SanitizerSet,
346347
rustc_type_ir::Variance,
347348
u32,
348349
usize,

compiler/rustc_middle/src/query/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ use rustc_session::lint::LintExpectationId;
100100
use rustc_span::def_id::LOCAL_CRATE;
101101
use rustc_span::source_map::Spanned;
102102
use rustc_span::{DUMMY_SP, Span, Symbol};
103-
use rustc_target::spec::PanicStrategy;
103+
use rustc_target::spec::{PanicStrategy, SanitizerSet};
104104
use {rustc_abi as abi, rustc_ast as ast, rustc_hir as hir};
105105

106106
use crate::infer::canonical::{self, Canonical};
@@ -2686,6 +2686,16 @@ rustc_queries! {
26862686
desc { |tcx| "looking up anon const kind of `{}`", tcx.def_path_str(def_id) }
26872687
separate_provide_extern
26882688
}
2689+
2690+
/// Checks for the nearest `#[sanitize(xyz = "off")]` or
2691+
/// `#[sanitize(xyz = "on")]` on this def and any enclosing defs, up to the
2692+
/// crate root.
2693+
///
2694+
/// Returns the set of sanitizers that is explicitly disabled for this def.
2695+
query disabled_sanitizers_for(key: LocalDefId) -> SanitizerSet {
2696+
desc { |tcx| "checking what set of sanitizers are enabled on `{}`", tcx.def_path_str(key) }
2697+
feedable
2698+
}
26892699
}
26902700

26912701
rustc_with_all_queries! { define_callbacks! }

compiler/rustc_passes/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,12 @@ passes_rustc_pub_transparent =
542542
attribute should be applied to `#[repr(transparent)]` types
543543
.label = not a `#[repr(transparent)]` type
544544
545+
passes_sanitize_attribute_not_allowed =
546+
sanitize attribute not allowed here
547+
.not_fn_impl_mod = not a function, impl block, or module
548+
.no_body = function has no body
549+
.help = sanitize attribute can be applied to a function (with body), impl block, or module
550+
545551
passes_should_be_applied_to_fn =
546552
attribute should be applied to a function definition
547553
.label = {$on_crate ->

compiler/rustc_passes/src/check_attr.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
261261
[sym::no_sanitize, ..] => {
262262
self.check_no_sanitize(attr, span, target)
263263
}
264+
[sym::sanitize, ..] => {
265+
self.check_sanitize(attr, span, target)
266+
}
264267
[sym::thread_local, ..] => self.check_thread_local(attr, span, target),
265268
[sym::doc, ..] => self.check_doc_attrs(
266269
attr,
@@ -518,6 +521,46 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
518521
}
519522
}
520523

524+
/// Checks that the `#[sanitize(..)]` attribute is applied to a
525+
/// function/closure/method, or to an impl block or module.
526+
fn check_sanitize(&self, attr: &Attribute, target_span: Span, target: Target) {
527+
let mut not_fn_impl_mod = None;
528+
let mut no_body = None;
529+
530+
if let Some(list) = attr.meta_item_list() {
531+
for item in list.iter() {
532+
let MetaItemInner::MetaItem(set) = item else {
533+
return;
534+
};
535+
let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
536+
match target {
537+
Target::Fn
538+
| Target::Closure
539+
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent)
540+
| Target::Impl { .. }
541+
| Target::Mod => return,
542+
Target::Static if matches!(segments.as_slice(), [sym::address]) => return,
543+
544+
// These are "functions", but they aren't allowed because they don't
545+
// have a body, so the usual explanation would be confusing.
546+
Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
547+
no_body = Some(target_span);
548+
}
549+
550+
_ => {
551+
not_fn_impl_mod = Some(target_span);
552+
}
553+
}
554+
}
555+
self.dcx().emit_err(errors::SanitizeAttributeNotAllowed {
556+
attr_span: attr.span(),
557+
not_fn_impl_mod,
558+
no_body,
559+
help: (),
560+
});
561+
}
562+
}
563+
521564
/// Checks if `#[naked]` is applied to a function definition.
522565
fn check_naked(&self, hir_id: HirId, target: Target) {
523566
match target {
@@ -561,7 +604,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
561604
}
562605
}
563606
}
564-
565607
/// Checks if `#[collapse_debuginfo]` is applied to a macro.
566608
fn check_collapse_debuginfo(&self, attr: &Attribute, span: Span, target: Target) {
567609
match target {

compiler/rustc_passes/src/errors.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,23 @@ pub(crate) struct NoSanitize<'a> {
14991499
pub attr_str: &'a str,
15001500
}
15011501

1502+
/// "sanitize attribute not allowed here"
1503+
#[derive(Diagnostic)]
1504+
#[diag(passes_sanitize_attribute_not_allowed)]
1505+
pub(crate) struct SanitizeAttributeNotAllowed {
1506+
#[primary_span]
1507+
pub attr_span: Span,
1508+
/// "not a function, impl block, or module"
1509+
#[label(passes_not_fn_impl_mod)]
1510+
pub not_fn_impl_mod: Option<Span>,
1511+
/// "function has no body"
1512+
#[label(passes_no_body)]
1513+
pub no_body: Option<Span>,
1514+
/// "sanitize attribute can be applied to a function (with body), impl block, or module"
1515+
#[help]
1516+
pub help: (),
1517+
}
1518+
15021519
// FIXME(jdonszelmann): move back to rustc_attr
15031520
#[derive(Diagnostic)]
15041521
#[diag(passes_rustc_const_stable_indirect_pairing)]

0 commit comments

Comments
 (0)