Skip to content

Commit b846b42

Browse files
committed
Selectively disable sanitizer instrumentation
Add `no_sanitize` attribute that allows to opt out from sanitizer instrumentation in an annotated function.
1 parent eda1a7a commit b846b42

File tree

18 files changed

+255
-17
lines changed

18 files changed

+255
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# `no_sanitize`
2+
3+
The tracking issue for this feature is: [#39699]
4+
5+
[#39699]: https://github.com/rust-lang/rust/issues/39699
6+
7+
------------------------
8+
9+
The `no_sanitize` attribute can be used to selectively disable sanitizer
10+
instrumentation in an annotated function. This might be useful to: avoid
11+
instrumentation overhead in a performance critical function, or avoid
12+
instrumenting code that contains constructs unsupported by given sanitizer.
13+
14+
The precise effect of this annotation depends on particular sanitizer in use.
15+
For example, with `no_sanitize(thread)`, the thread sanitizer will no longer
16+
instrument non-atomic store / load operations, but it will instrument atomic
17+
operations to avoid reporting false positives and provide meaning full stack
18+
traces.
19+
20+
## Examples
21+
22+
``` rust
23+
#![feature(no_sanitize)]
24+
25+
#[no_sanitize(address)]
26+
fn foo() {
27+
// ...
28+
}
29+
```

src/librustc/middle/codegen_fn_attrs.rs

+6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ bitflags! {
7272
const FFI_RETURNS_TWICE = 1 << 10;
7373
/// `#[track_caller]`: allow access to the caller location
7474
const TRACK_CALLER = 1 << 11;
75+
/// `#[no_sanitize(address)]`: disables address sanitizer instrumentation
76+
const NO_SANITIZE_ADDRESS = 1 << 12;
77+
/// `#[no_sanitize(memory)]`: disables memory sanitizer instrumentation
78+
const NO_SANITIZE_MEMORY = 1 << 13;
79+
/// `#[no_sanitize(thread)]`: disables thread sanitizer instrumentation
80+
const NO_SANITIZE_THREAD = 1 << 14;
7581
}
7682
}
7783

src/librustc_codegen_llvm/attributes.rs

+20
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,26 @@ pub fn from_fn_attrs(
288288
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) {
289289
Attribute::NoAlias.apply_llfn(llvm::AttributePlace::ReturnValue, llfn);
290290
}
291+
if let Some(ref sanitizer) = cx.tcx.sess.opts.debugging_opts.sanitizer {
292+
match *sanitizer {
293+
Sanitizer::Address => {
294+
if !codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_ADDRESS) {
295+
llvm::Attribute::SanitizeAddress.apply_llfn(Function, llfn);
296+
}
297+
}
298+
Sanitizer::Memory => {
299+
if !codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_MEMORY) {
300+
llvm::Attribute::SanitizeMemory.apply_llfn(Function, llfn);
301+
}
302+
}
303+
Sanitizer::Thread => {
304+
if !codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_THREAD) {
305+
llvm::Attribute::SanitizeThread.apply_llfn(Function, llfn);
306+
}
307+
}
308+
Sanitizer::Leak => {}
309+
}
310+
}
291311

292312
unwind(
293313
llfn,

src/librustc_codegen_llvm/declare.rs

-16
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use crate::llvm::AttributePlace::Function;
1919
use crate::type_::Type;
2020
use crate::value::Value;
2121
use log::debug;
22-
use rustc::session::config::Sanitizer;
2322
use rustc::ty::Ty;
2423
use rustc_codegen_ssa::traits::*;
2524
use rustc_data_structures::small_c_str::SmallCStr;
@@ -47,21 +46,6 @@ fn declare_raw_fn(
4746
llvm::Attribute::NoRedZone.apply_llfn(Function, llfn);
4847
}
4948

50-
if let Some(ref sanitizer) = cx.tcx.sess.opts.debugging_opts.sanitizer {
51-
match *sanitizer {
52-
Sanitizer::Address => {
53-
llvm::Attribute::SanitizeAddress.apply_llfn(Function, llfn);
54-
}
55-
Sanitizer::Memory => {
56-
llvm::Attribute::SanitizeMemory.apply_llfn(Function, llfn);
57-
}
58-
Sanitizer::Thread => {
59-
llvm::Attribute::SanitizeThread.apply_llfn(Function, llfn);
60-
}
61-
_ => {}
62-
}
63-
}
64-
6549
attributes::default_optimisation_attrs(cx.tcx.sess, llfn);
6650
attributes::non_lazy_bind(cx.sess(), llfn);
6751
llfn

src/librustc_feature/active.rs

+3
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,9 @@ declare_features! (
541541
/// Allows `T: ?const Trait` syntax in bounds.
542542
(active, const_trait_bound_opt_out, "1.42.0", Some(67794), None),
543543

544+
/// Allows the use of `no_sanitize` attribute.
545+
(active, no_sanitize, "1.42.0", Some(39699), None),
546+
544547
// -------------------------------------------------------------------------
545548
// feature-group-end: actual feature gates
546549
// -------------------------------------------------------------------------

src/librustc_feature/builtin_attrs.rs

+5
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
261261
ungated!(cold, Whitelisted, template!(Word)),
262262
ungated!(no_builtins, Whitelisted, template!(Word)),
263263
ungated!(target_feature, Whitelisted, template!(List: r#"enable = "name""#)),
264+
gated!(
265+
no_sanitize, Whitelisted,
266+
template!(List: "address, memory, thread"),
267+
experimental!(no_sanitize)
268+
),
264269

265270
// FIXME: #14408 whitelist docs since rustdoc looks at them
266271
ungated!(doc, Whitelisted, template!(List: "hidden|inline|...", NameValueStr: "string")),

src/librustc_mir/transform/inline.rs

+23
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_index::vec::{Idx, IndexVec};
88
use rustc::middle::codegen_fn_attrs::CodegenFnAttrFlags;
99
use rustc::mir::visit::*;
1010
use rustc::mir::*;
11+
use rustc::session::config::Sanitizer;
1112
use rustc::ty::subst::{InternalSubsts, Subst, SubstsRef};
1213
use rustc::ty::{self, Instance, InstanceDef, ParamEnv, Ty, TyCtxt, TypeFoldable};
1314

@@ -228,6 +229,28 @@ impl Inliner<'tcx> {
228229
return false;
229230
}
230231

232+
// Avoid inlining functions marked as no_sanitize if sanitizer is enabled,
233+
// since instrumentation might be enabled and performed on the caller.
234+
match self.tcx.sess.opts.debugging_opts.sanitizer {
235+
Some(Sanitizer::Address) => {
236+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_ADDRESS) {
237+
return false;
238+
}
239+
}
240+
Some(Sanitizer::Memory) => {
241+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_MEMORY) {
242+
return false;
243+
}
244+
}
245+
Some(Sanitizer::Thread) => {
246+
if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_THREAD) {
247+
return false;
248+
}
249+
}
250+
Some(Sanitizer::Leak) => {}
251+
None => {}
252+
}
253+
231254
let hinted = match codegen_fn_attrs.inline {
232255
// Just treat inline(always) as a hint for now,
233256
// there are cases that prevent inlining that we

src/librustc_session/lint/builtin.rs

+7
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,12 @@ declare_lint! {
474474
};
475475
}
476476

477+
declare_lint! {
478+
pub INLINE_NO_SANITIZE,
479+
Warn,
480+
"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`",
481+
}
482+
477483
declare_lint_pass! {
478484
/// Does nothing as a lint pass, but registers some `Lint`s
479485
/// that are used by other parts of the compiler.
@@ -537,5 +543,6 @@ declare_lint_pass! {
537543
MUTABLE_BORROW_RESERVATION_CONFLICT,
538544
INDIRECT_STRUCTURAL_MATCH,
539545
SOFT_UNSTABLE,
546+
INLINE_NO_SANITIZE,
540547
]
541548
}

src/librustc_span/symbol.rs

+4
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ symbols! {
120120
abi_vectorcall,
121121
abi_x86_interrupt,
122122
aborts,
123+
address,
123124
add_with_overflow,
124125
advanced_slice_patterns,
125126
adx_target_feature,
@@ -445,6 +446,7 @@ symbols! {
445446
mem_uninitialized,
446447
mem_zeroed,
447448
member_constraints,
449+
memory,
448450
message,
449451
meta,
450452
min_align_of,
@@ -487,6 +489,7 @@ symbols! {
487489
None,
488490
non_exhaustive,
489491
non_modrs_mods,
492+
no_sanitize,
490493
no_stack_check,
491494
no_start,
492495
no_std,
@@ -721,6 +724,7 @@ symbols! {
721724
test_removed_feature,
722725
test_runner,
723726
then_with,
727+
thread,
724728
thread_local,
725729
tool_attributes,
726730
tool_lints,

src/librustc_typeck/collect.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -2743,6 +2743,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
27432743

27442744
let mut inline_span = None;
27452745
let mut link_ordinal_span = None;
2746+
let mut no_sanitize_span = None;
27462747
for attr in attrs.iter() {
27472748
if attr.check_name(sym::cold) {
27482749
codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD;
@@ -2832,6 +2833,24 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
28322833
if let ordinal @ Some(_) = check_link_ordinal(tcx, attr) {
28332834
codegen_fn_attrs.link_ordinal = ordinal;
28342835
}
2836+
} else if attr.check_name(sym::no_sanitize) {
2837+
no_sanitize_span = Some(attr.span);
2838+
if let Some(list) = attr.meta_item_list() {
2839+
for item in list.iter() {
2840+
if item.check_name(sym::address) {
2841+
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_ADDRESS;
2842+
} else if item.check_name(sym::memory) {
2843+
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_MEMORY;
2844+
} else if item.check_name(sym::thread) {
2845+
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_THREAD;
2846+
} else {
2847+
tcx.sess
2848+
.struct_span_err(item.span(), "invalid argument for `no_sanitize`")
2849+
.note("expected one of: `address`, `memory` or `thread`")
2850+
.emit();
2851+
}
2852+
}
2853+
}
28352854
}
28362855
}
28372856

@@ -2911,7 +2930,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
29112930
// purpose functions as they wouldn't have the right target features
29122931
// enabled. For that reason we also forbid #[inline(always)] as it can't be
29132932
// respected.
2914-
29152933
if codegen_fn_attrs.target_features.len() > 0 {
29162934
if codegen_fn_attrs.inline == InlineAttr::Always {
29172935
if let Some(span) = inline_span {
@@ -2924,6 +2942,25 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
29242942
}
29252943
}
29262944

2945+
let no_sanitize_flags = CodegenFnAttrFlags::NO_SANITIZE_ADDRESS
2946+
| CodegenFnAttrFlags::NO_SANITIZE_MEMORY
2947+
| CodegenFnAttrFlags::NO_SANITIZE_THREAD;
2948+
if codegen_fn_attrs.flags.intersects(no_sanitize_flags) {
2949+
if codegen_fn_attrs.inline == InlineAttr::Always {
2950+
if let (Some(no_sanitize_span), Some(inline_span)) = (no_sanitize_span, inline_span) {
2951+
let hir_id = tcx.hir().as_local_hir_id(id).unwrap();
2952+
tcx.struct_span_lint_hir(
2953+
lint::builtin::INLINE_NO_SANITIZE,
2954+
hir_id,
2955+
no_sanitize_span,
2956+
"`no_sanitize` will have no effect after inlining",
2957+
)
2958+
.span_note(inline_span, "inlining requested here")
2959+
.emit();
2960+
}
2961+
}
2962+
}
2963+
29272964
// Weak lang items have the same semantics as "std internal" symbols in the
29282965
// sense that they're preserved through all our LTO passes and only
29292966
// strippable by the linker.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Verifies that no_sanitize attribute prevents inlining when
2+
// given sanitizer is enabled, but has no effect on inlining otherwise.
3+
//
4+
// needs-sanitizer-support
5+
// only-x86_64
6+
//
7+
// revisions: ASAN LSAN
8+
//
9+
//[ASAN] compile-flags: -Zsanitizer=address -C opt-level=3 -Z mir-opt-level=3
10+
//[LSAN] compile-flags: -Zsanitizer=leak -C opt-level=3 -Z mir-opt-level=3
11+
12+
#![crate_type="lib"]
13+
#![feature(no_sanitize)]
14+
15+
// ASAN-LABEL: define void @test
16+
// ASAN: tail call fastcc void @random_inline
17+
// ASAN: }
18+
//
19+
// LSAN-LABEL: define void @test
20+
// LSAN-NO: call
21+
// LSAN: }
22+
#[no_mangle]
23+
pub fn test(n: &mut u32) {
24+
random_inline(n);
25+
}
26+
27+
#[no_sanitize(address)]
28+
#[inline]
29+
#[no_mangle]
30+
pub fn random_inline(n: &mut u32) {
31+
*n = 42;
32+
}
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Verifies that no_sanitze attribute can be used to
2+
// selectively disable sanitizer instrumentation.
3+
//
4+
// needs-sanitizer-support
5+
// compile-flags: -Zsanitizer=address
6+
7+
#![crate_type="lib"]
8+
#![feature(no_sanitize)]
9+
10+
// CHECK-LABEL: ; sanitizer_no_sanitize::unsanitized
11+
// CHECK-NEXT: ; Function Attrs:
12+
// CHECK-NOT: sanitize_address
13+
// CHECK: start:
14+
// CHECK-NOT: call void @__asan_report_load
15+
// CHECK: }
16+
#[no_sanitize(address)]
17+
pub fn unsanitized(b: &mut u8) -> u8 {
18+
*b
19+
}
20+
21+
// CHECK-LABEL: ; sanitizer_no_sanitize::sanitized
22+
// CHECK-NEXT: ; Function Attrs:
23+
// CHECK: sanitize_address
24+
// CHECK: start:
25+
// CHECK: call void @__asan_report_load
26+
// CHECK: }
27+
pub fn sanitized(b: &mut u8) -> u8 {
28+
*b
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#[no_sanitize(address)]
2+
//~^ the `#[no_sanitize]` attribute is an experimental feature
3+
fn main() {
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0658]: the `#[no_sanitize]` attribute is an experimental feature
2+
--> $DIR/feature-gate-no_sanitize.rs:1:1
3+
|
4+
LL | #[no_sanitize(address)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: for more information, see https://github.com/rust-lang/rust/issues/39699
8+
= help: add `#![feature(no_sanitize)]` to the crate attributes to enable
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0658`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#![feature(no_sanitize)]
2+
3+
#[no_sanitize(brontosaurus)] //~ ERROR invalid argument
4+
fn main() {
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: invalid argument for `no_sanitize`
2+
--> $DIR/invalid-no-sanitize.rs:3:15
3+
|
4+
LL | #[no_sanitize(brontosaurus)]
5+
| ^^^^^^^^^^^^
6+
|
7+
= note: expected one of: `address`, `memory` or `thread`
8+
9+
error: aborting due to previous error
10+

src/test/ui/sanitize-inline-always.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// check-pass
2+
3+
#![feature(no_sanitize)]
4+
5+
#[inline(always)]
6+
//~^ NOTE inlining requested here
7+
#[no_sanitize(address)]
8+
//~^ WARN will have no effect after inlining
9+
//~| NOTE on by default
10+
fn x() {
11+
}
12+
13+
fn main() {
14+
x()
15+
}

0 commit comments

Comments
 (0)