[Sanitizer] Refactor SanitizerArgs parsing in Driver.
Remove flag parsing details from the public header.
Use SanitizerSet to represent the set of enabled sanitizers.
Cleanup the implementation: update the comments to
reflect reality, remove dead code.
No functionality change.
llvm-svn: 221968
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 0490cc0..d2b3a91 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -21,8 +21,120 @@
using namespace clang::driver;
using namespace llvm::opt;
+namespace {
+/// Assign ordinals to possible values of -fsanitize= flag.
+/// We use the ordinal values as bit positions within \c SanitizeKind.
+enum SanitizeOrdinal {
+#define SANITIZER(NAME, ID) SO_##ID,
+#define SANITIZER_GROUP(NAME, ID, ALIAS) SO_##ID##Group,
+#include "clang/Basic/Sanitizers.def"
+ SO_Count
+};
+
+/// Represents a set of sanitizer kinds. It is also used to define:
+/// 1) set of sanitizers each sanitizer group expands into.
+/// 2) set of sanitizers sharing a specific property (e.g.
+/// all sanitizers with zero-base shadow).
+enum SanitizeKind {
+#define SANITIZER(NAME, ID) ID = 1 << SO_##ID,
+#define SANITIZER_GROUP(NAME, ID, ALIAS) \
+ID = ALIAS, ID##Group = 1 << SO_##ID##Group,
+#include "clang/Basic/Sanitizers.def"
+ NeedsUbsanRt = Undefined | Integer,
+ NotAllowedWithTrap = Vptr,
+ HasZeroBaseShadow = Thread | Memory | DataFlow,
+ NeedsUnwindTables = Address | Thread | Memory | DataFlow
+};
+}
+
+/// Returns true if set of \p Sanitizers contain at least one sanitizer from
+/// \p Kinds.
+static bool hasOneOf(const clang::SanitizerSet &Sanitizers, unsigned Kinds) {
+#define SANITIZER(NAME, ID) \
+ if (Sanitizers.has(clang::SanitizerKind::ID) && (Kinds & ID)) \
+ return true;
+#include "clang/Basic/Sanitizers.def"
+ return false;
+}
+
+/// Adds all sanitizers from \p Kinds to \p Sanitizers.
+static void addAllOf(clang::SanitizerSet &Sanitizers, unsigned Kinds) {
+#define SANITIZER(NAME, ID) \
+ if (Kinds & ID) \
+ Sanitizers.set(clang::SanitizerKind::ID, true);
+#include "clang/Basic/Sanitizers.def"
+}
+
+static unsigned toSanitizeKind(clang::SanitizerKind K) {
+#define SANITIZER(NAME, ID) \
+ if (K == clang::SanitizerKind::ID) \
+ return ID;
+#include "clang/Basic/Sanitizers.def"
+ llvm_unreachable("Invalid SanitizerKind!");
+}
+
+/// Parse a single value from a -fsanitize= or -fno-sanitize= value list.
+/// Returns a member of the \c SanitizeKind enumeration, or \c 0
+/// if \p Value is not known.
+static unsigned parseValue(const char *Value);
+
+/// Parse a -fsanitize= or -fno-sanitize= argument's values, diagnosing any
+/// invalid components. Returns OR of members of \c SanitizeKind enumeration.
+static unsigned parseArgValues(const Driver &D, const llvm::opt::Arg *A,
+ bool DiagnoseErrors);
+
+/// Parse a single flag of the form -f[no]sanitize=.
+/// Sets the masks defining required change of the set of sanitizers.
+/// Returns true if the flag was parsed successfully.
+static bool parseArgument(const Driver &D, const llvm::opt::Arg *A,
+ unsigned &Add, unsigned &Remove, bool DiagnoseErrors);
+
+/// Produce an argument string from ArgList \p Args, which shows how it
+/// provides some sanitizer kind from \p Mask. For example, the argument list
+/// "-fsanitize=thread,vptr -fsanitize=address" with mask \c NeedsUbsanRt
+/// would produce "-fsanitize=vptr".
+static std::string lastArgumentForMask(const Driver &D,
+ const llvm::opt::ArgList &Args,
+ unsigned Mask);
+
+static std::string lastArgumentForKind(const Driver &D,
+ const llvm::opt::ArgList &Args,
+ clang::SanitizerKind K) {
+ return lastArgumentForMask(D, Args, toSanitizeKind(K));
+}
+
+/// Produce an argument string from argument \p A, which shows how it provides
+/// a value in \p Mask. For instance, the argument
+/// "-fsanitize=address,alignment" with mask \c NeedsUbsanRt would produce
+/// "-fsanitize=alignment".
+static std::string describeSanitizeArg(const llvm::opt::Arg *A, unsigned Mask);
+
+/// For each sanitizer group bit set in \p Kinds, set the bits for sanitizers
+/// this group enables.
+static unsigned expandGroups(unsigned Kinds);
+
+/// Return the subset of \p Kinds supported by toolchain \p TC. If
+/// \p DiagnoseErrors is true, produce an error diagnostic for each sanitizer
+/// removed from \p Kinds.
+static unsigned filterUnsupportedKinds(const ToolChain &TC, unsigned Kinds,
+ const llvm::opt::Arg *A,
+ bool DiagnoseErrors,
+ unsigned &DiagnosedKinds);
+
+bool SanitizerArgs::needsUbsanRt() const {
+ return !UbsanTrapOnError && hasOneOf(Sanitizers, NeedsUbsanRt);
+}
+
+bool SanitizerArgs::hasZeroBaseShadow() const {
+ return AsanZeroBaseShadow || hasOneOf(Sanitizers, HasZeroBaseShadow);
+}
+
+bool SanitizerArgs::needsUnwindTables() const {
+ return hasOneOf(Sanitizers, NeedsUnwindTables);
+}
+
void SanitizerArgs::clear() {
- Kind = 0;
+ Sanitizers.clear();
BlacklistFile = "";
SanitizeCoverage = 0;
MsanTrackOrigins = 0;
@@ -45,7 +157,7 @@
for (ArgList::const_reverse_iterator I = Args.rbegin(), E = Args.rend();
I != E; ++I) {
unsigned Add, Remove;
- if (!parse(D, Args, *I, Add, Remove, true))
+ if (!parseArgument(D, *I, Add, Remove, true))
continue;
(*I)->claim();
@@ -55,17 +167,17 @@
Add &= ~AllRemove;
// At this point we have not expanded groups, so any unsupported sanitizers
// in Add are those which have been explicitly enabled. Diagnose them.
- Add = filterUnsupportedKinds(TC, Add, Args, *I, /*DiagnoseErrors=*/true,
+ Add = filterUnsupportedKinds(TC, Add, *I, /*DiagnoseErrors=*/true,
DiagnosedKinds);
Add = expandGroups(Add);
// Group expansion may have enabled a sanitizer which is disabled later.
Add &= ~AllRemove;
// Silently discard any unsupported sanitizers implicitly enabled through
// group expansion.
- Add = filterUnsupportedKinds(TC, Add, Args, *I, /*DiagnoseErrors=*/false,
+ Add = filterUnsupportedKinds(TC, Add, *I, /*DiagnoseErrors=*/false,
DiagnosedKinds);
- Kind |= Add;
+ addAllOf(Sanitizers, Add);
}
UbsanTrapOnError =
@@ -73,37 +185,37 @@
options::OPT_fno_sanitize_undefined_trap_on_error, false);
// Warn about undefined sanitizer options that require runtime support.
- if (UbsanTrapOnError && notAllowedWithTrap()) {
- D.Diag(diag::err_drv_argument_not_allowed_with)
- << lastArgumentForKind(D, Args, NotAllowedWithTrap)
+ if (UbsanTrapOnError && hasOneOf(Sanitizers, NotAllowedWithTrap)) {
+ D.Diag(clang::diag::err_drv_argument_not_allowed_with)
+ << lastArgumentForMask(D, Args, NotAllowedWithTrap)
<< "-fsanitize-undefined-trap-on-error";
}
- // Only one runtime library can be used at once.
- bool NeedsAsan = needsAsanRt();
- bool NeedsTsan = needsTsanRt();
- bool NeedsMsan = needsMsanRt();
- bool NeedsLsan = needsLeakDetection();
+ // Check for incompatible sanitizers.
+ bool NeedsAsan = Sanitizers.has(SanitizerKind::Address);
+ bool NeedsTsan = Sanitizers.has(SanitizerKind::Thread);
+ bool NeedsMsan = Sanitizers.has(SanitizerKind::Memory);
+ bool NeedsLsan = Sanitizers.has(SanitizerKind::Leak);
if (NeedsAsan && NeedsTsan)
- D.Diag(diag::err_drv_argument_not_allowed_with)
- << lastArgumentForKind(D, Args, NeedsAsanRt)
- << lastArgumentForKind(D, Args, NeedsTsanRt);
+ D.Diag(clang::diag::err_drv_argument_not_allowed_with)
+ << lastArgumentForKind(D, Args, SanitizerKind::Address)
+ << lastArgumentForKind(D, Args, SanitizerKind::Thread);
if (NeedsAsan && NeedsMsan)
- D.Diag(diag::err_drv_argument_not_allowed_with)
- << lastArgumentForKind(D, Args, NeedsAsanRt)
- << lastArgumentForKind(D, Args, NeedsMsanRt);
+ D.Diag(clang::diag::err_drv_argument_not_allowed_with)
+ << lastArgumentForKind(D, Args, SanitizerKind::Address)
+ << lastArgumentForKind(D, Args, SanitizerKind::Memory);
if (NeedsTsan && NeedsMsan)
- D.Diag(diag::err_drv_argument_not_allowed_with)
- << lastArgumentForKind(D, Args, NeedsTsanRt)
- << lastArgumentForKind(D, Args, NeedsMsanRt);
+ D.Diag(clang::diag::err_drv_argument_not_allowed_with)
+ << lastArgumentForKind(D, Args, SanitizerKind::Thread)
+ << lastArgumentForKind(D, Args, SanitizerKind::Memory);
if (NeedsLsan && NeedsTsan)
- D.Diag(diag::err_drv_argument_not_allowed_with)
- << lastArgumentForKind(D, Args, NeedsLeakDetection)
- << lastArgumentForKind(D, Args, NeedsTsanRt);
+ D.Diag(clang::diag::err_drv_argument_not_allowed_with)
+ << lastArgumentForKind(D, Args, SanitizerKind::Leak)
+ << lastArgumentForKind(D, Args, SanitizerKind::Thread);
if (NeedsLsan && NeedsMsan)
- D.Diag(diag::err_drv_argument_not_allowed_with)
- << lastArgumentForKind(D, Args, NeedsLeakDetection)
- << lastArgumentForKind(D, Args, NeedsMsanRt);
+ D.Diag(clang::diag::err_drv_argument_not_allowed_with)
+ << lastArgumentForKind(D, Args, SanitizerKind::Leak)
+ << lastArgumentForKind(D, Args, SanitizerKind::Memory);
// FIXME: Currently -fsanitize=leak is silently ignored in the presence of
// -fsanitize=address. Perhaps it should print an error, or perhaps
// -f(-no)sanitize=leak should change whether leak detection is enabled by
@@ -120,11 +232,11 @@
std::unique_ptr<llvm::SpecialCaseList> SCL(
llvm::SpecialCaseList::create(BLPath, BLError));
if (!SCL.get())
- D.Diag(diag::err_drv_malformed_sanitizer_blacklist) << BLError;
+ D.Diag(clang::diag::err_drv_malformed_sanitizer_blacklist) << BLError;
else
BlacklistFile = BLPath;
} else {
- D.Diag(diag::err_drv_no_such_file) << BLPath;
+ D.Diag(clang::diag::err_drv_no_such_file) << BLPath;
}
}
} else {
@@ -150,7 +262,7 @@
StringRef S = A->getValue();
if (S.getAsInteger(0, MsanTrackOrigins) || MsanTrackOrigins < 0 ||
MsanTrackOrigins > 2) {
- D.Diag(diag::err_drv_invalid_value) << A->getAsString(Args) << S;
+ D.Diag(clang::diag::err_drv_invalid_value) << A->getAsString(Args) << S;
}
}
}
@@ -163,7 +275,7 @@
// Legal values are 0..4.
if (S.getAsInteger(0, SanitizeCoverage) || SanitizeCoverage < 0 ||
SanitizeCoverage > 4)
- D.Diag(diag::err_drv_invalid_value) << A->getAsString(Args) << S;
+ D.Diag(clang::diag::err_drv_invalid_value) << A->getAsString(Args) << S;
}
}
@@ -179,7 +291,7 @@
// Legal values are 0 and 1, 2, but in future we may add more levels.
if (S.getAsInteger(0, AsanFieldPadding) || AsanFieldPadding < 0 ||
AsanFieldPadding > 2) {
- D.Diag(diag::err_drv_invalid_value) << A->getAsString(Args) << S;
+ D.Diag(clang::diag::err_drv_invalid_value) << A->getAsString(Args) << S;
}
}
@@ -191,10 +303,10 @@
case options::OPT__SLASH_MTd:
case options::OPT__SLASH_MDd:
case options::OPT__SLASH_LDd:
- D.Diag(diag::err_drv_argument_not_allowed_with)
+ D.Diag(clang::diag::err_drv_argument_not_allowed_with)
<< WindowsDebugRTArg->getAsString(Args)
- << lastArgumentForKind(D, Args, NeedsAsanRt);
- D.Diag(diag::note_drv_address_sanitizer_debug_runtime);
+ << lastArgumentForKind(D, Args, SanitizerKind::Address);
+ D.Diag(clang::diag::note_drv_address_sanitizer_debug_runtime);
}
}
}
@@ -206,11 +318,11 @@
void SanitizerArgs::addArgs(const llvm::opt::ArgList &Args,
llvm::opt::ArgStringList &CmdArgs) const {
- if (!Kind)
+ if (Sanitizers.empty())
return;
SmallString<256> SanitizeOpt("-fsanitize=");
#define SANITIZER(NAME, ID) \
- if (Kind & ID) \
+ if (Sanitizers.has(SanitizerKind::ID)) \
SanitizeOpt += NAME ",";
#include "clang/Basic/Sanitizers.def"
SanitizeOpt.pop_back();
@@ -231,11 +343,31 @@
CmdArgs.push_back(Args.MakeArgString("-fsanitize-coverage=" +
llvm::utostr(SanitizeCoverage)));
// Workaround for PR16386.
- if (needsMsanRt())
+ if (Sanitizers.has(SanitizerKind::Memory))
CmdArgs.push_back(Args.MakeArgString("-fno-assume-sane-operator-new"));
}
-unsigned SanitizerArgs::parse(const char *Value) {
+bool SanitizerArgs::getDefaultBlacklist(const Driver &D, std::string &BLPath) {
+ const char *BlacklistFile = nullptr;
+ if (Sanitizers.has(SanitizerKind::Address))
+ BlacklistFile = "asan_blacklist.txt";
+ else if (Sanitizers.has(SanitizerKind::Memory))
+ BlacklistFile = "msan_blacklist.txt";
+ else if (Sanitizers.has(SanitizerKind::Thread))
+ BlacklistFile = "tsan_blacklist.txt";
+ else if (Sanitizers.has(SanitizerKind::DataFlow))
+ BlacklistFile = "dfsan_abilist.txt";
+
+ if (BlacklistFile) {
+ SmallString<64> Path(D.ResourceDir);
+ llvm::sys::path::append(Path, BlacklistFile);
+ BLPath = Path.str();
+ return true;
+ }
+ return false;
+}
+
+unsigned parseValue(const char *Value) {
unsigned ParsedKind = llvm::StringSwitch<SanitizeKind>(Value)
#define SANITIZER(NAME, ID) .Case(NAME, ID)
#define SANITIZER_GROUP(NAME, ID, ALIAS) .Case(NAME, ID##Group)
@@ -244,44 +376,21 @@
return ParsedKind;
}
-unsigned SanitizerArgs::expandGroups(unsigned Kinds) {
+unsigned expandGroups(unsigned Kinds) {
#define SANITIZER(NAME, ID)
#define SANITIZER_GROUP(NAME, ID, ALIAS) if (Kinds & ID##Group) Kinds |= ID;
#include "clang/Basic/Sanitizers.def"
return Kinds;
}
-void SanitizerArgs::filterUnsupportedMask(const ToolChain &TC, unsigned &Kinds,
- unsigned Mask,
- const llvm::opt::ArgList &Args,
- const llvm::opt::Arg *A,
- bool DiagnoseErrors,
- unsigned &DiagnosedKinds) {
- unsigned MaskedKinds = Kinds & Mask;
- if (!MaskedKinds)
- return;
- Kinds &= ~Mask;
- // Do we have new kinds to diagnose?
- if (DiagnoseErrors && (DiagnosedKinds & MaskedKinds) != MaskedKinds) {
- // Only diagnose the new kinds.
- std::string Desc =
- describeSanitizeArg(Args, A, MaskedKinds & ~DiagnosedKinds);
- TC.getDriver().Diag(diag::err_drv_unsupported_opt_for_target)
- << Desc << TC.getTriple().str();
- DiagnosedKinds |= MaskedKinds;
- }
-}
-
-unsigned SanitizerArgs::filterUnsupportedKinds(const ToolChain &TC,
- unsigned Kinds,
- const llvm::opt::ArgList &Args,
- const llvm::opt::Arg *A,
- bool DiagnoseErrors,
- unsigned &DiagnosedKinds) {
+unsigned filterUnsupportedKinds(const ToolChain &TC, unsigned Kinds,
+ const llvm::opt::Arg *A, bool DiagnoseErrors,
+ unsigned &DiagnosedKinds) {
bool IsFreeBSD = TC.getTriple().getOS() == llvm::Triple::FreeBSD;
bool IsLinux = TC.getTriple().getOS() == llvm::Triple::Linux;
bool IsX86 = TC.getTriple().getArch() == llvm::Triple::x86;
bool IsX86_64 = TC.getTriple().getArch() == llvm::Triple::x86_64;
+
unsigned KindsToFilterOut = 0;
if (!(IsLinux && IsX86_64)) {
KindsToFilterOut |= Memory | DataFlow;
@@ -292,64 +401,70 @@
if (!(IsLinux && (IsX86 || IsX86_64))) {
KindsToFilterOut |= Function;
}
- filterUnsupportedMask(TC, Kinds, KindsToFilterOut, Args, A, DiagnoseErrors,
- DiagnosedKinds);
- return Kinds;
+ KindsToFilterOut &= Kinds;
+
+ // Do we have new kinds to diagnose?
+ unsigned KindsToDiagnose = KindsToFilterOut & ~DiagnosedKinds;
+ if (DiagnoseErrors && KindsToDiagnose) {
+ // Only diagnose the new kinds.
+ std::string Desc = describeSanitizeArg(A, KindsToDiagnose);
+ TC.getDriver().Diag(clang::diag::err_drv_unsupported_opt_for_target)
+ << Desc << TC.getTriple().str();
+ DiagnosedKinds |= KindsToFilterOut;
+ }
+
+ return Kinds & ~KindsToFilterOut;
}
-unsigned SanitizerArgs::parse(const Driver &D, const llvm::opt::Arg *A,
- bool DiagnoseErrors) {
+unsigned parseArgValues(const Driver &D, const llvm::opt::Arg *A,
+ bool DiagnoseErrors) {
unsigned Kind = 0;
for (unsigned I = 0, N = A->getNumValues(); I != N; ++I) {
- if (unsigned K = parse(A->getValue(I)))
+ if (unsigned K = parseValue(A->getValue(I)))
Kind |= K;
else if (DiagnoseErrors)
- D.Diag(diag::err_drv_unsupported_option_argument)
+ D.Diag(clang::diag::err_drv_unsupported_option_argument)
<< A->getOption().getName() << A->getValue(I);
}
return Kind;
}
-bool SanitizerArgs::parse(const Driver &D, const llvm::opt::ArgList &Args,
- const llvm::opt::Arg *A, unsigned &Add,
- unsigned &Remove, bool DiagnoseErrors) {
+bool parseArgument(const Driver &D, const llvm::opt::Arg *A, unsigned &Add,
+ unsigned &Remove, bool DiagnoseErrors) {
Add = 0;
Remove = 0;
if (A->getOption().matches(options::OPT_fsanitize_EQ)) {
- Add = parse(D, A, DiagnoseErrors);
- } else if (A->getOption().matches(options::OPT_fno_sanitize_EQ)) {
- Remove = parse(D, A, DiagnoseErrors);
- } else {
- // Flag is not relevant to sanitizers.
- return false;
+ Add = parseArgValues(D, A, DiagnoseErrors);
+ return true;
}
- return true;
+ if (A->getOption().matches(options::OPT_fno_sanitize_EQ)) {
+ Remove = parseArgValues(D, A, DiagnoseErrors);
+ return true;
+ }
+ return false;
}
-std::string SanitizerArgs::lastArgumentForKind(const Driver &D,
- const llvm::opt::ArgList &Args,
- unsigned Kind) {
+std::string lastArgumentForMask(const Driver &D, const llvm::opt::ArgList &Args,
+ unsigned Mask) {
for (llvm::opt::ArgList::const_reverse_iterator I = Args.rbegin(),
E = Args.rend();
I != E; ++I) {
unsigned Add, Remove;
- if (parse(D, Args, *I, Add, Remove, false) &&
- (expandGroups(Add) & Kind))
- return describeSanitizeArg(Args, *I, Kind);
- Kind &= ~Remove;
+ if (parseArgument(D, *I, Add, Remove, false) &&
+ (expandGroups(Add) & Mask))
+ return describeSanitizeArg(*I, Mask);
+ Mask &= ~Remove;
}
llvm_unreachable("arg list didn't provide expected value");
}
-std::string SanitizerArgs::describeSanitizeArg(const llvm::opt::ArgList &Args,
- const llvm::opt::Arg *A,
- unsigned Mask) {
- if (!A->getOption().matches(options::OPT_fsanitize_EQ))
- return A->getAsString(Args);
+std::string describeSanitizeArg(const llvm::opt::Arg *A, unsigned Mask) {
+ assert(A->getOption().matches(options::OPT_fsanitize_EQ)
+ && "Invalid argument in describeSanitizerArg!");
std::string Sanitizers;
for (unsigned I = 0, N = A->getNumValues(); I != N; ++I) {
- if (expandGroups(parse(A->getValue(I))) & Mask) {
+ if (expandGroups(parseValue(A->getValue(I))) & Mask) {
if (!Sanitizers.empty())
Sanitizers += ",";
Sanitizers += A->getValue(I);
@@ -359,23 +474,3 @@
assert(!Sanitizers.empty() && "arg didn't provide expected value");
return "-fsanitize=" + Sanitizers;
}
-
-bool SanitizerArgs::getDefaultBlacklist(const Driver &D, std::string &BLPath) {
- const char *BlacklistFile = nullptr;
- if (Kind & NeedsAsanRt)
- BlacklistFile = "asan_blacklist.txt";
- else if (Kind & NeedsMsanRt)
- BlacklistFile = "msan_blacklist.txt";
- else if (Kind & NeedsTsanRt)
- BlacklistFile = "tsan_blacklist.txt";
- else if (Kind & NeedsDfsanRt)
- BlacklistFile = "dfsan_abilist.txt";
-
- if (BlacklistFile) {
- SmallString<64> Path(D.ResourceDir);
- llvm::sys::path::append(Path, BlacklistFile);
- BLPath = Path.str();
- return true;
- }
- return false;
-}