about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/doc/unstable-book/src/compiler-flags/sanitizer.md3
-rw-r--r--src/librustc_codegen_llvm/attributes.rs45
-rw-r--r--src/librustc_codegen_llvm/back/write.rs44
-rw-r--r--src/librustc_codegen_llvm/base.rs6
-rw-r--r--src/librustc_codegen_llvm/llvm/ffi.rs7
-rw-r--r--src/librustc_codegen_ssa/back/link.rs40
-rw-r--r--src/librustc_codegen_ssa/back/symbol_export.rs4
-rw-r--r--src/librustc_codegen_ssa/back/write.rs12
-rw-r--r--src/librustc_interface/tests.rs8
-rw-r--r--src/librustc_middle/middle/codegen_fn_attrs.rs17
-rw-r--r--src/librustc_mir/transform/inline.rs21
-rw-r--r--src/librustc_session/Cargo.toml1
-rw-r--r--src/librustc_session/config.rs75
-rw-r--r--src/librustc_session/lib.rs3
-rw-r--r--src/librustc_session/options.rs32
-rw-r--r--src/librustc_session/session.rs70
-rw-r--r--src/librustc_typeck/collect.rs9
-rw-r--r--src/rustllvm/PassWrapper.cpp13
-rw-r--r--src/test/ui/sanitize/incompatible.rs6
-rw-r--r--src/test/ui/sanitize/incompatible.stderr4
-rw-r--r--src/test/ui/sanitize/unsupported-target.rs3
-rw-r--r--src/test/ui/sanitize/unsupported-target.stderr2
22 files changed, 211 insertions, 214 deletions
diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md
index 7ebd8054ba0..5e2e04c063b 100644
--- a/src/doc/unstable-book/src/compiler-flags/sanitizer.md
+++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md
@@ -12,8 +12,7 @@ This feature allows for use of one of following sanitizers:
 * [ThreadSanitizer][clang-tsan] a fast data race detector.
 
 To enable a sanitizer compile with `-Zsanitizer=address`, `-Zsanitizer=leak`,
-`-Zsanitizer=memory` or `-Zsanitizer=thread`. Only a single sanitizer can be
-enabled at a time.
+`-Zsanitizer=memory` or `-Zsanitizer=thread`.
 
 # AddressSanitizer
 
diff --git a/src/librustc_codegen_llvm/attributes.rs b/src/librustc_codegen_llvm/attributes.rs
index a4e17a5f675..6234ade8a16 100644
--- a/src/librustc_codegen_llvm/attributes.rs
+++ b/src/librustc_codegen_llvm/attributes.rs
@@ -11,7 +11,7 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc_middle::ty::layout::HasTyCtxt;
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::{self, TyCtxt};
-use rustc_session::config::{OptLevel, Sanitizer};
+use rustc_session::config::{OptLevel, SanitizerSet};
 use rustc_session::Session;
 
 use crate::attributes;
@@ -45,26 +45,16 @@ 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 => {}
-        }
+pub fn sanitize(cx: &CodegenCx<'ll, '_>, no_sanitize: SanitizerSet, llfn: &'ll Value) {
+    let enabled = cx.tcx.sess.opts.debugging_opts.sanitizer - no_sanitize;
+    if enabled.contains(SanitizerSet::ADDRESS) {
+        llvm::Attribute::SanitizeAddress.apply_llfn(Function, llfn);
+    }
+    if enabled.contains(SanitizerSet::MEMORY) {
+        llvm::Attribute::SanitizeMemory.apply_llfn(Function, llfn);
+    }
+    if enabled.contains(SanitizerSet::THREAD) {
+        llvm::Attribute::SanitizeThread.apply_llfn(Function, llfn);
     }
 }
 
@@ -123,9 +113,14 @@ fn set_probestack(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
     // Currently stack probes seem somewhat incompatible with the address
     // sanitizer and thread sanitizer. With asan we're already protected from
     // stack overflow anyway so we don't really need stack probes regardless.
-    match cx.sess().opts.debugging_opts.sanitizer {
-        Some(Sanitizer::Address | Sanitizer::Thread) => return,
-        _ => {}
+    if cx
+        .sess()
+        .opts
+        .debugging_opts
+        .sanitizer
+        .intersects(SanitizerSet::ADDRESS | SanitizerSet::THREAD)
+    {
+        return;
     }
 
     // probestack doesn't play nice either with `-C profile-generate`.
@@ -296,7 +291,7 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
     if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) {
         Attribute::NoAlias.apply_llfn(llvm::AttributePlace::ReturnValue, llfn);
     }
-    sanitize(cx, codegen_fn_attrs.flags, llfn);
+    sanitize(cx, codegen_fn_attrs.no_sanitize, llfn);
 
     // Always annotate functions with the target-cpu they are compiled for.
     // Without this, ThinLTO won't inline Rust functions into Clang generated
diff --git a/src/librustc_codegen_llvm/back/write.rs b/src/librustc_codegen_llvm/back/write.rs
index 26f5334668b..868ce876a81 100644
--- a/src/librustc_codegen_llvm/back/write.rs
+++ b/src/librustc_codegen_llvm/back/write.rs
@@ -21,7 +21,7 @@ use rustc_fs_util::{link_or_copy, path_to_c_string};
 use rustc_hir::def_id::LOCAL_CRATE;
 use rustc_middle::bug;
 use rustc_middle::ty::TyCtxt;
-use rustc_session::config::{self, Lto, OutputType, Passes, Sanitizer, SwitchWithOptPath};
+use rustc_session::config::{self, Lto, OutputType, Passes, SanitizerSet, SwitchWithOptPath};
 use rustc_session::Session;
 use rustc_span::InnerSpan;
 use rustc_target::spec::{CodeModel, RelocModel};
@@ -394,12 +394,13 @@ pub(crate) unsafe fn optimize_with_new_llvm_pass_manager(
     let is_lto = opt_stage == llvm::OptStage::ThinLTO || opt_stage == llvm::OptStage::FatLTO;
     // Sanitizer instrumentation is only inserted during the pre-link optimization stage.
     let sanitizer_options = if !is_lto {
-        config.sanitizer.as_ref().map(|s| llvm::SanitizerOptions {
-            sanitize_memory: *s == Sanitizer::Memory,
-            sanitize_thread: *s == Sanitizer::Thread,
-            sanitize_address: *s == Sanitizer::Address,
-            sanitize_recover: config.sanitizer_recover.contains(s),
+        Some(llvm::SanitizerOptions {
+            sanitize_address: config.sanitizer.contains(SanitizerSet::ADDRESS),
+            sanitize_address_recover: config.sanitizer_recover.contains(SanitizerSet::ADDRESS),
+            sanitize_memory: config.sanitizer.contains(SanitizerSet::MEMORY),
+            sanitize_memory_recover: config.sanitizer_recover.contains(SanitizerSet::MEMORY),
             sanitize_memory_track_origins: config.sanitizer_memory_track_origins as c_int,
+            sanitize_thread: config.sanitizer.contains(SanitizerSet::THREAD),
         })
     } else {
         None
@@ -600,25 +601,18 @@ pub(crate) unsafe fn optimize(
 }
 
 unsafe fn add_sanitizer_passes(config: &ModuleConfig, passes: &mut Vec<&'static mut llvm::Pass>) {
-    let sanitizer = match &config.sanitizer {
-        None => return,
-        Some(s) => s,
-    };
-
-    let recover = config.sanitizer_recover.contains(sanitizer);
-    match sanitizer {
-        Sanitizer::Address => {
-            passes.push(llvm::LLVMRustCreateAddressSanitizerFunctionPass(recover));
-            passes.push(llvm::LLVMRustCreateModuleAddressSanitizerPass(recover));
-        }
-        Sanitizer::Memory => {
-            let track_origins = config.sanitizer_memory_track_origins as c_int;
-            passes.push(llvm::LLVMRustCreateMemorySanitizerPass(track_origins, recover));
-        }
-        Sanitizer::Thread => {
-            passes.push(llvm::LLVMRustCreateThreadSanitizerPass());
-        }
-        Sanitizer::Leak => {}
+    if config.sanitizer.contains(SanitizerSet::ADDRESS) {
+        let recover = config.sanitizer_recover.contains(SanitizerSet::ADDRESS);
+        passes.push(llvm::LLVMRustCreateAddressSanitizerFunctionPass(recover));
+        passes.push(llvm::LLVMRustCreateModuleAddressSanitizerPass(recover));
+    }
+    if config.sanitizer.contains(SanitizerSet::MEMORY) {
+        let track_origins = config.sanitizer_memory_track_origins as c_int;
+        let recover = config.sanitizer_recover.contains(SanitizerSet::MEMORY);
+        passes.push(llvm::LLVMRustCreateMemorySanitizerPass(track_origins, recover));
+    }
+    if config.sanitizer.contains(SanitizerSet::THREAD) {
+        passes.push(llvm::LLVMRustCreateThreadSanitizerPass());
     }
 }
 
diff --git a/src/librustc_codegen_llvm/base.rs b/src/librustc_codegen_llvm/base.rs
index 3e17a51528e..e99fb8dcae1 100644
--- a/src/librustc_codegen_llvm/base.rs
+++ b/src/librustc_codegen_llvm/base.rs
@@ -29,12 +29,12 @@ use rustc_codegen_ssa::traits::*;
 use rustc_codegen_ssa::{ModuleCodegen, ModuleKind};
 use rustc_data_structures::small_c_str::SmallCStr;
 use rustc_middle::dep_graph;
-use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
+use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
 use rustc_middle::middle::cstore::EncodedMetadata;
 use rustc_middle::middle::exported_symbols;
 use rustc_middle::mir::mono::{Linkage, Visibility};
 use rustc_middle::ty::TyCtxt;
-use rustc_session::config::DebugInfo;
+use rustc_session::config::{DebugInfo, SanitizerSet};
 use rustc_span::symbol::Symbol;
 
 use std::ffi::CString;
@@ -132,7 +132,7 @@ pub fn compile_codegen_unit(
             // If this codegen unit contains the main function, also create the
             // wrapper here
             if let Some(entry) = maybe_create_entry_wrapper::<Builder<'_, '_, '_>>(&cx) {
-                attributes::sanitize(&cx, CodegenFnAttrFlags::empty(), entry);
+                attributes::sanitize(&cx, SanitizerSet::empty(), entry);
             }
 
             // Run replace-all-uses-with for statics that need it
diff --git a/src/librustc_codegen_llvm/llvm/ffi.rs b/src/librustc_codegen_llvm/llvm/ffi.rs
index 372fb17573a..8063d97aa73 100644
--- a/src/librustc_codegen_llvm/llvm/ffi.rs
+++ b/src/librustc_codegen_llvm/llvm/ffi.rs
@@ -439,11 +439,12 @@ pub enum OptStage {
 /// LLVMRustSanitizerOptions
 #[repr(C)]
 pub struct SanitizerOptions {
-    pub sanitize_memory: bool,
-    pub sanitize_thread: bool,
     pub sanitize_address: bool,
-    pub sanitize_recover: bool,
+    pub sanitize_address_recover: bool,
+    pub sanitize_memory: bool,
+    pub sanitize_memory_recover: bool,
     pub sanitize_memory_track_origins: c_int,
+    pub sanitize_thread: bool,
 }
 
 /// LLVMRelocMode
diff --git a/src/librustc_codegen_ssa/back/link.rs b/src/librustc_codegen_ssa/back/link.rs
index 1eef86f6c93..c7a58d9f2a2 100644
--- a/src/librustc_codegen_ssa/back/link.rs
+++ b/src/librustc_codegen_ssa/back/link.rs
@@ -4,7 +4,7 @@ use rustc_hir::def_id::CrateNum;
 use rustc_middle::middle::cstore::{EncodedMetadata, LibSource, NativeLib};
 use rustc_middle::middle::dependency_format::Linkage;
 use rustc_session::config::{self, CFGuard, CrateType, DebugInfo};
-use rustc_session::config::{OutputFilenames, OutputType, PrintRequest, Sanitizer};
+use rustc_session::config::{OutputFilenames, OutputType, PrintRequest, SanitizerSet};
 use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename};
 use rustc_session::search_paths::PathKind;
 use rustc_session::utils::NativeLibKind;
@@ -766,23 +766,26 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(
     }
 }
 
-fn link_sanitizer_runtime(sess: &Session, crate_type: CrateType, linker: &mut dyn Linker) {
-    let sanitizer = match &sess.opts.debugging_opts.sanitizer {
-        Some(s) => s,
-        None => return,
-    };
-
+fn link_sanitizers(sess: &Session, crate_type: CrateType, linker: &mut dyn Linker) {
     if crate_type != CrateType::Executable {
         return;
     }
+    let sanitizer = sess.opts.debugging_opts.sanitizer;
+    if sanitizer.contains(SanitizerSet::ADDRESS) {
+        link_sanitizer_runtime(sess, linker, "asan");
+    }
+    if sanitizer.contains(SanitizerSet::LEAK) {
+        link_sanitizer_runtime(sess, linker, "lsan");
+    }
+    if sanitizer.contains(SanitizerSet::MEMORY) {
+        link_sanitizer_runtime(sess, linker, "msan");
+    }
+    if sanitizer.contains(SanitizerSet::THREAD) {
+        link_sanitizer_runtime(sess, linker, "tsan");
+    }
+}
 
-    let name = match sanitizer {
-        Sanitizer::Address => "asan",
-        Sanitizer::Leak => "lsan",
-        Sanitizer::Memory => "msan",
-        Sanitizer::Thread => "tsan",
-    };
-
+fn link_sanitizer_runtime(sess: &Session, linker: &mut dyn Linker, name: &str) {
     let default_sysroot = filesearch::get_or_default_sysroot();
     let default_tlib =
         filesearch::make_target_lib_path(&default_sysroot, sess.opts.target_triple.triple());
@@ -1555,9 +1558,10 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
 
     // NO-OPT-OUT, OBJECT-FILES-NO, AUDIT-ORDER
     if sess.target.target.options.is_like_fuchsia && crate_type == CrateType::Executable {
-        let prefix = match sess.opts.debugging_opts.sanitizer {
-            Some(Sanitizer::Address) => "asan/",
-            _ => "",
+        let prefix = if sess.opts.debugging_opts.sanitizer.contains(SanitizerSet::ADDRESS) {
+            "asan/"
+        } else {
+            ""
         };
         cmd.arg(format!("--dynamic-linker={}ld.so.1", prefix));
     }
@@ -1581,7 +1585,7 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
     }
 
     // OBJECT-FILES-YES, AUDIT-ORDER
-    link_sanitizer_runtime(sess, crate_type, cmd);
+    link_sanitizers(sess, crate_type, cmd);
 
     // OBJECT-FILES-NO, AUDIT-ORDER
     // Linker plugins should be specified early in the list of arguments
diff --git a/src/librustc_codegen_ssa/back/symbol_export.rs b/src/librustc_codegen_ssa/back/symbol_export.rs
index 98f7da8361c..217ad57ddc9 100644
--- a/src/librustc_codegen_ssa/back/symbol_export.rs
+++ b/src/librustc_codegen_ssa/back/symbol_export.rs
@@ -15,7 +15,7 @@ use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
 use rustc_middle::ty::Instance;
 use rustc_middle::ty::{SymbolName, TyCtxt};
-use rustc_session::config::{CrateType, Sanitizer};
+use rustc_session::config::{CrateType, SanitizerSet};
 
 pub fn threshold(tcx: TyCtxt<'_>) -> SymbolExportLevel {
     crates_export_threshold(&tcx.sess.crate_types())
@@ -204,7 +204,7 @@ fn exported_symbols_provider_local(
         }));
     }
 
-    if let Some(Sanitizer::Memory) = tcx.sess.opts.debugging_opts.sanitizer {
+    if tcx.sess.opts.debugging_opts.sanitizer.contains(SanitizerSet::MEMORY) {
         // Similar to profiling, preserve weak msan symbol during LTO.
         const MSAN_WEAK_SYMBOLS: [&str; 2] = ["__msan_track_origins", "__msan_keep_going"];
 
diff --git a/src/librustc_codegen_ssa/back/write.rs b/src/librustc_codegen_ssa/back/write.rs
index 49054765b9d..23e0b9344ec 100644
--- a/src/librustc_codegen_ssa/back/write.rs
+++ b/src/librustc_codegen_ssa/back/write.rs
@@ -29,7 +29,7 @@ use rustc_middle::middle::exported_symbols::SymbolExportLevel;
 use rustc_middle::ty::TyCtxt;
 use rustc_session::cgu_reuse_tracker::CguReuseTracker;
 use rustc_session::config::{self, CrateType, Lto, OutputFilenames, OutputType};
-use rustc_session::config::{Passes, Sanitizer, SwitchWithOptPath};
+use rustc_session::config::{Passes, SanitizerSet, SwitchWithOptPath};
 use rustc_session::Session;
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::{sym, Symbol};
@@ -86,8 +86,8 @@ pub struct ModuleConfig {
     pub pgo_gen: SwitchWithOptPath,
     pub pgo_use: Option<PathBuf>,
 
-    pub sanitizer: Option<Sanitizer>,
-    pub sanitizer_recover: Vec<Sanitizer>,
+    pub sanitizer: SanitizerSet,
+    pub sanitizer_recover: SanitizerSet,
     pub sanitizer_memory_track_origins: usize,
 
     // Flags indicating which outputs to produce.
@@ -195,10 +195,10 @@ impl ModuleConfig {
             ),
             pgo_use: if_regular!(sess.opts.cg.profile_use.clone(), None),
 
-            sanitizer: if_regular!(sess.opts.debugging_opts.sanitizer.clone(), None),
+            sanitizer: if_regular!(sess.opts.debugging_opts.sanitizer, SanitizerSet::empty()),
             sanitizer_recover: if_regular!(
-                sess.opts.debugging_opts.sanitizer_recover.clone(),
-                vec![]
+                sess.opts.debugging_opts.sanitizer_recover,
+                SanitizerSet::empty()
             ),
             sanitizer_memory_track_origins: if_regular!(
                 sess.opts.debugging_opts.sanitizer_memory_track_origins,
diff --git a/src/librustc_interface/tests.rs b/src/librustc_interface/tests.rs
index c2a7d1a4a61..a832c2decaa 100644
--- a/src/librustc_interface/tests.rs
+++ b/src/librustc_interface/tests.rs
@@ -6,7 +6,9 @@ use rustc_session::config::Strip;
 use rustc_session::config::{build_configuration, build_session_options, to_crate_config};
 use rustc_session::config::{rustc_optgroups, ErrorOutputType, ExternLocation, Options, Passes};
 use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath};
-use rustc_session::config::{Externs, OutputType, OutputTypes, Sanitizer, SymbolManglingVersion};
+use rustc_session::config::{
+    Externs, OutputType, OutputTypes, SanitizerSet, SymbolManglingVersion,
+};
 use rustc_session::lint::Level;
 use rustc_session::search_paths::SearchPath;
 use rustc_session::utils::NativeLibKind;
@@ -569,9 +571,9 @@ fn test_debugging_options_tracking_hash() {
     tracked!(relro_level, Some(RelroLevel::Full));
     tracked!(report_delayed_bugs, true);
     tracked!(run_dsymutil, false);
-    tracked!(sanitizer, Some(Sanitizer::Address));
+    tracked!(sanitizer, SanitizerSet::ADDRESS);
     tracked!(sanitizer_memory_track_origins, 2);
-    tracked!(sanitizer_recover, vec![Sanitizer::Address]);
+    tracked!(sanitizer_recover, SanitizerSet::ADDRESS);
     tracked!(saturating_float_casts, Some(true));
     tracked!(share_generics, Some(true));
     tracked!(show_span, Some(String::from("abc")));
diff --git a/src/librustc_middle/middle/codegen_fn_attrs.rs b/src/librustc_middle/middle/codegen_fn_attrs.rs
index c480944069e..d2749f8529b 100644
--- a/src/librustc_middle/middle/codegen_fn_attrs.rs
+++ b/src/librustc_middle/middle/codegen_fn_attrs.rs
@@ -1,5 +1,6 @@
 use crate::mir::mono::Linkage;
 use rustc_attr::{InlineAttr, OptimizeAttr};
+use rustc_session::config::SanitizerSet;
 use rustc_span::symbol::Symbol;
 
 #[derive(Clone, RustcEncodable, RustcDecodable, HashStable)]
@@ -30,6 +31,9 @@ pub struct CodegenFnAttrs {
     /// The `#[link_section = "..."]` attribute, or what executable section this
     /// should be placed in.
     pub link_section: Option<Symbol>,
+    /// The `#[no_sanitize(...)]` attribute. Indicates sanitizers for which
+    /// instrumentation should be disabled inside the annotated function.
+    pub no_sanitize: SanitizerSet,
 }
 
 bitflags! {
@@ -69,20 +73,12 @@ 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;
         /// #[ffi_pure]: applies clang's `pure` attribute to a foreign function
         /// declaration.
-        const FFI_PURE = 1 << 15;
+        const FFI_PURE                  = 1 << 12;
         /// #[ffi_const]: applies clang's `const` attribute to a foreign function
         /// declaration.
-        const FFI_CONST = 1 << 16;
+        const FFI_CONST                 = 1 << 13;
     }
 }
 
@@ -98,6 +94,7 @@ impl CodegenFnAttrs {
             target_features: vec![],
             linkage: None,
             link_section: None,
+            no_sanitize: SanitizerSet::empty(),
         }
     }
 
diff --git a/src/librustc_mir/transform/inline.rs b/src/librustc_mir/transform/inline.rs
index db909494aed..068d055fa78 100644
--- a/src/librustc_mir/transform/inline.rs
+++ b/src/librustc_mir/transform/inline.rs
@@ -9,7 +9,6 @@ use rustc_middle::mir::visit::*;
 use rustc_middle::mir::*;
 use rustc_middle::ty::subst::{Subst, SubstsRef};
 use rustc_middle::ty::{self, ConstKind, Instance, InstanceDef, ParamEnv, Ty, TyCtxt};
-use rustc_session::config::Sanitizer;
 use rustc_target::spec::abi::Abi;
 
 use super::simplify::{remove_dead_blocks, CfgSimplifier};
@@ -232,24 +231,8 @@ impl Inliner<'tcx> {
 
         // 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 => {}
+        if self.tcx.sess.opts.debugging_opts.sanitizer.intersects(codegen_fn_attrs.no_sanitize) {
+            return false;
         }
 
         let hinted = match codegen_fn_attrs.inline {
diff --git a/src/librustc_session/Cargo.toml b/src/librustc_session/Cargo.toml
index 4d57c6384dd..abce7359c0e 100644
--- a/src/librustc_session/Cargo.toml
+++ b/src/librustc_session/Cargo.toml
@@ -9,6 +9,7 @@ name = "rustc_session"
 path = "lib.rs"
 
 [dependencies]
+bitflags = "1.2.1"
 getopts = "0.2"
 log = "0.4"
 rustc_errors = { path = "../librustc_errors" }
diff --git a/src/librustc_session/config.rs b/src/librustc_session/config.rs
index 411a6eecbba..53cec040836 100644
--- a/src/librustc_session/config.rs
+++ b/src/librustc_session/config.rs
@@ -10,6 +10,7 @@ use crate::{early_error, early_warn, Session};
 
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::impl_stable_hash_via_hash;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 
 use rustc_target::spec::{Target, TargetTriple};
 
@@ -37,35 +38,55 @@ pub struct Config {
     pub ptr_width: u32,
 }
 
-#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub enum Sanitizer {
-    Address,
-    Leak,
-    Memory,
-    Thread,
+bitflags! {
+    #[derive(Default, RustcEncodable, RustcDecodable)]
+    pub struct SanitizerSet: u8 {
+        const ADDRESS = 1 << 0;
+        const LEAK    = 1 << 1;
+        const MEMORY  = 1 << 2;
+        const THREAD  = 1 << 3;
+    }
 }
 
-impl fmt::Display for Sanitizer {
+/// Formats a sanitizer set as a comma separated list of sanitizers' names.
+impl fmt::Display for SanitizerSet {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match *self {
-            Sanitizer::Address => "address".fmt(f),
-            Sanitizer::Leak => "leak".fmt(f),
-            Sanitizer::Memory => "memory".fmt(f),
-            Sanitizer::Thread => "thread".fmt(f),
+        let mut first = true;
+        for s in *self {
+            let name = match s {
+                SanitizerSet::ADDRESS => "address",
+                SanitizerSet::LEAK => "leak",
+                SanitizerSet::MEMORY => "memory",
+                SanitizerSet::THREAD => "thread",
+                _ => panic!("unrecognized sanitizer {:?}", s),
+            };
+            if !first {
+                f.write_str(",")?;
+            }
+            f.write_str(name)?;
+            first = false;
         }
+        Ok(())
     }
 }
 
-impl FromStr for Sanitizer {
-    type Err = ();
-    fn from_str(s: &str) -> Result<Sanitizer, ()> {
-        match s {
-            "address" => Ok(Sanitizer::Address),
-            "leak" => Ok(Sanitizer::Leak),
-            "memory" => Ok(Sanitizer::Memory),
-            "thread" => Ok(Sanitizer::Thread),
-            _ => Err(()),
-        }
+impl IntoIterator for SanitizerSet {
+    type Item = SanitizerSet;
+    type IntoIter = std::vec::IntoIter<SanitizerSet>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        [SanitizerSet::ADDRESS, SanitizerSet::LEAK, SanitizerSet::MEMORY, SanitizerSet::THREAD]
+            .iter()
+            .copied()
+            .filter(|&s| self.contains(s))
+            .collect::<Vec<_>>()
+            .into_iter()
+    }
+}
+
+impl<CTX> HashStable<CTX> for SanitizerSet {
+    fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) {
+        self.bits().hash_stable(ctx, hasher);
     }
 }
 
@@ -726,10 +747,12 @@ pub fn default_configuration(sess: &Session) -> CrateConfig {
             }
         }
     }
-    if let Some(s) = &sess.opts.debugging_opts.sanitizer {
+
+    for s in sess.opts.debugging_opts.sanitizer {
         let symbol = Symbol::intern(&s.to_string());
         ret.insert((sym::sanitize, Some(symbol)));
     }
+
     if sess.opts.debug_assertions {
         ret.insert((Symbol::intern("debug_assertions"), None));
     }
@@ -1995,7 +2018,7 @@ impl PpMode {
 crate mod dep_tracking {
     use super::{
         CFGuard, CrateType, DebugInfo, ErrorOutputType, LinkerPluginLto, LtoCli, OptLevel,
-        OutputTypes, Passes, Sanitizer, SourceFileHashAlgorithm, SwitchWithOptPath,
+        OutputTypes, Passes, SanitizerSet, SourceFileHashAlgorithm, SwitchWithOptPath,
         SymbolManglingVersion,
     };
     use crate::lint;
@@ -2069,8 +2092,7 @@ crate mod dep_tracking {
     impl_dep_tracking_hash_via_hash!(UnstableFeatures);
     impl_dep_tracking_hash_via_hash!(OutputTypes);
     impl_dep_tracking_hash_via_hash!(NativeLibKind);
-    impl_dep_tracking_hash_via_hash!(Sanitizer);
-    impl_dep_tracking_hash_via_hash!(Option<Sanitizer>);
+    impl_dep_tracking_hash_via_hash!(SanitizerSet);
     impl_dep_tracking_hash_via_hash!(CFGuard);
     impl_dep_tracking_hash_via_hash!(TargetTriple);
     impl_dep_tracking_hash_via_hash!(Edition);
@@ -2085,7 +2107,6 @@ crate mod dep_tracking {
     impl_dep_tracking_hash_for_sortable_vec_of!((String, lint::Level));
     impl_dep_tracking_hash_for_sortable_vec_of!((String, Option<String>, NativeLibKind));
     impl_dep_tracking_hash_for_sortable_vec_of!((String, u64));
-    impl_dep_tracking_hash_for_sortable_vec_of!(Sanitizer);
 
     impl<T1, T2> DepTrackingHash for (T1, T2)
     where
diff --git a/src/librustc_session/lib.rs b/src/librustc_session/lib.rs
index 80b693fe1ab..be9d2e7be27 100644
--- a/src/librustc_session/lib.rs
+++ b/src/librustc_session/lib.rs
@@ -1,6 +1,9 @@
 #![feature(crate_visibility_modifier)]
 #![feature(or_patterns)]
 
+#[macro_use]
+extern crate bitflags;
+
 pub mod cgu_reuse_tracker;
 pub mod utils;
 #[macro_use]
diff --git a/src/librustc_session/options.rs b/src/librustc_session/options.rs
index 2d231359057..8032909bbcd 100644
--- a/src/librustc_session/options.rs
+++ b/src/librustc_session/options.rs
@@ -248,8 +248,7 @@ macro_rules! options {
         pub const parse_passes: &str = "a space-separated list of passes, or `all`";
         pub const parse_panic_strategy: &str = "either `unwind` or `abort`";
         pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`";
-        pub const parse_sanitizer: &str = "one of: `address`, `leak`, `memory` or `thread`";
-        pub const parse_sanitizer_list: &str = "comma separated list of sanitizers";
+        pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `leak`, `memory` or `thread`";
         pub const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2";
         pub const parse_cfguard: &str = "either `disabled`, `nochecks`, or `checks`";
         pub const parse_strip: &str = "either `none`, `debuginfo`, or `symbols`";
@@ -459,24 +458,15 @@ macro_rules! options {
             true
         }
 
-        fn parse_sanitizer(slot: &mut Option<Sanitizer>, v: Option<&str>) -> bool {
-            if let Some(Ok(s)) =  v.map(str::parse) {
-                *slot = Some(s);
-                true
-            } else {
-                false
-            }
-        }
-
-        fn parse_sanitizer_list(slot: &mut Vec<Sanitizer>, v: Option<&str>) -> bool {
+        fn parse_sanitizers(slot: &mut SanitizerSet, v: Option<&str>) -> bool {
             if let Some(v) = v {
-                for s in v.split(',').map(str::parse) {
-                    if let Ok(s) = s {
-                        if !slot.contains(&s) {
-                            slot.push(s);
-                        }
-                    } else {
-                        return false;
+                for s in v.split(',') {
+                    *slot |= match s {
+                        "address" => SanitizerSet::ADDRESS,
+                        "leak" => SanitizerSet::LEAK,
+                        "memory" => SanitizerSet::MEMORY,
+                        "thread" => SanitizerSet::THREAD,
+                        _ => return false,
                     }
                 }
                 true
@@ -974,11 +964,11 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
     // soon.
     run_dsymutil: bool = (true, parse_bool, [TRACKED],
         "if on Mac, run `dsymutil` and delete intermediate object files (default: yes)"),
-    sanitizer: Option<Sanitizer> = (None, parse_sanitizer, [TRACKED],
+    sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED],
         "use a sanitizer"),
     sanitizer_memory_track_origins: usize = (0, parse_sanitizer_memory_track_origins, [TRACKED],
         "enable origins tracking in MemorySanitizer"),
-    sanitizer_recover: Vec<Sanitizer> = (vec![], parse_sanitizer_list, [TRACKED],
+    sanitizer_recover: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED],
         "enable recovery for selected sanitizers"),
     saturating_float_casts: Option<bool> = (None, parse_opt_bool, [TRACKED],
         "make float->int casts UB-free: numbers outside the integer type's range are clipped to \
diff --git a/src/librustc_session/session.rs b/src/librustc_session/session.rs
index 06d7d4f14d8..90cd6ae5bf9 100644
--- a/src/librustc_session/session.rs
+++ b/src/librustc_session/session.rs
@@ -1,7 +1,7 @@
 use crate::cgu_reuse_tracker::CguReuseTracker;
 use crate::code_stats::CodeStats;
 pub use crate::code_stats::{DataTypeKind, FieldInfo, SizeKind, VariantInfo};
-use crate::config::{self, CrateType, OutputType, PrintRequest, Sanitizer, SwitchWithOptPath};
+use crate::config::{self, CrateType, OutputType, PrintRequest, SanitizerSet, SwitchWithOptPath};
 use crate::filesearch;
 use crate::lint;
 use crate::parse::ParseSess;
@@ -650,14 +650,9 @@ impl Session {
     }
     pub fn fewer_names(&self) -> bool {
         let more_names = self.opts.output_types.contains_key(&OutputType::LlvmAssembly)
-            || self.opts.output_types.contains_key(&OutputType::Bitcode);
-
-        // Address sanitizer and memory sanitizer use alloca name when reporting an issue.
-        let more_names = match self.opts.debugging_opts.sanitizer {
-            Some(Sanitizer::Address) => true,
-            Some(Sanitizer::Memory) => true,
-            _ => more_names,
-        };
+            || self.opts.output_types.contains_key(&OutputType::Bitcode)
+            // AddressSanitizer and MemorySanitizer use alloca name when reporting an issue.
+            || self.opts.debugging_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::MEMORY);
 
         self.opts.debugging_opts.fewer_names || !more_names
     }
@@ -1020,12 +1015,10 @@ impl Session {
 
     /// Checks if LLVM lifetime markers should be emitted.
     pub fn emit_lifetime_markers(&self) -> bool {
-        match self.opts.debugging_opts.sanitizer {
-            // AddressSanitizer uses lifetimes to detect use after scope bugs.
-            // MemorySanitizer uses lifetimes to detect use of uninitialized stack variables.
-            Some(Sanitizer::Address | Sanitizer::Memory) => true,
-            _ => self.opts.optimize != config::OptLevel::No,
-        }
+        self.opts.optimize != config::OptLevel::No
+        // AddressSanitizer uses lifetimes to detect use after scope bugs.
+        // MemorySanitizer uses lifetimes to detect use of uninitialized stack variables.
+        || self.opts.debugging_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::MEMORY)
     }
 }
 
@@ -1356,33 +1349,36 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
         );
     }
 
+    const ASAN_SUPPORTED_TARGETS: &[&str] =
+        &["aarch64-fuchsia", "x86_64-apple-darwin", "x86_64-fuchsia", "x86_64-unknown-linux-gnu"];
+    const LSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-apple-darwin", "x86_64-unknown-linux-gnu"];
+    const MSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];
+    const TSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-apple-darwin", "x86_64-unknown-linux-gnu"];
+
     // Sanitizers can only be used on some tested platforms.
-    if let Some(ref sanitizer) = sess.opts.debugging_opts.sanitizer {
-        const ASAN_SUPPORTED_TARGETS: &[&str] = &[
-            "x86_64-unknown-linux-gnu",
-            "x86_64-apple-darwin",
-            "x86_64-fuchsia",
-            "aarch64-fuchsia",
-        ];
-        const TSAN_SUPPORTED_TARGETS: &[&str] =
-            &["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
-        const LSAN_SUPPORTED_TARGETS: &[&str] =
-            &["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
-        const MSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];
-
-        let supported_targets = match *sanitizer {
-            Sanitizer::Address => ASAN_SUPPORTED_TARGETS,
-            Sanitizer::Thread => TSAN_SUPPORTED_TARGETS,
-            Sanitizer::Leak => LSAN_SUPPORTED_TARGETS,
-            Sanitizer::Memory => MSAN_SUPPORTED_TARGETS,
+    for s in sess.opts.debugging_opts.sanitizer {
+        let supported_targets = match s {
+            SanitizerSet::ADDRESS => ASAN_SUPPORTED_TARGETS,
+            SanitizerSet::LEAK => LSAN_SUPPORTED_TARGETS,
+            SanitizerSet::MEMORY => MSAN_SUPPORTED_TARGETS,
+            SanitizerSet::THREAD => TSAN_SUPPORTED_TARGETS,
+            _ => panic!("unrecognized sanitizer {}", s),
         };
-
         if !supported_targets.contains(&&*sess.opts.target_triple.triple()) {
             sess.err(&format!(
-                "{:?}Sanitizer only works with the `{}` target",
-                sanitizer,
-                supported_targets.join("` or `")
+                "`-Zsanitizer={}` only works with targets: {}",
+                s,
+                supported_targets.join(", ")
+            ));
+        }
+        let conflicting = sess.opts.debugging_opts.sanitizer - s;
+        if !conflicting.is_empty() {
+            sess.err(&format!(
+                "`-Zsanitizer={}` is incompatible with `-Zsanitizer={}`",
+                s, conflicting,
             ));
+            // Don't report additional errors.
+            break;
         }
     }
 }
diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs
index 3bd75095bb6..91266eeb9ba 100644
--- a/src/librustc_typeck/collect.rs
+++ b/src/librustc_typeck/collect.rs
@@ -40,6 +40,7 @@ use rustc_middle::ty::util::Discr;
 use rustc_middle::ty::util::IntTypeExt;
 use rustc_middle::ty::{self, AdtKind, Const, ToPolyTraitRef, Ty, TyCtxt};
 use rustc_middle::ty::{ReprOptions, ToPredicate, WithConstness};
+use rustc_session::config::SanitizerSet;
 use rustc_session::lint;
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
@@ -2450,11 +2451,11 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
             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;
+                        codegen_fn_attrs.no_sanitize |= SanitizerSet::ADDRESS;
                     } else if item.check_name(sym::memory) {
-                        codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_MEMORY;
+                        codegen_fn_attrs.no_sanitize |= SanitizerSet::MEMORY;
                     } else if item.check_name(sym::thread) {
-                        codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_SANITIZE_THREAD;
+                        codegen_fn_attrs.no_sanitize |= SanitizerSet::THREAD;
                     } else {
                         tcx.sess
                             .struct_span_err(item.span(), "invalid argument for `no_sanitize`")
@@ -2554,7 +2555,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
         }
     }
 
-    if codegen_fn_attrs.flags.intersects(CodegenFnAttrFlags::NO_SANITIZE_ANY) {
+    if !codegen_fn_attrs.no_sanitize.is_empty() {
         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.expect_local());
diff --git a/src/rustllvm/PassWrapper.cpp b/src/rustllvm/PassWrapper.cpp
index 323bd26c698..9bc111c26ba 100644
--- a/src/rustllvm/PassWrapper.cpp
+++ b/src/rustllvm/PassWrapper.cpp
@@ -717,11 +717,12 @@ enum class LLVMRustOptStage {
 };
 
 struct LLVMRustSanitizerOptions {
+  bool SanitizeAddress;
+  bool SanitizeAddressRecover;
   bool SanitizeMemory;
+  bool SanitizeMemoryRecover;
+  int  SanitizeMemoryTrackOrigins;
   bool SanitizeThread;
-  bool SanitizeAddress;
-  bool SanitizeRecover;
-  int SanitizeMemoryTrackOrigins;
 };
 
 extern "C" void
@@ -808,7 +809,7 @@ LLVMRustOptimizeWithNewPassManager(
     if (SanitizerOptions->SanitizeMemory) {
       MemorySanitizerOptions Options(
           SanitizerOptions->SanitizeMemoryTrackOrigins,
-          SanitizerOptions->SanitizeRecover,
+          SanitizerOptions->SanitizeMemoryRecover,
           /*CompileKernel=*/false);
 #if LLVM_VERSION_GE(10, 0)
       PipelineStartEPCallbacks.push_back([Options](ModulePassManager &MPM) {
@@ -842,14 +843,14 @@ LLVMRustOptimizeWithNewPassManager(
       OptimizerLastEPCallbacks.push_back(
         [SanitizerOptions](FunctionPassManager &FPM, PassBuilder::OptimizationLevel Level) {
           FPM.addPass(AddressSanitizerPass(
-              /*CompileKernel=*/false, SanitizerOptions->SanitizeRecover,
+              /*CompileKernel=*/false, SanitizerOptions->SanitizeAddressRecover,
               /*UseAfterScope=*/true));
         }
       );
       PipelineStartEPCallbacks.push_back(
         [SanitizerOptions](ModulePassManager &MPM) {
           MPM.addPass(ModuleAddressSanitizerPass(
-              /*CompileKernel=*/false, SanitizerOptions->SanitizeRecover));
+              /*CompileKernel=*/false, SanitizerOptions->SanitizeAddressRecover));
         }
       );
     }
diff --git a/src/test/ui/sanitize/incompatible.rs b/src/test/ui/sanitize/incompatible.rs
new file mode 100644
index 00000000000..4947f3b3d8b
--- /dev/null
+++ b/src/test/ui/sanitize/incompatible.rs
@@ -0,0 +1,6 @@
+// compile-flags: -Z sanitizer=address -Z sanitizer=memory --target x86_64-unknown-linux-gnu
+// error-pattern: error: `-Zsanitizer=address` is incompatible with `-Zsanitizer=memory`
+
+#![feature(no_core)]
+#![no_core]
+#![no_main]
diff --git a/src/test/ui/sanitize/incompatible.stderr b/src/test/ui/sanitize/incompatible.stderr
new file mode 100644
index 00000000000..f86db41bac7
--- /dev/null
+++ b/src/test/ui/sanitize/incompatible.stderr
@@ -0,0 +1,4 @@
+error: `-Zsanitizer=address` is incompatible with `-Zsanitizer=memory`
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/sanitize/unsupported-target.rs b/src/test/ui/sanitize/unsupported-target.rs
index 444333c3f01..6ccc9988cde 100644
--- a/src/test/ui/sanitize/unsupported-target.rs
+++ b/src/test/ui/sanitize/unsupported-target.rs
@@ -1,6 +1,5 @@
-// ignore-tidy-linelength
 // compile-flags: -Z sanitizer=leak --target i686-unknown-linux-gnu
-// error-pattern: error: LeakSanitizer only works with the `x86_64-unknown-linux-gnu` or `x86_64-apple-darwin` target
+// error-pattern: error: `-Zsanitizer=leak` only works with targets:
 
 #![feature(no_core)]
 #![no_core]
diff --git a/src/test/ui/sanitize/unsupported-target.stderr b/src/test/ui/sanitize/unsupported-target.stderr
index 38be58dd4b3..f9434bc9512 100644
--- a/src/test/ui/sanitize/unsupported-target.stderr
+++ b/src/test/ui/sanitize/unsupported-target.stderr
@@ -1,4 +1,4 @@
-error: LeakSanitizer only works with the `x86_64-unknown-linux-gnu` or `x86_64-apple-darwin` target
+error: `-Zsanitizer=leak` only works with targets: x86_64-apple-darwin, x86_64-unknown-linux-gnu
 
 error: aborting due to previous error