Extra features of UndefinedBehaviorSanitizer:
- - ``-fno-sanitize-recover``: By default, after a sanitizer diagnoses
- an issue, it will attempt to continue executing the program if there
- is a reasonable behavior it can give to the faulting operation. This
- option causes the program to abort instead.
- ``-fsanitize-undefined-trap-on-error``: Causes traps to be emitted
rather than calls to runtime libraries when a problem is detected.
This option is intended for use in cases where the sanitizer runtime
program. The ``-fsanitize=undefined`` checks can be combined with other
sanitizers.
+**-f[no-]sanitize-recover=check1,check2,...**
+
+ Controls which checks enabled by ``-fsanitize=`` flag are non-fatal.
+ If the check is fatal, program will halt after the first error
+ of this kind is detected and error report is printed.
+
+ By default, non-fatal checks are those enabled by UndefinedBehaviorSanitizer,
+ except for ``-fsanitize=return`` and ``-fsanitize=unreachable``. Some
+ sanitizers (e.g. :doc:`AddressSanitizer`) may not support recovery,
+ and always crash the program after the issue is detected.
+
.. option:: -fno-assume-sane-operator-new
Don't assume that the C++'s new operator is sane.
def fsanitize_address_field_padding : Joined<["-"], "fsanitize-address-field-padding=">,
Group<f_clang_Group>, Flags<[CC1Option]>,
HelpText<"Level of field padding for AddressSanitizer">;
-def fsanitize_recover : Flag<["-"], "fsanitize-recover">,
- Group<f_clang_Group>;
+def fsanitize_recover : Flag<["-"], "fsanitize-recover">, Group<f_clang_Group>;
def fno_sanitize_recover : Flag<["-"], "fno-sanitize-recover">,
- Group<f_clang_Group>, Flags<[CC1Option]>,
- HelpText<"Disable sanitizer check recovery">;
+ Group<f_clang_Group>;
+def fsanitize_recover_EQ : CommaJoined<["-"], "fsanitize-recover=">,
+ Group<f_clang_Group>,
+ Flags<[CC1Option]>,
+ HelpText<"Enable recovery for specified sanitizers">;
+def fno_sanitize_recover_EQ
+ : CommaJoined<["-"], "fno-sanitize-recover=">,
+ Group<f_clang_Group>,
+ HelpText<"Disable recovery for specified sanitizers">;
def fsanitize_undefined_trap_on_error : Flag<["-"], "fsanitize-undefined-trap-on-error">,
Group<f_clang_Group>, Flags<[CC1Option]>;
def fno_sanitize_undefined_trap_on_error : Flag<["-"], "fno-sanitize-undefined-trap-on-error">,
class SanitizerArgs {
SanitizerSet Sanitizers;
- bool SanitizeRecover;
+ SanitizerSet RecoverableSanitizers;
std::string BlacklistFile;
int SanitizeCoverage;
/// The default TLS model to use.
ENUM_CODEGENOPT(DefaultTLSModel, TLSModel, 2, GeneralDynamicTLSModel)
-CODEGENOPT(SanitizeRecover, 1, 1) ///< Attempt to recover from sanitizer checks
- ///< by continuing execution when possible
-
#undef CODEGENOPT
#undef ENUM_CODEGENOPT
#undef VALUE_CODEGENOPT
#include <memory>
#include <string>
#include <vector>
+#include "clang/Basic/Sanitizers.h"
#include "llvm/Support/Regex.h"
namespace clang {
/// Set of files definining the rules for the symbol rewriting.
std::vector<std::string> RewriteMapFiles;
+ /// Set of sanitizer checks that are non-fatal (i.e. execution should be
+ /// continued when possible).
+ SanitizerSet SanitizeRecover;
+
public:
// Define accessors/mutators for code generation options of enumeration type.
#define CODEGENOPT(Name, Bits, Default)
namespace {
/// \brief Specify under what conditions this check can be recovered
enum class CheckRecoverableKind {
- /// Always terminate program execution if this check fails
+ /// Always terminate program execution if this check fails.
Unrecoverable,
- /// Check supports recovering, allows user to specify which
+ /// Check supports recovering, runtime has both fatal (noreturn) and
+ /// non-fatal handlers for this check.
Recoverable,
/// Runtime conditionally aborts, always need to support recovery.
AlwaysRecoverable
}
}
+static void emitCheckHandlerCall(CodeGenFunction &CGF,
+ llvm::FunctionType *FnType,
+ ArrayRef<llvm::Value *> FnArgs,
+ StringRef CheckName,
+ CheckRecoverableKind RecoverKind, bool IsFatal,
+ llvm::BasicBlock *ContBB) {
+ assert(IsFatal || RecoverKind != CheckRecoverableKind::Unrecoverable);
+ bool NeedsAbortSuffix =
+ IsFatal && RecoverKind != CheckRecoverableKind::Unrecoverable;
+ std::string FnName = ("__ubsan_handle_" + CheckName +
+ (NeedsAbortSuffix ? "_abort" : "")).str();
+ bool MayReturn =
+ !IsFatal || RecoverKind == CheckRecoverableKind::AlwaysRecoverable;
+
+ llvm::AttrBuilder B;
+ if (!MayReturn) {
+ B.addAttribute(llvm::Attribute::NoReturn)
+ .addAttribute(llvm::Attribute::NoUnwind);
+ }
+ B.addAttribute(llvm::Attribute::UWTable);
+
+ llvm::Value *Fn = CGF.CGM.CreateRuntimeFunction(
+ FnType, FnName,
+ llvm::AttributeSet::get(CGF.getLLVMContext(),
+ llvm::AttributeSet::FunctionIndex, B));
+ llvm::CallInst *HandlerCall = CGF.EmitNounwindRuntimeCall(Fn, FnArgs);
+ if (!MayReturn) {
+ HandlerCall->setDoesNotReturn();
+ CGF.Builder.CreateUnreachable();
+ } else {
+ CGF.Builder.CreateBr(ContBB);
+ }
+}
+
void CodeGenFunction::EmitCheck(
ArrayRef<std::pair<llvm::Value *, SanitizerKind>> Checked,
StringRef CheckName, ArrayRef<llvm::Constant *> StaticArgs,
ArrayRef<llvm::Value *> DynamicArgs) {
assert(IsSanitizerScope);
assert(Checked.size() > 0);
- llvm::Value *Cond = Checked[0].first;
+
+ llvm::Value *FatalCond = nullptr;
+ llvm::Value *RecoverableCond = nullptr;
+ for (int i = 0, n = Checked.size(); i < n; ++i) {
+ llvm::Value *Check = Checked[i].first;
+ llvm::Value *&Cond =
+ CGM.getCodeGenOpts().SanitizeRecover.has(Checked[i].second)
+ ? RecoverableCond
+ : FatalCond;
+ Cond = Cond ? Builder.CreateAnd(Cond, Check) : Check;
+ }
+
+ llvm::Value *JointCond;
+ if (FatalCond && RecoverableCond)
+ JointCond = Builder.CreateAnd(FatalCond, RecoverableCond);
+ else
+ JointCond = FatalCond ? FatalCond : RecoverableCond;
+ assert(JointCond);
+
CheckRecoverableKind RecoverKind = getRecoverableKind(Checked[0].second);
assert(SanOpts.has(Checked[0].second));
+#ifndef NDEBUG
for (int i = 1, n = Checked.size(); i < n; ++i) {
- Cond = Builder.CreateAnd(Cond, Checked[i].first);
assert(RecoverKind == getRecoverableKind(Checked[i].second) &&
"All recoverable kinds in a single check must be same!");
assert(SanOpts.has(Checked[i].second));
}
+#endif
if (CGM.getCodeGenOpts().SanitizeUndefinedTrapOnError) {
- assert (RecoverKind != CheckRecoverableKind::AlwaysRecoverable &&
- "Runtime call required for AlwaysRecoverable kind!");
- return EmitTrapCheck(Cond);
+ assert(RecoverKind != CheckRecoverableKind::AlwaysRecoverable &&
+ "Runtime call required for AlwaysRecoverable kind!");
+ // Assume that -fsanitize-undefined-trap-on-error overrides
+ // -fsanitize-recover= options, as we can only print meaningful error
+ // message and recover if we have a runtime support.
+ return EmitTrapCheck(JointCond);
}
llvm::BasicBlock *Cont = createBasicBlock("cont");
-
- llvm::BasicBlock *Handler = createBasicBlock("handler." + CheckName);
-
- llvm::Instruction *Branch = Builder.CreateCondBr(Cond, Cont, Handler);
-
+ llvm::BasicBlock *Handlers = createBasicBlock("handler." + CheckName);
+ llvm::Instruction *Branch = Builder.CreateCondBr(JointCond, Cont, Handlers);
// Give hint that we very much don't expect to execute the handler
// Value chosen to match UR_NONTAKEN_WEIGHT, see BranchProbabilityInfo.cpp
llvm::MDBuilder MDHelper(getLLVMContext());
llvm::MDNode *Node = MDHelper.createBranchWeights((1U << 20) - 1, 1);
Branch->setMetadata(llvm::LLVMContext::MD_prof, Node);
+ EmitBlock(Handlers);
- EmitBlock(Handler);
-
+ // Emit handler arguments and create handler function type.
llvm::Constant *Info = llvm::ConstantStruct::getAnon(StaticArgs);
auto *InfoPtr =
new llvm::GlobalVariable(CGM.getModule(), Info->getType(), false,
ArgTypes.push_back(IntPtrTy);
}
- bool Recover = RecoverKind == CheckRecoverableKind::AlwaysRecoverable ||
- (RecoverKind == CheckRecoverableKind::Recoverable &&
- CGM.getCodeGenOpts().SanitizeRecover);
-
llvm::FunctionType *FnType =
llvm::FunctionType::get(CGM.VoidTy, ArgTypes, false);
- llvm::AttrBuilder B;
- if (!Recover) {
- B.addAttribute(llvm::Attribute::NoReturn)
- .addAttribute(llvm::Attribute::NoUnwind);
- }
- B.addAttribute(llvm::Attribute::UWTable);
- // Checks that have two variants use a suffix to differentiate them
- bool NeedsAbortSuffix = RecoverKind != CheckRecoverableKind::Unrecoverable &&
- !CGM.getCodeGenOpts().SanitizeRecover;
- std::string FunctionName = ("__ubsan_handle_" + CheckName +
- (NeedsAbortSuffix? "_abort" : "")).str();
- llvm::Value *Fn = CGM.CreateRuntimeFunction(
- FnType, FunctionName,
- llvm::AttributeSet::get(getLLVMContext(),
- llvm::AttributeSet::FunctionIndex, B));
- llvm::CallInst *HandlerCall = EmitNounwindRuntimeCall(Fn, Args);
- if (Recover) {
- Builder.CreateBr(Cont);
+ if (!FatalCond || !RecoverableCond) {
+ // Simple case: we need to generate a single handler call, either
+ // fatal, or non-fatal.
+ emitCheckHandlerCall(*this, FnType, Args, CheckName, RecoverKind,
+ (FatalCond != nullptr), Cont);
} else {
- HandlerCall->setDoesNotReturn();
- Builder.CreateUnreachable();
+ // Emit two handler calls: first one for set of unrecoverable checks,
+ // another one for recoverable.
+ llvm::BasicBlock *NonFatalHandlerBB =
+ createBasicBlock("non_fatal." + CheckName);
+ llvm::BasicBlock *FatalHandlerBB = createBasicBlock("fatal." + CheckName);
+ Builder.CreateCondBr(FatalCond, NonFatalHandlerBB, FatalHandlerBB);
+ EmitBlock(FatalHandlerBB);
+ emitCheckHandlerCall(*this, FnType, Args, CheckName, RecoverKind, true,
+ NonFatalHandlerBB);
+ EmitBlock(NonFatalHandlerBB);
+ emitCheckHandlerCall(*this, FnType, Args, CheckName, RecoverKind, false,
+ Cont);
}
EmitBlock(Cont);
NotAllowedWithTrap = Vptr,
RequiresPIE = Memory | DataFlow,
NeedsUnwindTables = Address | Thread | Memory | DataFlow,
- SupportsCoverage = Address | Memory | Leak | Undefined | Integer
+ SupportsCoverage = Address | Memory | Leak | Undefined | Integer,
+ RecoverableByDefault = Undefined | Integer,
+ Unrecoverable = Address | Unreachable | Return,
+ LegacyFsanitizeRecoverMask = Undefined | Integer
};
}
void SanitizerArgs::clear() {
Sanitizers.clear();
- SanitizeRecover = false;
+ RecoverableSanitizers.clear();
BlacklistFile = "";
SanitizeCoverage = 0;
MsanTrackOrigins = 0;
}
addAllOf(Sanitizers, Kinds);
- SanitizeRecover = Args.hasFlag(options::OPT_fsanitize_recover,
- options::OPT_fno_sanitize_recover, true);
+ // Parse -f(no-)?sanitize-recover flags.
+ unsigned RecoverableKinds = RecoverableByDefault;
+ unsigned DiagnosedUnrecoverableKinds = 0;
+ for (const auto *Arg : Args) {
+ if (Arg->getOption().matches(options::OPT_fsanitize_recover)) {
+ // FIXME: Add deprecation notice, and then remove this flag.
+ RecoverableKinds |= expandGroups(LegacyFsanitizeRecoverMask);
+ Arg->claim();
+ } else if (Arg->getOption().matches(options::OPT_fno_sanitize_recover)) {
+ // FIXME: Add deprecation notice, and then remove this flag.
+ RecoverableKinds &= ~expandGroups(LegacyFsanitizeRecoverMask);
+ Arg->claim();
+ } else if (Arg->getOption().matches(options::OPT_fsanitize_recover_EQ)) {
+ unsigned Add = parseArgValues(D, Arg, true);
+ // Report error if user explicitly tries to recover from unrecoverable
+ // sanitizer.
+ if (unsigned KindsToDiagnose =
+ Add & Unrecoverable & ~DiagnosedUnrecoverableKinds) {
+ SanitizerSet SetToDiagnose;
+ addAllOf(SetToDiagnose, KindsToDiagnose);
+ D.Diag(diag::err_drv_unsupported_option_argument)
+ << Arg->getOption().getName() << toString(SetToDiagnose);
+ DiagnosedUnrecoverableKinds |= KindsToDiagnose;
+ }
+ RecoverableKinds |= expandGroups(Add);
+ Arg->claim();
+ } else if (Arg->getOption().matches(options::OPT_fno_sanitize_recover_EQ)) {
+ RecoverableKinds &= ~expandGroups(parseArgValues(D, Arg, true));
+ Arg->claim();
+ }
+ }
+ RecoverableKinds &= Kinds;
+ RecoverableKinds &= ~Unrecoverable;
+ addAllOf(RecoverableSanitizers, RecoverableKinds);
UbsanTrapOnError =
Args.hasFlag(options::OPT_fsanitize_undefined_trap_on_error,
return;
CmdArgs.push_back(Args.MakeArgString("-fsanitize=" + toString(Sanitizers)));
- if (!SanitizeRecover)
- CmdArgs.push_back("-fno-sanitize-recover");
+ if (!RecoverableSanitizers.empty())
+ CmdArgs.push_back(Args.MakeArgString("-fsanitize-recover=" +
+ toString(RecoverableSanitizers)));
if (UbsanTrapOnError)
CmdArgs.push_back("-fsanitize-undefined-trap-on-error");
unsigned parseArgValues(const Driver &D, const llvm::opt::Arg *A,
bool DiagnoseErrors) {
assert((A->getOption().matches(options::OPT_fsanitize_EQ) ||
- A->getOption().matches(options::OPT_fno_sanitize_EQ)) &&
+ A->getOption().matches(options::OPT_fno_sanitize_EQ) ||
+ A->getOption().matches(options::OPT_fsanitize_recover_EQ) ||
+ A->getOption().matches(options::OPT_fno_sanitize_recover_EQ)) &&
"Invalid argument in parseArgValues!");
unsigned Kinds = 0;
for (unsigned I = 0, N = A->getNumValues(); I != N; ++I) {
return Pattern;
}
+static void parseSanitizerKinds(StringRef FlagName,
+ const std::vector<std::string> &Sanitizers,
+ DiagnosticsEngine &Diags, SanitizerSet &S) {
+ for (const auto &Sanitizer : Sanitizers) {
+ SanitizerKind K = llvm::StringSwitch<SanitizerKind>(Sanitizer)
+#define SANITIZER(NAME, ID) .Case(NAME, SanitizerKind::ID)
+#include "clang/Basic/Sanitizers.def"
+ .Default(SanitizerKind::Unknown);
+ if (K == SanitizerKind::Unknown)
+ Diags.Report(diag::err_drv_invalid_value) << FlagName << Sanitizer;
+ else
+ S.set(K, true);
+ }
+}
+
static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK,
DiagnosticsEngine &Diags,
const TargetOptions &TargetOpts) {
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
Opts.VerifyModule = !Args.hasArg(OPT_disable_llvm_verifier);
- Opts.SanitizeRecover = !Args.hasArg(OPT_fno_sanitize_recover);
Opts.DisableGCov = Args.hasArg(OPT_test_coverage);
Opts.EmitGcovArcs = Args.hasArg(OPT_femit_coverage_data);
Opts.RewriteMapFiles = Args.getAllArgValues(OPT_frewrite_map_file);
+ // Parse -fsanitize-recover= arguments.
+ // FIXME: Report unrecoverable sanitizers incorrectly specified here.
+ parseSanitizerKinds("-fsanitize-recover=",
+ Args.getAllArgValues(OPT_fsanitize_recover_EQ), Diags,
+ Opts.SanitizeRecover);
+
return Success;
}
}
// Parse -fsanitize= arguments.
- std::vector<std::string> Sanitizers = Args.getAllArgValues(OPT_fsanitize_EQ);
- for (const auto &Sanitizer : Sanitizers) {
- SanitizerKind K = llvm::StringSwitch<SanitizerKind>(Sanitizer)
-#define SANITIZER(NAME, ID) .Case(NAME, SanitizerKind::ID)
-#include "clang/Basic/Sanitizers.def"
- .Default(SanitizerKind::Unknown);
- if (K == SanitizerKind::Unknown)
- Diags.Report(diag::err_drv_invalid_value)
- << "-fsanitize=" << Sanitizer;
- else
- Opts.Sanitize.set(K, true);
- }
+ parseSanitizerKinds("-fsanitize=", Args.getAllArgValues(OPT_fsanitize_EQ),
+ Diags, Opts.Sanitize);
// -fsanitize-address-field-padding=N has to be a LangOpt, parse it here.
Opts.SanitizeAddressFieldPadding =
getLastArgIntValue(Args, OPT_fsanitize_address_field_padding, 0, Diags);
-// RUN: %clang_cc1 -fsanitize=alignment,null,object-size,shift,return,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute,nonnull-attribute -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-COMMON --check-prefix=CHECK-UBSAN
-// RUN: %clang_cc1 -fsanitize-undefined-trap-on-error -fsanitize=alignment,null,object-size,shift,return,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute,nonnull-attribute -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-COMMON --check-prefix=CHECK-TRAP
-// RUN: %clang_cc1 -fsanitize=null -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-NULL
+// RUN: %clang_cc1 -fsanitize=alignment,null,object-size,shift,return,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute,nonnull-attribute -fsanitize-recover=alignment,null,object-size,shift,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute,nonnull-attribute -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-COMMON --check-prefix=CHECK-UBSAN
+// RUN: %clang_cc1 -fsanitize-undefined-trap-on-error -fsanitize=alignment,null,object-size,shift,return,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute,nonnull-attribute -fsanitize-recover=alignment,null,object-size,shift,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute,nonnull-attribute -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-COMMON --check-prefix=CHECK-TRAP
+// RUN: %clang_cc1 -fsanitize=null -fsanitize-recover=null -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-NULL
// RUN: %clang_cc1 -fsanitize=signed-integer-overflow -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-OVERFLOW
// CHECK-UBSAN: @[[INT:.*]] = private unnamed_addr constant { i16, i16, [6 x i8] } { i16 0, i16 11, [6 x i8] c"'int'\00" }
// Verify proper type emitted for compound assignments
-// RUN: %clang_cc1 -ffreestanding -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=signed-integer-overflow,unsigned-integer-overflow | FileCheck %s
+// RUN: %clang_cc1 -ffreestanding -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-recover=signed-integer-overflow,unsigned-integer-overflow | FileCheck %s
#include <stdint.h>
-// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fsanitize=unsigned-integer-overflow %s -emit-llvm -o - | FileCheck %s --check-prefix=RECOVER
-// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fsanitize=unsigned-integer-overflow -fno-sanitize-recover %s -emit-llvm -o - | FileCheck %s --check-prefix=ABORT
-
+// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fsanitize=unsigned-integer-overflow -fsanitize-recover=unsigned-integer-overflow %s -emit-llvm -o - | FileCheck %s --check-prefix=RECOVER
+// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fsanitize=unsigned-integer-overflow %s -emit-llvm -o - | FileCheck %s --check-prefix=ABORT
+// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fsanitize=null,object-size,alignment -fsanitize-recover=object-size %s -emit-llvm -o - | FileCheck %s --check-prefix=PARTIAL
// RECOVER: @test
// ABORT: @test
// ABORT: unreachable
x = y + z;
}
+
+void foo() {
+ union { int i; } u;
+ u.i=1;
+ // PARTIAL: %[[CHECK0:.*]] = icmp ne {{.*}}* %[[PTR:.*]], null
+
+ // PARTIAL: %[[SIZE:.*]] = call i64 @llvm.objectsize.i64.p0i8(i8* {{.*}}, i1 false)
+ // PARTIAL-NEXT: %[[CHECK1:.*]] = icmp uge i64 %[[SIZE]], 4
+
+ // PARTIAL: %[[MISALIGN:.*]] = and i64 {{.*}}, 3
+ // PARTIAL-NEXT: %[[CHECK2:.*]] = icmp eq i64 %[[MISALIGN]], 0
+
+ // PARTIAL: %[[CHECK02:.*]] = and i1 %[[CHECK0]], %[[CHECK2]]
+ // PARTIAL-NEXT: %[[CHECK012:.*]] = and i1 %[[CHECK02]], %[[CHECK1]]
+
+ // PARTIAL: br i1 %[[CHECK012]], {{.*}} !prof ![[WEIGHT_MD:.*]], !nosanitize
+
+ // PARTIAL: br i1 %[[CHECK02]], {{.*}}
+ // PARTIAL: call void @__ubsan_handle_type_mismatch_abort(
+ // PARTIAL-NEXT: unreachable
+ // PARTIAL: call void @__ubsan_handle_type_mismatch(
+}
// Verify ubsan vptr does not check down-casts on blacklisted types.
// RUN: echo "type:_ZTI3Foo" > %t-type.blacklist
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=vptr -emit-llvm %s -o - | FileCheck %s --check-prefix=DEFAULT
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=vptr -fsanitize-blacklist=%t-type.blacklist -emit-llvm %s -o - | FileCheck %s --check-prefix=TYPE
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=vptr -fsanitize-recover=vptr -emit-llvm %s -o - | FileCheck %s --check-prefix=DEFAULT
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=vptr -fsanitize-recover=vptr -fsanitize-blacklist=%t-type.blacklist -emit-llvm %s -o - | FileCheck %s --check-prefix=TYPE
class Bar {
public:
-// RUN: %clang_cc1 -std=c++11 -fsanitize=signed-integer-overflow,integer-divide-by-zero,float-divide-by-zero,shift,unreachable,return,vla-bound,alignment,null,vptr,object-size,float-cast-overflow,bool,enum,array-bounds,function -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s
-// RUN: %clang_cc1 -std=c++11 -fsanitize=vptr,address -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-ASAN
-// RUN: %clang_cc1 -std=c++11 -fsanitize=vptr -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=DOWNCAST-NULL
+// RUN: %clang_cc1 -std=c++11 -fsanitize=signed-integer-overflow,integer-divide-by-zero,float-divide-by-zero,shift,unreachable,return,vla-bound,alignment,null,vptr,object-size,float-cast-overflow,bool,enum,array-bounds,function -fsanitize-recover=signed-integer-overflow,integer-divide-by-zero,float-divide-by-zero,shift,vla-bound,alignment,null,vptr,object-size,float-cast-overflow,bool,enum,array-bounds,function -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s
+// RUN: %clang_cc1 -std=c++11 -fsanitize=vptr,address -fsanitize-recover=vptr,address -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-ASAN
+// RUN: %clang_cc1 -std=c++11 -fsanitize=vptr -fsanitize-recover=vptr -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=DOWNCAST-NULL
struct S {
double d;
// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -### 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fsanitize-recover -### 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
+// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fsanitize-recover=all -### 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fno-sanitize-recover -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-RECOVER
-// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fno-sanitize-recover -fsanitize-recover -### 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
-// RUZ: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fsanitize-recover -fno-sanitize-recover -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-RECOVER
-// CHECK-RECOVER-NOT: sanitize-recover
-// CHECK-NO-RECOVER: "-fno-sanitize-recover"
+// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fno-sanitize-recover=all -fsanitize-recover=thread -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-RECOVER
+// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fno-sanitize-recover -fsanitize-recover=undefined -### 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
+// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fsanitize-recover -fno-sanitize-recover -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-RECOVER
+// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fno-sanitize-recover=all -fsanitize-recover=object-size -### 2>&1 | FileCheck %s --check-prefix=CHECK-PARTIAL-RECOVER
+
+// CHECK-RECOVER: "-fsanitize-recover={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|function|shift|vla-bound|alignment|null|vptr|object-size|float-cast-overflow|array-bounds|enum|bool|returns-nonnull-attribute|nonnull-attribute),?){16}"}}
+// CHECK-NO-RECOVER-NOT: sanitize-recover
+// CHECK-PARTIAL-RECOVER: "-fsanitize-recover=object-size"
+
+// RUN: %clang -target x86_64-linux-gnu %s -fsanitize=undefined -fsanitize-recover=address,foobar,object-size,unreachable -### 2>&1 | FileCheck %s --check-prefix=CHECK-DIAG-RECOVER
+// CHECK-DIAG-RECOVER: unsupported argument 'foobar' to option 'fsanitize-recover='
+// CHECK-DIAG-RECOVER: unsupported argument 'address,unreachable' to option 'fsanitize-recover='
// RUN: %clang -target x86_64-linux-gnu -fsanitize=leak %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANL
// CHECK-SANL: "-fsanitize=leak"