about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorDylan DPC <dylan.dpc@gmail.com>2020-02-07 17:00:16 +0100
committerGitHub <noreply@github.com>2020-02-07 17:00:16 +0100
commit2f1eaeea772c2800cd45d263a6927db366f5bdc5 (patch)
tree1f9f6d8e4553a4a0cee6a539eb2767bcf9259d09 /src
parentb5e21dbb5cabdaaadc47a4d8e3f59979dcad2871 (diff)
parent80adde2e337f4e0d784da401b2db37c5d4d3468b (diff)
downloadrust-2f1eaeea772c2800cd45d263a6927db366f5bdc5.tar.gz
rust-2f1eaeea772c2800cd45d263a6927db366f5bdc5.zip
Rollup merge of #68164 - tmiasko:no-sanitize, r=nikomatsakis
Selectively disable sanitizer instrumentation

Add `no_sanitize` attribute that allows to opt out from sanitizer
instrumentation in an annotated function.
Diffstat (limited to 'src')
-rw-r--r--src/doc/unstable-book/src/language-features/no-sanitize.md29
-rw-r--r--src/librustc/middle/codegen_fn_attrs.rs8
-rw-r--r--src/librustc_codegen_llvm/attributes.rs26
-rw-r--r--src/librustc_codegen_llvm/base.rs7
-rw-r--r--src/librustc_codegen_llvm/declare.rs16
-rw-r--r--src/librustc_codegen_ssa/base.rs22
-rw-r--r--src/librustc_feature/active.rs3
-rw-r--r--src/librustc_feature/builtin_attrs.rs5
-rw-r--r--src/librustc_mir/transform/inline.rs23
-rw-r--r--src/librustc_session/lint/builtin.rs7
-rw-r--r--src/librustc_span/symbol.rs4
-rw-r--r--src/librustc_typeck/collect.rs38
-rw-r--r--src/test/codegen/sanitizer-no-sanitize-inlining.rs32
-rw-r--r--src/test/codegen/sanitizer-no-sanitize.rs29
-rw-r--r--src/test/ui/feature-gates/feature-gate-no_sanitize.rs4
-rw-r--r--src/test/ui/feature-gates/feature-gate-no_sanitize.stderr12
-rw-r--r--src/test/ui/invalid/invalid-no-sanitize.rs5
-rw-r--r--src/test/ui/invalid/invalid-no-sanitize.stderr10
-rw-r--r--src/test/ui/sanitize-inline-always.rs15
-rw-r--r--src/test/ui/sanitize-inline-always.stderr13
20 files changed, 279 insertions, 29 deletions
diff --git a/src/doc/unstable-book/src/language-features/no-sanitize.md b/src/doc/unstable-book/src/language-features/no-sanitize.md
new file mode 100644
index 00000000000..28c683934d4
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/no-sanitize.md
@@ -0,0 +1,29 @@
+# `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/librustc/middle/codegen_fn_attrs.rs b/src/librustc/middle/codegen_fn_attrs.rs
index 9f8c2020861..82adcfddc28 100644
--- a/src/librustc/middle/codegen_fn_attrs.rs
+++ b/src/librustc/middle/codegen_fn_attrs.rs
@@ -72,6 +72,14 @@ bitflags! {
         const FFI_RETURNS_TWICE         = 1 << 10;
         /// `#[track_caller]`: allow access to the caller location
         const TRACK_CALLER              = 1 << 11;
+        /// `#[no_sanitize(address)]`: disables address sanitizer instrumentation
+        const NO_SANITIZE_ADDRESS = 1 << 12;
+        /// `#[no_sanitize(memory)]`: disables memory sanitizer instrumentation
+        const NO_SANITIZE_MEMORY  = 1 << 13;
+        /// `#[no_sanitize(thread)]`: disables thread sanitizer instrumentation
+        const NO_SANITIZE_THREAD  = 1 << 14;
+        /// All `#[no_sanitize(...)]` attributes.
+        const NO_SANITIZE_ANY = Self::NO_SANITIZE_ADDRESS.bits | Self::NO_SANITIZE_MEMORY.bits | Self::NO_SANITIZE_THREAD.bits;
     }
 }
 
diff --git a/src/librustc_codegen_llvm/attributes.rs b/src/librustc_codegen_llvm/attributes.rs
index e3920d99c90..a9e4fdba030 100644
--- a/src/librustc_codegen_llvm/attributes.rs
+++ b/src/librustc_codegen_llvm/attributes.rs
@@ -46,6 +46,31 @@ fn inline(cx: &CodegenCx<'ll, '_>, val: &'ll Value, inline: InlineAttr) {
     };
 }
 
+/// Apply LLVM sanitize attributes.
+#[inline]
+pub fn sanitize(cx: &CodegenCx<'ll, '_>, codegen_fn_flags: CodegenFnAttrFlags, llfn: &'ll Value) {
+    if let Some(ref sanitizer) = cx.tcx.sess.opts.debugging_opts.sanitizer {
+        match *sanitizer {
+            Sanitizer::Address => {
+                if !codegen_fn_flags.contains(CodegenFnAttrFlags::NO_SANITIZE_ADDRESS) {
+                    llvm::Attribute::SanitizeAddress.apply_llfn(Function, llfn);
+                }
+            }
+            Sanitizer::Memory => {
+                if !codegen_fn_flags.contains(CodegenFnAttrFlags::NO_SANITIZE_MEMORY) {
+                    llvm::Attribute::SanitizeMemory.apply_llfn(Function, llfn);
+                }
+            }
+            Sanitizer::Thread => {
+                if !codegen_fn_flags.contains(CodegenFnAttrFlags::NO_SANITIZE_THREAD) {
+                    llvm::Attribute::SanitizeThread.apply_llfn(Function, llfn);
+                }
+            }
+            Sanitizer::Leak => {}
+        }
+    }
+}
+
 /// Tell LLVM to emit or not emit the information necessary to unwind the stack for the function.
 #[inline]
 pub fn emit_uwtable(val: &'ll Value, emit: bool) {
@@ -288,6 +313,7 @@ pub fn from_fn_attrs(
     if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) {
         Attribute::NoAlias.apply_llfn(llvm::AttributePlace::ReturnValue, llfn);
     }
+    sanitize(cx, codegen_fn_attrs.flags, llfn);
 
     unwind(
         llfn,
diff --git a/src/librustc_codegen_llvm/base.rs b/src/librustc_codegen_llvm/base.rs
index d3b524c1a1e..04c084e459e 100644
--- a/src/librustc_codegen_llvm/base.rs
+++ b/src/librustc_codegen_llvm/base.rs
@@ -15,6 +15,7 @@
 
 use super::ModuleLlvm;
 
+use crate::attributes;
 use crate::builder::Builder;
 use crate::common;
 use crate::context::CodegenCx;
@@ -23,7 +24,7 @@ use crate::metadata;
 use crate::value::Value;
 
 use rustc::dep_graph;
-use rustc::middle::codegen_fn_attrs::CodegenFnAttrs;
+use rustc::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
 use rustc::middle::cstore::EncodedMetadata;
 use rustc::middle::exported_symbols;
 use rustc::mir::mono::{Linkage, Visibility};
@@ -131,7 +132,9 @@ pub fn compile_codegen_unit(
 
             // If this codegen unit contains the main function, also create the
             // wrapper here
-            maybe_create_entry_wrapper::<Builder<'_, '_, '_>>(&cx);
+            if let Some(entry) = maybe_create_entry_wrapper::<Builder<'_, '_, '_>>(&cx) {
+                attributes::sanitize(&cx, CodegenFnAttrFlags::empty(), entry);
+            }
 
             // Run replace-all-uses-with for statics that need it
             for &(old_g, new_g) in cx.statics_to_rauw().borrow().iter() {
diff --git a/src/librustc_codegen_llvm/declare.rs b/src/librustc_codegen_llvm/declare.rs
index bb06b521621..691f32dd85a 100644
--- a/src/librustc_codegen_llvm/declare.rs
+++ b/src/librustc_codegen_llvm/declare.rs
@@ -19,7 +19,6 @@ use crate::llvm::AttributePlace::Function;
 use crate::type_::Type;
 use crate::value::Value;
 use log::debug;
-use rustc::session::config::Sanitizer;
 use rustc::ty::Ty;
 use rustc_codegen_ssa::traits::*;
 use rustc_data_structures::small_c_str::SmallCStr;
@@ -47,21 +46,6 @@ fn declare_raw_fn(
         llvm::Attribute::NoRedZone.apply_llfn(Function, llfn);
     }
 
-    if let Some(ref sanitizer) = cx.tcx.sess.opts.debugging_opts.sanitizer {
-        match *sanitizer {
-            Sanitizer::Address => {
-                llvm::Attribute::SanitizeAddress.apply_llfn(Function, llfn);
-            }
-            Sanitizer::Memory => {
-                llvm::Attribute::SanitizeMemory.apply_llfn(Function, llfn);
-            }
-            Sanitizer::Thread => {
-                llvm::Attribute::SanitizeThread.apply_llfn(Function, llfn);
-            }
-            _ => {}
-        }
-    }
-
     attributes::default_optimisation_attrs(cx.tcx.sess, llfn);
     attributes::non_lazy_bind(cx.sess(), llfn);
     llfn
diff --git a/src/librustc_codegen_ssa/base.rs b/src/librustc_codegen_ssa/base.rs
index 1f43a4027c5..90015091384 100644
--- a/src/librustc_codegen_ssa/base.rs
+++ b/src/librustc_codegen_ssa/base.rs
@@ -391,10 +391,12 @@ pub fn codegen_instance<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
 
 /// Creates the `main` function which will initialize the rust runtime and call
 /// users main function.
-pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(cx: &'a Bx::CodegenCx) {
+pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
+    cx: &'a Bx::CodegenCx,
+) -> Option<Bx::Function> {
     let (main_def_id, span) = match cx.tcx().entry_fn(LOCAL_CRATE) {
         Some((def_id, _)) => (def_id, cx.tcx().def_span(def_id)),
-        None => return,
+        None => return None,
     };
 
     let instance = Instance::mono(cx.tcx(), main_def_id);
@@ -402,17 +404,15 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(cx: &'
     if !cx.codegen_unit().contains_item(&MonoItem::Fn(instance)) {
         // We want to create the wrapper in the same codegen unit as Rust's main
         // function.
-        return;
+        return None;
     }
 
     let main_llfn = cx.get_fn_addr(instance);
 
-    let et = cx.tcx().entry_fn(LOCAL_CRATE).map(|e| e.1);
-    match et {
-        Some(EntryFnType::Main) => create_entry_fn::<Bx>(cx, span, main_llfn, main_def_id, true),
-        Some(EntryFnType::Start) => create_entry_fn::<Bx>(cx, span, main_llfn, main_def_id, false),
-        None => {} // Do nothing.
-    }
+    return cx.tcx().entry_fn(LOCAL_CRATE).map(|(_, et)| {
+        let use_start_lang_item = EntryFnType::Start != et;
+        create_entry_fn::<Bx>(cx, span, main_llfn, main_def_id, use_start_lang_item)
+    });
 
     fn create_entry_fn<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
         cx: &'a Bx::CodegenCx,
@@ -420,7 +420,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(cx: &'
         rust_main: Bx::Value,
         rust_main_def_id: DefId,
         use_start_lang_item: bool,
-    ) {
+    ) -> Bx::Function {
         // The entry function is either `int main(void)` or `int main(int argc, char **argv)`,
         // depending on whether the target needs `argc` and `argv` to be passed in.
         let llfty = if cx.sess().target.target.options.main_needs_argc_argv {
@@ -481,6 +481,8 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(cx: &'
         let result = bx.call(start_fn, &args, None);
         let cast = bx.intcast(result, cx.type_int(), true);
         bx.ret(cast);
+
+        llfn
     }
 }
 
diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs
index 4ae79f9ccaa..d7fd15a8a7b 100644
--- a/src/librustc_feature/active.rs
+++ b/src/librustc_feature/active.rs
@@ -541,6 +541,9 @@ declare_features! (
     /// Allows `T: ?const Trait` syntax in bounds.
     (active, const_trait_bound_opt_out, "1.42.0", Some(67794), None),
 
+    /// Allows the use of `no_sanitize` attribute.
+    (active, no_sanitize, "1.42.0", Some(39699), None),
+
     // -------------------------------------------------------------------------
     // feature-group-end: actual feature gates
     // -------------------------------------------------------------------------
diff --git a/src/librustc_feature/builtin_attrs.rs b/src/librustc_feature/builtin_attrs.rs
index a38726e3de8..e2e061c185c 100644
--- a/src/librustc_feature/builtin_attrs.rs
+++ b/src/librustc_feature/builtin_attrs.rs
@@ -261,6 +261,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
     ungated!(cold, Whitelisted, template!(Word)),
     ungated!(no_builtins, Whitelisted, template!(Word)),
     ungated!(target_feature, Whitelisted, template!(List: r#"enable = "name""#)),
+    gated!(
+        no_sanitize, Whitelisted,
+        template!(List: "address, memory, thread"),
+        experimental!(no_sanitize)
+    ),
 
     // FIXME: #14408 whitelist docs since rustdoc looks at them
     ungated!(doc, Whitelisted, template!(List: "hidden|inline|...", NameValueStr: "string")),
diff --git a/src/librustc_mir/transform/inline.rs b/src/librustc_mir/transform/inline.rs
index a3cafcb5763..b6802505df7 100644
--- a/src/librustc_mir/transform/inline.rs
+++ b/src/librustc_mir/transform/inline.rs
@@ -8,6 +8,7 @@ use rustc_index::vec::{Idx, IndexVec};
 use rustc::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc::mir::visit::*;
 use rustc::mir::*;
+use rustc::session::config::Sanitizer;
 use rustc::ty::subst::{InternalSubsts, Subst, SubstsRef};
 use rustc::ty::{self, Instance, InstanceDef, ParamEnv, Ty, TyCtxt, TypeFoldable};
 
@@ -228,6 +229,28 @@ impl Inliner<'tcx> {
             return false;
         }
 
+        // Avoid inlining functions marked as no_sanitize if sanitizer is enabled,
+        // since instrumentation might be enabled and performed on the caller.
+        match self.tcx.sess.opts.debugging_opts.sanitizer {
+            Some(Sanitizer::Address) => {
+                if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_ADDRESS) {
+                    return false;
+                }
+            }
+            Some(Sanitizer::Memory) => {
+                if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_MEMORY) {
+                    return false;
+                }
+            }
+            Some(Sanitizer::Thread) => {
+                if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_SANITIZE_THREAD) {
+                    return false;
+                }
+            }
+            Some(Sanitizer::Leak) => {}
+            None => {}
+        }
+
         let hinted = match codegen_fn_attrs.inline {
             // Just treat inline(always) as a hint for now,
             // there are cases that prevent inlining that we
diff --git a/src/librustc_session/lint/builtin.rs b/src/librustc_session/lint/builtin.rs
index c326061100b..a61ab5b5e17 100644
--- a/src/librustc_session/lint/builtin.rs
+++ b/src/librustc_session/lint/builtin.rs
@@ -474,6 +474,12 @@ declare_lint! {
     };
 }
 
+declare_lint! {
+    pub INLINE_NO_SANITIZE,
+    Warn,
+    "detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`",
+}
+
 declare_lint_pass! {
     /// Does nothing as a lint pass, but registers some `Lint`s
     /// that are used by other parts of the compiler.
@@ -537,5 +543,6 @@ declare_lint_pass! {
         MUTABLE_BORROW_RESERVATION_CONFLICT,
         INDIRECT_STRUCTURAL_MATCH,
         SOFT_UNSTABLE,
+        INLINE_NO_SANITIZE,
     ]
 }
diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs
index c060e8948e3..931a3c15cf0 100644
--- a/src/librustc_span/symbol.rs
+++ b/src/librustc_span/symbol.rs
@@ -120,6 +120,7 @@ symbols! {
         abi_vectorcall,
         abi_x86_interrupt,
         aborts,
+        address,
         add_with_overflow,
         advanced_slice_patterns,
         adx_target_feature,
@@ -445,6 +446,7 @@ symbols! {
         mem_uninitialized,
         mem_zeroed,
         member_constraints,
+        memory,
         message,
         meta,
         min_align_of,
@@ -487,6 +489,7 @@ symbols! {
         None,
         non_exhaustive,
         non_modrs_mods,
+        no_sanitize,
         no_stack_check,
         no_start,
         no_std,
@@ -721,6 +724,7 @@ symbols! {
         test_removed_feature,
         test_runner,
         then_with,
+        thread,
         thread_local,
         tool_attributes,
         tool_lints,
diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs
index dc089c90456..040b85b98ed 100644
--- a/src/librustc_typeck/collect.rs
+++ b/src/librustc_typeck/collect.rs
@@ -1,3 +1,5 @@
+// ignore-tidy-filelength
+
 //! "Collection" is the process of determining the type and other external
 //! details of each item in Rust. Collection is specifically concerned
 //! with *inter-procedural* things -- for example, for a function
@@ -2743,6 +2745,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
 
     let mut inline_span = None;
     let mut link_ordinal_span = None;
+    let mut no_sanitize_span = None;
     for attr in attrs.iter() {
         if attr.check_name(sym::cold) {
             codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD;
@@ -2832,6 +2835,24 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
             if let ordinal @ Some(_) = check_link_ordinal(tcx, attr) {
                 codegen_fn_attrs.link_ordinal = ordinal;
             }
+        } else if attr.check_name(sym::no_sanitize) {
+            no_sanitize_span = Some(attr.span);
+            if let Some(list) = attr.meta_item_list() {
+                for item in list.iter() {
+                    if item.check_name(sym::address) {
+                        codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_ADDRESS;
+                    } else if item.check_name(sym::memory) {
+                        codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_MEMORY;
+                    } else if item.check_name(sym::thread) {
+                        codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_THREAD;
+                    } else {
+                        tcx.sess
+                            .struct_span_err(item.span(), "invalid argument for `no_sanitize`")
+                            .note("expected one of: `address`, `memory` or `thread`")
+                            .emit();
+                    }
+                }
+            }
         }
     }
 
@@ -2911,7 +2932,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
     // purpose functions as they wouldn't have the right target features
     // enabled. For that reason we also forbid #[inline(always)] as it can't be
     // respected.
-
     if codegen_fn_attrs.target_features.len() > 0 {
         if codegen_fn_attrs.inline == InlineAttr::Always {
             if let Some(span) = inline_span {
@@ -2924,6 +2944,22 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
         }
     }
 
+    if codegen_fn_attrs.flags.intersects(CodegenFnAttrFlags::NO_SANITIZE_ANY) {
+        if codegen_fn_attrs.inline == InlineAttr::Always {
+            if let (Some(no_sanitize_span), Some(inline_span)) = (no_sanitize_span, inline_span) {
+                let hir_id = tcx.hir().as_local_hir_id(id).unwrap();
+                tcx.struct_span_lint_hir(
+                    lint::builtin::INLINE_NO_SANITIZE,
+                    hir_id,
+                    no_sanitize_span,
+                    "`no_sanitize` will have no effect after inlining",
+                )
+                .span_note(inline_span, "inlining requested here")
+                .emit();
+            }
+        }
+    }
+
     // Weak lang items have the same semantics as "std internal" symbols in the
     // sense that they're preserved through all our LTO passes and only
     // strippable by the linker.
diff --git a/src/test/codegen/sanitizer-no-sanitize-inlining.rs b/src/test/codegen/sanitizer-no-sanitize-inlining.rs
new file mode 100644
index 00000000000..d96e76618d3
--- /dev/null
+++ b/src/test/codegen/sanitizer-no-sanitize-inlining.rs
@@ -0,0 +1,32 @@
+// Verifies that no_sanitize attribute prevents inlining when
+// given sanitizer is enabled, but has no effect on inlining otherwise.
+//
+// needs-sanitizer-support
+// only-x86_64
+//
+// revisions: ASAN LSAN
+//
+//[ASAN] compile-flags: -Zsanitizer=address -C opt-level=3 -Z mir-opt-level=3
+//[LSAN] compile-flags: -Zsanitizer=leak    -C opt-level=3 -Z mir-opt-level=3
+
+#![crate_type="lib"]
+#![feature(no_sanitize)]
+
+// ASAN-LABEL: define void @test
+// ASAN:         tail call fastcc void @random_inline
+// ASAN:       }
+//
+// LSAN-LABEL: define void @test
+// LSAN-NO:      call
+// LSAN:       }
+#[no_mangle]
+pub fn test(n: &mut u32) {
+    random_inline(n);
+}
+
+#[no_sanitize(address)]
+#[inline]
+#[no_mangle]
+pub fn random_inline(n: &mut u32) {
+    *n = 42;
+}
diff --git a/src/test/codegen/sanitizer-no-sanitize.rs b/src/test/codegen/sanitizer-no-sanitize.rs
new file mode 100644
index 00000000000..dfceb28c8dd
--- /dev/null
+++ b/src/test/codegen/sanitizer-no-sanitize.rs
@@ -0,0 +1,29 @@
+// Verifies that no_sanitze attribute can be used to
+// selectively disable sanitizer instrumentation.
+//
+// needs-sanitizer-support
+// compile-flags: -Zsanitizer=address
+
+#![crate_type="lib"]
+#![feature(no_sanitize)]
+
+// CHECK-LABEL: ; sanitizer_no_sanitize::unsanitized
+// CHECK-NEXT:  ; Function Attrs:
+// CHECK-NOT:   sanitize_address
+// CHECK:       start:
+// CHECK-NOT:   call void @__asan_report_load
+// CHECK:       }
+#[no_sanitize(address)]
+pub fn unsanitized(b: &mut u8) -> u8 {
+    *b
+}
+
+// CHECK-LABEL: ; sanitizer_no_sanitize::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/src/test/ui/feature-gates/feature-gate-no_sanitize.rs b/src/test/ui/feature-gates/feature-gate-no_sanitize.rs
new file mode 100644
index 00000000000..66a9263e13a
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-no_sanitize.rs
@@ -0,0 +1,4 @@
+#[no_sanitize(address)]
+//~^ the `#[no_sanitize]` attribute is an experimental feature
+fn main() {
+}
diff --git a/src/test/ui/feature-gates/feature-gate-no_sanitize.stderr b/src/test/ui/feature-gates/feature-gate-no_sanitize.stderr
new file mode 100644
index 00000000000..7359cf03652
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-no_sanitize.stderr
@@ -0,0 +1,12 @@
+error[E0658]: the `#[no_sanitize]` attribute is an experimental feature
+  --> $DIR/feature-gate-no_sanitize.rs:1:1
+   |
+LL | #[no_sanitize(address)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: for more information, see https://github.com/rust-lang/rust/issues/39699
+   = help: add `#![feature(no_sanitize)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/invalid/invalid-no-sanitize.rs b/src/test/ui/invalid/invalid-no-sanitize.rs
new file mode 100644
index 00000000000..b52e3cc83fa
--- /dev/null
+++ b/src/test/ui/invalid/invalid-no-sanitize.rs
@@ -0,0 +1,5 @@
+#![feature(no_sanitize)]
+
+#[no_sanitize(brontosaurus)] //~ ERROR invalid argument
+fn main() {
+}
diff --git a/src/test/ui/invalid/invalid-no-sanitize.stderr b/src/test/ui/invalid/invalid-no-sanitize.stderr
new file mode 100644
index 00000000000..e9983e5fbd2
--- /dev/null
+++ b/src/test/ui/invalid/invalid-no-sanitize.stderr
@@ -0,0 +1,10 @@
+error: invalid argument for `no_sanitize`
+  --> $DIR/invalid-no-sanitize.rs:3:15
+   |
+LL | #[no_sanitize(brontosaurus)]
+   |               ^^^^^^^^^^^^
+   |
+   = note: expected one of: `address`, `memory` or `thread`
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/sanitize-inline-always.rs b/src/test/ui/sanitize-inline-always.rs
new file mode 100644
index 00000000000..52dc5578180
--- /dev/null
+++ b/src/test/ui/sanitize-inline-always.rs
@@ -0,0 +1,15 @@
+// check-pass
+
+#![feature(no_sanitize)]
+
+#[inline(always)]
+//~^ NOTE inlining requested here
+#[no_sanitize(address)]
+//~^ WARN will have no effect after inlining
+//~| NOTE on by default
+fn x() {
+}
+
+fn main() {
+    x()
+}
diff --git a/src/test/ui/sanitize-inline-always.stderr b/src/test/ui/sanitize-inline-always.stderr
new file mode 100644
index 00000000000..50b9474baa2
--- /dev/null
+++ b/src/test/ui/sanitize-inline-always.stderr
@@ -0,0 +1,13 @@
+warning: `no_sanitize` will have no effect after inlining
+  --> $DIR/sanitize-inline-always.rs:7:1
+   |
+LL | #[no_sanitize(address)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(inline_no_sanitize)]` on by default
+note: inlining requested here
+  --> $DIR/sanitize-inline-always.rs:5:1
+   |
+LL | #[inline(always)]
+   | ^^^^^^^^^^^^^^^^^
+