Author: Thurston Dang Date: 2025-01-09T21:52:30-08:00 New Revision: 76fac9c01736b1254e42427f8e0910c0f1d01fba
URL: https://github.com/llvm/llvm-project/commit/76fac9c01736b1254e42427f8e0910c0f1d01fba DIFF: https://github.com/llvm/llvm-project/commit/76fac9c01736b1254e42427f8e0910c0f1d01fba.diff LOG: [sanitizer] Parse weighted sanitizer args and -fsanitize-skip-hot-cutoff (#121619) This adds a function to parse weighted sanitizer flags (e.g., `-fsanitize-blah=undefined=0.5,null=0.3`) and adds the plumbing to apply that to a new flag, `-fsanitize-skip-hot-cutoff`. `-fsanitize-skip-hot-cutoff` currently has no effect; future work will use it to generalize ubsan-guard-checks (originally introduced in 5f9ed2ff8364ff3e4fac410472f421299dafa793). --------- Co-authored-by: Vitaly Buka <vitalyb...@google.com> Added: Modified: clang/include/clang/Basic/CodeGenOptions.h clang/include/clang/Basic/Sanitizers.h clang/include/clang/Driver/Options.td clang/include/clang/Driver/SanitizerArgs.h clang/lib/Basic/Sanitizers.cpp clang/lib/Driver/SanitizerArgs.cpp clang/lib/Frontend/CompilerInvocation.cpp clang/test/Driver/fsanitize.c Removed: ################################################################################ diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index c555fb3b72d648..b64ad74d711c60 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -384,6 +384,11 @@ class CodeGenOptions : public CodeGenOptionsBase { /// the expense of debuggability). SanitizerSet SanitizeMergeHandlers; + /// Set of thresholds in a range [0.0, 1.0]: the top hottest code responsible + /// for the given fraction of PGO counters will be excluded from sanitization + /// (0.0 [default] to skip none, 1.0 to skip all). + SanitizerMaskCutoffs SanitizeSkipHotCutoffs; + /// List of backend command-line options for -fembed-bitcode. std::vector<uint8_t> CmdArgs; diff --git a/clang/include/clang/Basic/Sanitizers.h b/clang/include/clang/Basic/Sanitizers.h index c890242269b334..2ff1acb7720949 100644 --- a/clang/include/clang/Basic/Sanitizers.h +++ b/clang/include/clang/Basic/Sanitizers.h @@ -154,6 +154,16 @@ struct SanitizerKind { #include "clang/Basic/Sanitizers.def" }; // SanitizerKind +class SanitizerMaskCutoffs { + std::vector<double> Cutoffs; + +public: + std::optional<double> operator[](unsigned Kind) const; + + void set(SanitizerMask K, double V); + void clear(SanitizerMask K = SanitizerKind::All); +}; + struct SanitizerSet { /// Check if a certain (single) sanitizer is enabled. bool has(SanitizerMask K) const { @@ -186,10 +196,24 @@ struct SanitizerSet { /// Returns a non-zero SanitizerMask, or \c 0 if \p Value is not known. SanitizerMask parseSanitizerValue(StringRef Value, bool AllowGroups); +/// Parse a single weighted value (e.g., 'undefined=0.05') from a -fsanitize= or +/// -fno-sanitize= value list. +/// The relevant weight(s) are updated in the passed Cutoffs parameter. +/// Individual Cutoffs are never reset to zero unless explicitly set +/// (e.g., 'null=0.0'). +/// Returns \c false if \p Value is not known or the weight is not valid. +bool parseSanitizerWeightedValue(StringRef Value, bool AllowGroups, + SanitizerMaskCutoffs &Cutoffs); + /// Serialize a SanitizerSet into values for -fsanitize= or -fno-sanitize=. void serializeSanitizerSet(SanitizerSet Set, SmallVectorImpl<StringRef> &Values); +/// Serialize a SanitizerMaskCutoffs into values for -fsanitize= or +/// -fno-sanitize=. +void serializeSanitizerMaskCutoffs(const SanitizerMaskCutoffs &Cutoffs, + SmallVectorImpl<std::string> &Values); + /// For each sanitizer group bit set in \p Kinds, set the bits for sanitizers /// this group enables. SanitizerMask expandSanitizerGroups(SanitizerMask Kinds); diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index eb860f73121fd7..41a7e8c3728066 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2652,6 +2652,14 @@ def fsanitize_undefined_strip_path_components_EQ : Joined<["-"], "fsanitize-unde HelpText<"Strip (or keep only, if negative) a given number of path components " "when emitting check metadata.">, MarshallingInfoInt<CodeGenOpts<"EmitCheckPathComponentsToStrip">, "0", "int">; +def fsanitize_skip_hot_cutoff_EQ + : CommaJoined<["-"], "fsanitize-skip-hot-cutoff=">, + Group<f_clang_Group>, + HelpText< + "Exclude sanitization for the top hottest code responsible for " + "the given fraction of PGO counters " + "(0.0 [default] = skip none; 1.0 = skip all). " + "Argument format: <sanitizer1>=<value1>,<sanitizer2>=<value2>,...">; } // end -f[no-]sanitize* flags diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index 3b275092bbbe86..a54995e2b153b2 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -26,6 +26,7 @@ class SanitizerArgs { SanitizerSet RecoverableSanitizers; SanitizerSet TrapSanitizers; SanitizerSet MergeHandlers; + SanitizerMaskCutoffs SkipHotCutoffs; std::vector<std::string> UserIgnorelistFiles; std::vector<std::string> SystemIgnorelistFiles; diff --git a/clang/lib/Basic/Sanitizers.cpp b/clang/lib/Basic/Sanitizers.cpp index 62ccdf8e9bbf28..5b9b88d032702f 100644 --- a/clang/lib/Basic/Sanitizers.cpp +++ b/clang/lib/Basic/Sanitizers.cpp @@ -14,10 +14,35 @@ #include "llvm/ADT/Hashing.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/Format.h" #include "llvm/Support/MathExtras.h" +#include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <optional> using namespace clang; +static const double SanitizerMaskCutoffsEps = 0.000000001f; + +void SanitizerMaskCutoffs::set(SanitizerMask K, double V) { + if (V < SanitizerMaskCutoffsEps && Cutoffs.empty()) + return; + for (unsigned int i = 0; i < SanitizerKind::SO_Count; ++i) + if (K & SanitizerMask::bitPosToMask(i)) { + Cutoffs.resize(SanitizerKind::SO_Count); + Cutoffs[i] = V; + } +} + +std::optional<double> SanitizerMaskCutoffs::operator[](unsigned Kind) const { + if (Cutoffs.empty() || Cutoffs[Kind] < SanitizerMaskCutoffsEps) + return std::nullopt; + + return Cutoffs[Kind]; +} + +void SanitizerMaskCutoffs::clear(SanitizerMask K) { set(K, 0); } + // Once LLVM switches to C++17, the constexpr variables can be inline and we // won't need this. #define SANITIZER(NAME, ID) constexpr SanitizerMask SanitizerKind::ID; @@ -36,6 +61,29 @@ SanitizerMask clang::parseSanitizerValue(StringRef Value, bool AllowGroups) { return ParsedKind; } +bool clang::parseSanitizerWeightedValue(StringRef Value, bool AllowGroups, + SanitizerMaskCutoffs &Cutoffs) { + SanitizerMask ParsedKind = llvm::StringSwitch<SanitizerMask>(Value) +#define SANITIZER(NAME, ID) .StartsWith(NAME "=", SanitizerKind::ID) +#define SANITIZER_GROUP(NAME, ID, ALIAS) \ + .StartsWith(NAME "=", \ + AllowGroups ? SanitizerKind::ID##Group : SanitizerMask()) +#include "clang/Basic/Sanitizers.def" + .Default(SanitizerMask()); + + if (!ParsedKind) + return false; + auto [N, W] = Value.split('='); + double A; + if (W.getAsDouble(A)) + return false; + A = std::clamp(A, 0.0, 1.0); + // AllowGroups is already taken into account for ParsedKind, + // hence we unconditionally expandSanitizerGroups. + Cutoffs.set(expandSanitizerGroups(ParsedKind), A); + return true; +} + void clang::serializeSanitizerSet(SanitizerSet Set, SmallVectorImpl<StringRef> &Values) { #define SANITIZER(NAME, ID) \ @@ -44,6 +92,18 @@ void clang::serializeSanitizerSet(SanitizerSet Set, #include "clang/Basic/Sanitizers.def" } +void clang::serializeSanitizerMaskCutoffs( + const SanitizerMaskCutoffs &Cutoffs, SmallVectorImpl<std::string> &Values) { +#define SANITIZER(NAME, ID) \ + if (auto C = Cutoffs[SanitizerKind::SO_##ID]) { \ + std::string Str; \ + llvm::raw_string_ostream OS(Str); \ + OS << NAME "=" << llvm::format("%.8f", *C); \ + Values.emplace_back(StringRef(Str).rtrim('0')); \ + } +#include "clang/Basic/Sanitizers.def" +} + SanitizerMask clang::expandSanitizerGroups(SanitizerMask Kinds) { #define SANITIZER(NAME, ID) #define SANITIZER_GROUP(NAME, ID, ALIAS) \ diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index 98116e2c8336b8..a0d6919c6dc8d0 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -10,6 +10,7 @@ #include "clang/Driver/Driver.h" #include "clang/Driver/Options.h" #include "clang/Driver/ToolChain.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Path.h" @@ -113,6 +114,12 @@ enum BinaryMetadataFeature { static SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A, bool DiagnoseErrors); +/// Parse a -fsanitize=<sanitizer1>=<value1>... or -fno-sanitize= argument's +/// values, diagnosing any invalid components. +/// Cutoffs are stored in the passed parameter. +static void parseArgCutoffs(const Driver &D, const llvm::opt::Arg *A, + bool DiagnoseErrors, SanitizerMaskCutoffs &Cutoffs); + /// Parse -f(no-)?sanitize-coverage= flag values, diagnosing any invalid /// components. Returns OR of members of \c CoverageFeature enumeration. static int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A, @@ -323,6 +330,19 @@ static SanitizerMask parseSanitizeTrapArgs(const Driver &D, options::OPT_fno_sanitize_trap_EQ); } +static SanitizerMaskCutoffs +parseSanitizeSkipHotCutoffArgs(const Driver &D, const llvm::opt::ArgList &Args, + bool DiagnoseErrors) { + SanitizerMaskCutoffs Cutoffs; + for (const auto *Arg : Args) + if (Arg->getOption().matches(options::OPT_fsanitize_skip_hot_cutoff_EQ)) { + Arg->claim(); + parseArgCutoffs(D, Arg, DiagnoseErrors, Cutoffs); + } + + return Cutoffs; +} + bool SanitizerArgs::needsFuzzerInterceptors() const { return needsFuzzer() && !needsAsanRt() && !needsTsanRt() && !needsMsanRt(); } @@ -713,6 +733,9 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, options::OPT_fno_sanitize_merge_handlers_EQ); MergeKinds &= Kinds; + // Parse -fno-sanitize-top-hot flags + SkipHotCutoffs = parseSanitizeSkipHotCutoffArgs(D, Args, DiagnoseErrors); + // Setup ignorelist files. // Add default ignorelist from resource directory for activated sanitizers, // and validate special case lists format. @@ -1132,6 +1155,9 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, "Overlap between recoverable and trapping sanitizers"); MergeHandlers.Mask |= MergeKinds; + + // Zero out SkipHotCutoffs for unused sanitizers + SkipHotCutoffs.clear(~Sanitizers.Mask); } static std::string toString(const clang::SanitizerSet &Sanitizers) { @@ -1146,6 +1172,12 @@ static std::string toString(const clang::SanitizerSet &Sanitizers) { return Res; } +static std::string toString(const clang::SanitizerMaskCutoffs &Cutoffs) { + llvm::SmallVector<std::string, 4> Res; + serializeSanitizerMaskCutoffs(Cutoffs, Res); + return llvm::join(Res, ","); +} + static void addSpecialCaseListOpt(const llvm::opt::ArgList &Args, llvm::opt::ArgStringList &CmdArgs, const char *SCLOptFlag, @@ -1297,6 +1329,11 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, CmdArgs.push_back( Args.MakeArgString("-fsanitize-merge=" + toString(MergeHandlers))); + std::string SkipHotCutoffsStr = toString(SkipHotCutoffs); + if (!SkipHotCutoffsStr.empty()) + CmdArgs.push_back( + Args.MakeArgString("-fsanitize-skip-hot-cutoff=" + SkipHotCutoffsStr)); + addSpecialCaseListOpt(Args, CmdArgs, "-fsanitize-ignorelist=", UserIgnorelistFiles); addSpecialCaseListOpt(Args, CmdArgs, @@ -1494,6 +1531,22 @@ SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A, return Kinds; } +void parseArgCutoffs(const Driver &D, const llvm::opt::Arg *A, + bool DiagnoseErrors, SanitizerMaskCutoffs &Cutoffs) { + assert(A->getOption().matches(options::OPT_fsanitize_skip_hot_cutoff_EQ) && + "Invalid argument in parseArgCutoffs!"); + for (int i = 0, n = A->getNumValues(); i != n; ++i) { + const char *Value = A->getValue(i); + + // We don't check the value of Cutoffs[i]: it's legal to specify + // a cutoff of 0. + if (!parseSanitizerWeightedValue(Value, /*AllowGroups=*/true, Cutoffs) && + DiagnoseErrors) + D.Diag(clang::diag::err_drv_unsupported_option_argument) + << A->getSpelling() << Value; + } +} + static int parseOverflowPatternExclusionValues(const Driver &D, const llvm::opt::Arg *A, bool DiagnoseErrors) { diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index d711df02ce9503..39bed84536c6a3 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1453,6 +1453,18 @@ static SmallVector<StringRef, 4> serializeSanitizerKinds(SanitizerSet S) { return Values; } +static SanitizerMaskCutoffs +parseSanitizerWeightedKinds(StringRef FlagName, + const std::vector<std::string> &Sanitizers, + DiagnosticsEngine &Diags) { + SanitizerMaskCutoffs Cutoffs; + for (const auto &Sanitizer : Sanitizers) { + if (!parseSanitizerWeightedValue(Sanitizer, /*AllowGroups=*/false, Cutoffs)) + Diags.Report(diag::err_drv_invalid_value) << FlagName << Sanitizer; + } + return Cutoffs; +} + static void parseXRayInstrumentationBundle(StringRef FlagName, StringRef Bundle, ArgList &Args, DiagnosticsEngine &D, XRayInstrSet &S) { @@ -1813,6 +1825,11 @@ void CompilerInvocationBase::GenerateCodeGenArgs(const CodeGenOptions &Opts, serializeSanitizerKinds(Opts.SanitizeMergeHandlers)) GenerateArg(Consumer, OPT_fsanitize_merge_handlers_EQ, Sanitizer); + SmallVector<std::string, 4> Values; + serializeSanitizerMaskCutoffs(Opts.SanitizeSkipHotCutoffs, Values); + for (std::string Sanitizer : Values) + GenerateArg(Consumer, OPT_fsanitize_skip_hot_cutoff_EQ, Sanitizer); + if (!Opts.EmitVersionIdentMetadata) GenerateArg(Consumer, OPT_Qn); @@ -2293,6 +2310,11 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, Args.getAllArgValues(OPT_fsanitize_merge_handlers_EQ), Diags, Opts.SanitizeMergeHandlers); + // Parse -fsanitize-skip-hot-cutoff= arguments. + Opts.SanitizeSkipHotCutoffs = parseSanitizerWeightedKinds( + "-fsanitize-skip-hot-cutoff=", + Args.getAllArgValues(OPT_fsanitize_skip_hot_cutoff_EQ), Diags); + Opts.EmitVersionIdentMetadata = Args.hasFlag(OPT_Qy, OPT_Qn, true); if (!LangOpts->CUDAIsDevice) diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c index aeae15aada70cc..1d3caec748d77a 100644 --- a/clang/test/Driver/fsanitize.c +++ b/clang/test/Driver/fsanitize.c @@ -1154,3 +1154,56 @@ // RUN: not %clang --target=x86_64-linux-gnu -fsanitize=realtime,undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-REALTIME-UBSAN // CHECK-REALTIME-UBSAN: error: invalid argument '-fsanitize=realtime' not allowed with '-fsanitize=undefined' + + +// * Test -fsanitize-skip-hot-cutoff * + +// -fsanitize-skip-hot-cutoff=undefined=0.5 +// RUN: %clang -Werror --target=x86_64-linux-gnu -fsanitize=undefined -fsanitize-skip-hot-cutoff=undefined=0.5 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF1 +// CHECK-SKIP-HOT-CUTOFF1: "-fsanitize-skip-hot-cutoff={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function|vptr)=0.5(0*),?){19}"}} + +// No-op: no sanitizers are specified +// RUN: %clang -Werror --target=x86_64-linux-gnu -fsanitize-skip-hot-cutoff=undefined=0.5 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF2 +// CHECK-SKIP-HOT-CUTOFF2-NOT: "-fsanitize" +// CHECK-SKIP-HOT-CUTOFF2-NOT: "-fsanitize-skip-hot-cutoff" + +// Enable undefined, then cancel out integer using a cutoff of 0.0 +// RUN: %clang -Werror --target=x86_64-linux-gnu -fsanitize=undefined -fsanitize-skip-hot-cutoff=undefined=0.5,integer=0.0 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF3 +// CHECK-SKIP-HOT-CUTOFF3: "-fsanitize-skip-hot-cutoff={{((unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function|vptr)=0.5(0*),?){15}"}} + +// Enable undefined, then cancel out integer using a cutoff of 0.0, then re-enable signed-integer-overflow +// RUN: %clang -Werror --target=x86_64-linux-gnu -fsanitize=undefined -fsanitize-skip-hot-cutoff=undefined=0.5,integer=0.0,signed-integer-overflow=0.7 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF4 +// CHECK-SKIP-HOT-CUTOFF4: "-fsanitize-skip-hot-cutoff={{((signed-integer-overflow|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function|vptr)=0.[57]0*,?){16}"}} + +// Check that -fsanitize-skip-hot-cutoff=undefined=0.4 does not widen the set of -fsanitize=integer checks. +// RUN: %clang -Werror --target=x86_64-linux-gnu -fsanitize=integer -fsanitize-skip-hot-cutoff=undefined=0.4 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF5 +// CHECK-SKIP-HOT-CUTOFF5: "-fsanitize-skip-hot-cutoff={{((integer-divide-by-zero|shift-base|shift-exponent|signed-integer-overflow)=0.40*,?){4}"}} + +// No-op: it's allowed for the user to specify a cutoff of 0.0, though the argument is not passed along by the driver. +// RUN: %clang -Werror --target=x86_64-linux-gnu -fsanitize=undefined -fsanitize-skip-hot-cutoff=undefined=0.0 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF6 +// CHECK-SKIP-HOT-CUTOFF6: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function|vptr),?){19}"}} +// CHECK-SKIP-HOT-CUTOFF6-NOT: "-fsanitize-skip-hot-cutoff" + +// Invalid: bad sanitizer +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize-skip-hot-cutoff=pot=0.0 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF7 +// CHECK-SKIP-HOT-CUTOFF7: unsupported argument 'pot=0.0' to option '-fsanitize-skip-hot-cutoff=' + +// Invalid: bad cutoff +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize-skip-hot-cutoff=undefined=xyzzy %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF8 +// CHECK-SKIP-HOT-CUTOFF8: unsupported argument 'undefined=xyzzy' to option '-fsanitize-skip-hot-cutoff=' + +// Invalid: -fno-sanitize-top without parameters +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize-skip-hot-cutoff %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF9 +// CHECK-SKIP-HOT-CUTOFF9: unknown argument: '-fsanitize-skip-hot-cutoff' + +// Invalid: -fno-sanitize-top=undefined without cutoff +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize-skip-hot-cutoff=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF10 +// CHECK-SKIP-HOT-CUTOFF10: unsupported argument 'undefined' to option '-fsanitize-skip-hot-cutoff=' + +// Invalid: -fno-sanitize-top=undefined= without cutoff +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize-skip-hot-cutoff=undefined= %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF11 +// CHECK-SKIP-HOT-CUTOFF11: unsupported argument 'undefined=' to option '-fsanitize-skip-hot-cutoff=' + +// No-op: -fno-sanitize-top= without parameters is unusual but valid +// RUN: %clang -Werror --target=x86_64-linux-gnu -fsanitize-skip-hot-cutoff= %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SKIP-HOT-CUTOFF12 +// CHECK-SKIP-HOT-CUTOFF12-NOT: "-fsanitize-skip-hot-cutoff" _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits