Skip to content

Commit 58b95c9

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 64b185e commit 58b95c9

File tree

21 files changed

+743
-7
lines changed

21 files changed

+743
-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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_attr_data_structures::{
99
use rustc_hir::def::DefKind;
1010
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
1111
use rustc_hir::weak_lang_items::WEAK_LANG_ITEMS;
12-
use rustc_hir::{self as hir, LangItem, lang_items};
12+
use rustc_hir::{self as hir, Attribute, LangItem, lang_items};
1313
use rustc_middle::middle::codegen_fn_attrs::{
1414
CodegenFnAttrFlags, CodegenFnAttrs, PatchableFunctionEntry,
1515
};
@@ -87,6 +87,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
8787

8888
let mut link_ordinal_span = None;
8989
let mut no_sanitize_span = None;
90+
let mut sanitize_span = None;
9091

9192
for attr in attrs.iter() {
9293
// In some cases, attribute are only valid on functions, but it's the `check_attr`
@@ -289,6 +290,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
289290
}
290291
}
291292
}
293+
sym::sanitize => sanitize_span = Some(attr.span()),
292294
sym::instruction_set => {
293295
codegen_fn_attrs.instruction_set =
294296
attr.meta_item_list().and_then(|l| match &l[..] {
@@ -389,6 +391,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
389391
codegen_fn_attrs.alignment =
390392
Ord::max(codegen_fn_attrs.alignment, tcx.sess.opts.unstable_opts.min_function_alignment);
391393

394+
// Compute the disabled sanitizers.
395+
codegen_fn_attrs.no_sanitize |= tcx.disabled_sanitizers_for(did);
392396
// On trait methods, inherit the `#[align]` of the trait's method prototype.
393397
codegen_fn_attrs.alignment = Ord::max(codegen_fn_attrs.alignment, tcx.inherited_align(did));
394398

@@ -463,6 +467,16 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
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)) = (sanitize_span, inline_span)
473+
{
474+
let hir_id = tcx.local_def_id_to_hir_id(did);
475+
tcx.node_span_lint(lint::builtin::INLINE_NO_SANITIZE, hir_id, sanitize_span, |lint| {
476+
lint.primary_message("setting `sanitize` off will have no effect after inlining");
477+
lint.span_note(inline_span, "inlining requested here");
478+
})
479+
}
466480

467481
// Weak lang items have the same semantics as "std internal" symbols in the
468482
// sense that they're preserved through all our LTO passes and only
@@ -555,6 +569,84 @@ fn opt_trait_item(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
555569
}
556570
}
557571

572+
/// For an attr that has the `sanitize` attribute, read the list of
573+
/// disabled sanitizers.
574+
fn parse_sanitize_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> SanitizerSet {
575+
let mut result = SanitizerSet::empty();
576+
if let Some(list) = attr.meta_item_list() {
577+
for item in list.iter() {
578+
let MetaItemInner::MetaItem(set) = item else {
579+
tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() });
580+
break;
581+
};
582+
let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
583+
match segments.as_slice() {
584+
[sym::address] if set.value_str() == Some(sym::off) => {
585+
result |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
586+
}
587+
[sym::address] if set.value_str() == Some(sym::on) => {
588+
result &= !SanitizerSet::ADDRESS;
589+
result &= !SanitizerSet::KERNELADDRESS;
590+
}
591+
[sym::cfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::CFI,
592+
[sym::cfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::CFI,
593+
[sym::kcfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::KCFI,
594+
[sym::kcfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::KCFI,
595+
[sym::memory] if set.value_str() == Some(sym::off) => {
596+
result |= SanitizerSet::MEMORY
597+
}
598+
[sym::memory] if set.value_str() == Some(sym::on) => {
599+
result &= !SanitizerSet::MEMORY
600+
}
601+
[sym::memtag] if set.value_str() == Some(sym::off) => {
602+
result |= SanitizerSet::MEMTAG
603+
}
604+
[sym::memtag] if set.value_str() == Some(sym::on) => {
605+
result &= !SanitizerSet::MEMTAG
606+
}
607+
[sym::shadow_call_stack] if set.value_str() == Some(sym::off) => {
608+
result |= SanitizerSet::SHADOWCALLSTACK
609+
}
610+
[sym::shadow_call_stack] if set.value_str() == Some(sym::on) => {
611+
result &= !SanitizerSet::SHADOWCALLSTACK
612+
}
613+
[sym::thread] if set.value_str() == Some(sym::off) => {
614+
result |= SanitizerSet::THREAD
615+
}
616+
[sym::thread] if set.value_str() == Some(sym::on) => {
617+
result &= !SanitizerSet::THREAD
618+
}
619+
[sym::hwaddress] if set.value_str() == Some(sym::off) => {
620+
result |= SanitizerSet::HWADDRESS
621+
}
622+
[sym::hwaddress] if set.value_str() == Some(sym::on) => {
623+
result &= !SanitizerSet::HWADDRESS
624+
}
625+
_ => {
626+
tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() });
627+
}
628+
}
629+
}
630+
}
631+
result
632+
}
633+
634+
fn disabled_sanitizers_for(tcx: TyCtxt<'_>, did: LocalDefId) -> SanitizerSet {
635+
// Check for a sanitize annotation directly on this def.
636+
if let Some(attr) = tcx.get_attr(did, sym::sanitize) {
637+
return parse_sanitize_attr(tcx, attr);
638+
}
639+
640+
// Otherwise backtrack.
641+
match tcx.opt_local_parent(did) {
642+
// Check the parent (recursively).
643+
Some(parent) => tcx.disabled_sanitizers_for(parent),
644+
// We reached the crate root without seeing an attribute, so
645+
// there is no sanitizers to exclude.
646+
None => SanitizerSet::empty(),
647+
}
648+
}
649+
558650
/// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller
559651
/// applied to the method prototype.
560652
fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
@@ -733,6 +825,11 @@ fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option<AutoDiffAttrs> {
733825
}
734826

735827
pub(crate) fn provide(providers: &mut Providers) {
736-
*providers =
737-
Providers { codegen_fn_attrs, should_inherit_track_caller, inherited_align, ..*providers };
828+
*providers = Providers {
829+
codegen_fn_attrs,
830+
should_inherit_track_caller,
831+
inherited_align,
832+
disabled_sanitizers_for,
833+
..*providers
834+
};
738835
}

compiler/rustc_codegen_ssa/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,14 @@ pub(crate) struct InvalidNoSanitize {
11081108
pub span: Span,
11091109
}
11101110

1111+
#[derive(Diagnostic)]
1112+
#[diag(codegen_ssa_invalid_sanitize)]
1113+
#[note]
1114+
pub(crate) struct InvalidSanitize {
1115+
#[primary_span]
1116+
pub span: Span,
1117+
}
1118+
11111119
#[derive(Diagnostic)]
11121120
#[diag(codegen_ssa_invalid_link_ordinal_nargs)]
11131121
#[note]

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,10 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
544544
template!(List: "address, kcfi, memory, thread"), DuplicatesOk,
545545
EncodeCrossCrate::No, experimental!(no_sanitize)
546546
),
547+
gated!(
548+
sanitize, Normal, template!(List: r#"address = "on|off", cfi = "on|off""#), ErrorPreceding,
549+
EncodeCrossCrate::No, sanitize, experimental!(sanitize),
550+
),
547551
gated!(
548552
coverage, Normal, template!(OneOf: &[sym::off, sym::on]),
549553
ErrorPreceding, EncodeCrossCrate::No,

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ declare_features! (
625625
(unstable, return_type_notation, "1.70.0", Some(109417)),
626626
/// Allows `extern "rust-cold"`.
627627
(unstable, rust_cold_cc, "1.63.0", Some(97544)),
628+
/// Allows the use of the `sanitize` attribute.
629+
(unstable, sanitize, "CURRENT_RUSTC_VERSION", Some(39699)),
628630
/// Allows the use of SIMD types in functions declared in `extern` blocks.
629631
(unstable, simd_ffi, "1.0.0", Some(27731)),
630632
/// 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
@@ -342,6 +342,7 @@ trivial! {
342342
rustc_span::Symbol,
343343
rustc_span::Ident,
344344
rustc_target::spec::PanicStrategy,
345+
rustc_target::spec::SanitizerSet,
345346
rustc_type_ir::Variance,
346347
u32,
347348
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_attr_data_structures as attr, rustc_hir as hir};
105105

106106
use crate::infer::canonical::{self, Canonical};
@@ -2669,6 +2669,16 @@ rustc_queries! {
26692669
desc { |tcx| "looking up anon const kind of `{}`", tcx.def_path_str(def_id) }
26702670
separate_provide_extern
26712671
}
2672+
2673+
/// Checks for the nearest `#[sanitize(xyz = "off")]` or
2674+
/// `#[sanitize(xyz = "on")]` on this def and any enclosing defs, up to the
2675+
/// crate root.
2676+
///
2677+
/// Returns the set of sanitizers that is explicitly disabled for this def.
2678+
query disabled_sanitizers_for(key: LocalDefId) -> SanitizerSet {
2679+
desc { |tcx| "checking what set of sanitizers are enabled on `{}`", tcx.def_path_str(key) }
2680+
feedable
2681+
}
26722682
}
26732683

26742684
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
@@ -663,6 +663,12 @@ passes_rustc_std_internal_symbol =
663663
attribute should be applied to functions or statics
664664
.label = not a function or static
665665
666+
passes_sanitize_attribute_not_allowed =
667+
sanitize attribute not allowed here
668+
.not_fn_impl_mod = not a function, impl block, or module
669+
.no_body = function has no body
670+
.help = sanitize attribute can be applied to a function (with body), impl block, or module
671+
666672
passes_should_be_applied_to_fn =
667673
attribute should be applied to a function definition
668674
.label = {$on_crate ->

compiler/rustc_passes/src/check_attr.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
292292
[sym::no_sanitize, ..] => {
293293
self.check_no_sanitize(attr, span, target)
294294
}
295+
[sym::sanitize, ..] => {
296+
self.check_sanitize(attr, span, target)
297+
}
295298
[sym::thread_local, ..] => self.check_thread_local(attr, span, target),
296299
[sym::doc, ..] => self.check_doc_attrs(
297300
attr,
@@ -664,6 +667,46 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
664667
}
665668
}
666669

670+
/// Checks that the `#[sanitize(..)]` attribute is applied to a
671+
/// function/closure/method, or to an impl block or module.
672+
fn check_sanitize(&self, attr: &Attribute, target_span: Span, target: Target) {
673+
let mut not_fn_impl_mod = None;
674+
let mut no_body = None;
675+
676+
if let Some(list) = attr.meta_item_list() {
677+
for item in list.iter() {
678+
let MetaItemInner::MetaItem(set) = item else {
679+
return;
680+
};
681+
let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
682+
match target {
683+
Target::Fn
684+
| Target::Closure
685+
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent)
686+
| Target::Impl
687+
| Target::Mod => return,
688+
Target::Static if matches!(segments.as_slice(), [sym::address]) => return,
689+
690+
// These are "functions", but they aren't allowed because they don't
691+
// have a body, so the usual explanation would be confusing.
692+
Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
693+
no_body = Some(target_span);
694+
}
695+
696+
_ => {
697+
not_fn_impl_mod = Some(target_span);
698+
}
699+
}
700+
}
701+
self.dcx().emit_err(errors::SanitizeAttributeNotAllowed {
702+
attr_span: attr.span(),
703+
not_fn_impl_mod,
704+
no_body,
705+
help: (),
706+
});
707+
}
708+
}
709+
667710
/// FIXME: Remove when all attributes are ported to the new parser
668711
fn check_generic_attr_unparsed(
669712
&self,

compiler/rustc_passes/src/errors.rs

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

1756+
/// "sanitize attribute not allowed here"
1757+
#[derive(Diagnostic)]
1758+
#[diag(passes_sanitize_attribute_not_allowed)]
1759+
pub(crate) struct SanitizeAttributeNotAllowed {
1760+
#[primary_span]
1761+
pub attr_span: Span,
1762+
/// "not a function, impl block, or module"
1763+
#[label(passes_not_fn_impl_mod)]
1764+
pub not_fn_impl_mod: Option<Span>,
1765+
/// "function has no body"
1766+
#[label(passes_no_body)]
1767+
pub no_body: Option<Span>,
1768+
/// "sanitize attribute can be applied to a function (with body), impl block, or module"
1769+
#[help]
1770+
pub help: (),
1771+
}
1772+
17561773
// FIXME(jdonszelmann): move back to rustc_attr
17571774
#[derive(Diagnostic)]
17581775
#[diag(passes_rustc_const_stable_indirect_pairing)]

0 commit comments

Comments
 (0)