From b8ef8e90281b9e0d159a30653bb139374d4abc52 Mon Sep 17 00:00:00 2001 From: Alexey Samsonov Date: Thu, 7 May 2015 22:34:06 +0000 Subject: [PATCH] [SanitizerCoverage] Implement user-friendly -fsanitize-coverage= flags. Summary: Possible coverage levels are: * -fsanitize-coverage=func - function-level coverage * -fsanitize-coverage=bb - basic-block-level coverage * -fsanitize-coverage=edge - edge-level coverage Extra features are: * -fsanitize-coverage=indirect-calls - coverage for indirect calls * -fsanitize-coverage=trace-bb - tracing for basic blocks * -fsanitize-coverage=trace-cmp - tracing for cmp instructions * -fsanitize-coverage=8bit-counters - frequency counters Levels and features can be combined in comma-separated list, and can be disabled by subsequent -fno-sanitize-coverage= flags, e.g.: -fsanitize-coverage=bb,trace-bb,8bit-counters -fno-sanitize-coverage=trace-bb is equivalient to: -fsanitize-coverage=bb,8bit-counters Original semantics of -fsanitize-coverage flag is preserved: * -fsanitize-coverage=0 disables the coverage * -fsanitize-coverage=1 is a synonym for -fsanitize-coverage=func * -fsanitize-coverage=2 is a synonym for -fsanitize-coverage=bb * -fsanitize-coverage=3 is a synonym for -fsanitize-coverage=edge * -fsanitize-coverage=4 is a synonym for -fsanitize-coverage=edge,indirect-calls Driver tries to diagnose invalid flag usage, in particular: * At most one level (func,bb,edge) must be specified. * "trace-bb" and "8bit-counters" features require some level to be specified. See test case for more examples. Test Plan: regression test suite Reviewers: kcc Subscribers: cfe-commits Differential Revision: http://reviews.llvm.org/D9577 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@236790 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Driver/Options.td | 7 +- include/clang/Driver/SanitizerArgs.h | 2 +- lib/CodeGen/BackendUtil.cpp | 4 +- lib/Driver/SanitizerArgs.cpp | 128 +++++++++++++++++++++++---- test/Driver/fsanitize-coverage.c | 45 +++++++++- 5 files changed, 166 insertions(+), 20 deletions(-) diff --git a/include/clang/Driver/Options.td b/include/clang/Driver/Options.td index 38368654d4..a78110e662 100644 --- a/include/clang/Driver/Options.td +++ b/include/clang/Driver/Options.td @@ -530,9 +530,14 @@ def fno_sanitize_blacklist : Flag<["-"], "fno-sanitize-blacklist">, Group, HelpText<"Don't use blacklist file for sanitizers">; def fsanitize_coverage - : Joined<["-"], "fsanitize-coverage=">, + : CommaJoined<["-"], "fsanitize-coverage=">, Group, Flags<[CoreOption]>, HelpText<"Specify the type of coverage instrumentation for Sanitizers">; +def fno_sanitize_coverage + : CommaJoined<["-"], "fno-sanitize-coverage=">, + Group, Flags<[CoreOption]>, + HelpText<"Disable specified features of coverage instrumentation for " + "Sanitizers">; def fsanitize_memory_track_origins_EQ : Joined<["-"], "fsanitize-memory-track-origins=">, Group, Flags<[CC1Option]>, HelpText<"Enable origins tracking in MemorySanitizer">; diff --git a/include/clang/Driver/SanitizerArgs.h b/include/clang/Driver/SanitizerArgs.h index 04e2821195..bfa63e7734 100644 --- a/include/clang/Driver/SanitizerArgs.h +++ b/include/clang/Driver/SanitizerArgs.h @@ -25,7 +25,7 @@ class SanitizerArgs { SanitizerSet RecoverableSanitizers; std::vector BlacklistFiles; - int SanitizeCoverage; + int CoverageFeatures; int MsanTrackOrigins; int AsanFieldPadding; bool AsanZeroBaseShadow; diff --git a/lib/CodeGen/BackendUtil.cpp b/lib/CodeGen/BackendUtil.cpp index 0206ad5838..20fd9af169 100644 --- a/lib/CodeGen/BackendUtil.cpp +++ b/lib/CodeGen/BackendUtil.cpp @@ -313,7 +313,9 @@ void EmitAssemblyHelper::CreatePasses() { addBoundsCheckingPass); } - if (CodeGenOpts.SanitizeCoverageType) { + if (CodeGenOpts.SanitizeCoverageType || + CodeGenOpts.SanitizeCoverageIndirectCalls || + CodeGenOpts.SanitizeCoverageTraceCmp) { PMBuilder.addExtension(PassManagerBuilder::EP_OptimizerLast, addSanitizerCoveragePass); PMBuilder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0, diff --git a/lib/Driver/SanitizerArgs.cpp b/lib/Driver/SanitizerArgs.cpp index 528b7243c5..94379ba629 100644 --- a/lib/Driver/SanitizerArgs.cpp +++ b/lib/Driver/SanitizerArgs.cpp @@ -50,6 +50,16 @@ enum SanitizeKind : uint64_t { LegacyFsanitizeRecoverMask = Undefined | Integer, NeedsLTO = CFI, }; + +enum CoverageFeature { + CoverageFunc = 1 << 0, + CoverageBB = 1 << 1, + CoverageEdge = 1 << 2, + CoverageIndirCall = 1 << 3, + CoverageTraceBB = 1 << 4, + CoverageTraceCmp = 1 << 5, + Coverage8bitCounters = 1 << 6, +}; } /// Returns true if set of \p Sanitizers contain at least one sanitizer from @@ -88,6 +98,10 @@ static uint64_t parseValue(const char *Value); static uint64_t parseArgValues(const Driver &D, const llvm::opt::Arg *A, bool DiagnoseErrors); +/// 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); + /// 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 @@ -181,7 +195,7 @@ void SanitizerArgs::clear() { Sanitizers.clear(); RecoverableSanitizers.clear(); BlacklistFiles.clear(); - SanitizeCoverage = 0; + CoverageFeatures = 0; MsanTrackOrigins = 0; AsanFieldPadding = 0; AsanZeroBaseShadow = false; @@ -393,16 +407,70 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, } } - // Parse -fsanitize-coverage=N. Currently one of asan/msan/lsan is required. + // Parse -f(no-)?sanitize-coverage flags if coverage is supported by the + // enabled sanitizers. if (Kinds & SanitizeKind::SupportsCoverage) { - if (Arg *A = Args.getLastArg(options::OPT_fsanitize_coverage)) { - StringRef S = A->getValue(); - // Legal values are 0..4. - if (S.getAsInteger(0, SanitizeCoverage) || SanitizeCoverage < 0 || - SanitizeCoverage > 4) - D.Diag(clang::diag::err_drv_invalid_value) << A->getAsString(Args) << S; + for (const auto *Arg : Args) { + if (Arg->getOption().matches(options::OPT_fsanitize_coverage)) { + Arg->claim(); + int LegacySanitizeCoverage; + if (Arg->getNumValues() == 1 && + !StringRef(Arg->getValue(0)) + .getAsInteger(0, LegacySanitizeCoverage) && + LegacySanitizeCoverage >= 0 && LegacySanitizeCoverage <= 4) { + // TODO: Add deprecation notice for this form. + switch (LegacySanitizeCoverage) { + case 0: + CoverageFeatures = 0; + break; + case 1: + CoverageFeatures = CoverageFunc; + break; + case 2: + CoverageFeatures = CoverageBB; + break; + case 3: + CoverageFeatures = CoverageEdge; + break; + case 4: + CoverageFeatures = CoverageEdge | CoverageIndirCall; + break; + } + continue; + } + CoverageFeatures |= parseCoverageFeatures(D, Arg); + } else if (Arg->getOption().matches(options::OPT_fno_sanitize_coverage)) { + Arg->claim(); + CoverageFeatures &= ~parseCoverageFeatures(D, Arg); + } } } + // Choose at most one coverage type: function, bb, or edge. + if ((CoverageFeatures & CoverageFunc) && (CoverageFeatures & CoverageBB)) + D.Diag(clang::diag::err_drv_argument_not_allowed_with) + << "-fsanitize-coverage=func" + << "-fsanitize-coverage=bb"; + if ((CoverageFeatures & CoverageFunc) && (CoverageFeatures & CoverageEdge)) + D.Diag(clang::diag::err_drv_argument_not_allowed_with) + << "-fsanitize-coverage=func" + << "-fsanitize-coverage=edge"; + if ((CoverageFeatures & CoverageBB) && (CoverageFeatures & CoverageEdge)) + D.Diag(clang::diag::err_drv_argument_not_allowed_with) + << "-fsanitize-coverage=bb" + << "-fsanitize-coverage=edge"; + // Basic block tracing and 8-bit counters require some type of coverage + // enabled. + int CoverageTypes = CoverageFunc | CoverageBB | CoverageEdge; + if ((CoverageFeatures & CoverageTraceBB) && + !(CoverageFeatures & CoverageTypes)) + D.Diag(clang::diag::err_drv_argument_only_allowed_with) + << "-fsanitize-coverage=trace-bb" + << "-fsanitize-coverage=(func|bb|edge)"; + if ((CoverageFeatures & Coverage8bitCounters) && + !(CoverageFeatures & CoverageTypes)) + D.Diag(clang::diag::err_drv_argument_only_allowed_with) + << "-fsanitize-coverage=8bit-counters" + << "-fsanitize-coverage=(func|bb|edge)"; if (Kinds & SanitizeKind::Address) { AsanSharedRuntime = @@ -482,14 +550,21 @@ void SanitizerArgs::addArgs(const llvm::opt::ArgList &Args, if (AsanFieldPadding) CmdArgs.push_back(Args.MakeArgString("-fsanitize-address-field-padding=" + llvm::utostr(AsanFieldPadding))); - if (SanitizeCoverage) { - int CoverageType = std::min(SanitizeCoverage, 3); - CmdArgs.push_back(Args.MakeArgString("-fsanitize-coverage-type=" + - llvm::utostr(CoverageType))); - if (SanitizeCoverage == 4) - CmdArgs.push_back( - Args.MakeArgString("-fsanitize-coverage-indirect-calls")); + // Translate available CoverageFeatures to corresponding clang-cc1 flags. + std::pair CoverageFlags[] = { + std::make_pair(CoverageFunc, "-fsanitize-coverage-type=1"), + std::make_pair(CoverageBB, "-fsanitize-coverage-type=2"), + std::make_pair(CoverageEdge, "-fsanitize-coverage-type=3"), + std::make_pair(CoverageIndirCall, "-fsanitize-coverage-indirect-calls"), + std::make_pair(CoverageTraceBB, "-fsanitize-coverage-trace-bb"), + std::make_pair(CoverageTraceCmp, "-fsanitize-coverage-trace-cmp"), + std::make_pair(Coverage8bitCounters, "-fsanitize-coverage-8bit-counters")}; + for (auto F : CoverageFlags) { + if (CoverageFeatures & F.first) + CmdArgs.push_back(Args.MakeArgString(F.second)); } + + // MSan: Workaround for PR16386. // ASan: This is mainly to help LSan with cases such as // https://code.google.com/p/address-sanitizer/issues/detail?id=373 @@ -543,6 +618,29 @@ uint64_t parseArgValues(const Driver &D, const llvm::opt::Arg *A, return Kinds; } +int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A) { + assert(A->getOption().matches(options::OPT_fsanitize_coverage) || + A->getOption().matches(options::OPT_fno_sanitize_coverage)); + int Features = 0; + for (int i = 0, n = A->getNumValues(); i != n; ++i) { + const char *Value = A->getValue(i); + int F = llvm::StringSwitch(Value) + .Case("func", CoverageFunc) + .Case("bb", CoverageBB) + .Case("edge", CoverageEdge) + .Case("indirect-calls", CoverageIndirCall) + .Case("trace-bb", CoverageTraceBB) + .Case("trace-cmp", CoverageTraceCmp) + .Case("8bit-counters", Coverage8bitCounters) + .Default(0); + if (F == 0) + D.Diag(clang::diag::err_drv_unsupported_option_argument) + << A->getOption().getName() << Value; + Features |= F; + } + return Features; +} + std::string lastArgumentForMask(const Driver &D, const llvm::opt::ArgList &Args, uint64_t Mask) { for (llvm::opt::ArgList::const_reverse_iterator I = Args.rbegin(), diff --git a/test/Driver/fsanitize-coverage.c b/test/Driver/fsanitize-coverage.c index 5e71901024..db5abf549f 100644 --- a/test/Driver/fsanitize-coverage.c +++ b/test/Driver/fsanitize-coverage.c @@ -1,6 +1,8 @@ // RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=0 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-0 -// RUN: %clang -target x86_64-linux-gnu -fsanitize=address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-0 +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=edge -fsanitize-coverage=0 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-0 +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-0 // CHECK-SANITIZE-COVERAGE-0-NOT: fsanitize-coverage-type + // RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=1 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-1 // RUN: %clang -target x86_64-linux-gnu -fsanitize=memory -fsanitize-coverage=1 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-1 // RUN: %clang -target x86_64-linux-gnu -fsanitize=leak -fsanitize-coverage=1 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-1 @@ -8,15 +10,54 @@ // RUN: %clang -target x86_64-linux-gnu -fsanitize=bool -fsanitize-coverage=1 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-1 // RUN: %clang -target x86_64-linux-gnu -fsanitize=dataflow -fsanitize-coverage=1 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-1 // CHECK-SANITIZE-COVERAGE-1: fsanitize-coverage-type=1 + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=2 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-2 +// CHECK-SANITIZE-COVERAGE-2: fsanitize-coverage-type=2 + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=3 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-3 +// CHECK-SANITIZE-COVERAGE-3: fsanitize-coverage-type=3 + // RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=4 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-4 // CHECK-SANITIZE-COVERAGE-4: fsanitize-coverage-type=3 // CHECK-SANITIZE-COVERAGE-4: fsanitize-coverage-indirect-calls + // RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=5 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-5 -// CHECK-SANITIZE-COVERAGE-5: error: invalid value '5' in '-fsanitize-coverage=5' +// CHECK-SANITIZE-COVERAGE-5: error: unsupported argument '5' to option 'fsanitize-coverage=' + // RUN: %clang -target x86_64-linux-gnu -fsanitize=thread -fsanitize-coverage=1 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-UNUSED // RUN: %clang -target x86_64-linux-gnu -fsanitize-coverage=1 %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-UNUSED // CHECK-SANITIZE-COVERAGE-UNUSED: argument unused during compilation: '-fsanitize-coverage=1' +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=edge,indirect-calls,trace-bb,trace-cmp,8bit-counters %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANITIZE-COVERAGE-FEATURES +// CHECK-SANITIZE-COVERAGE-FEATURES: -fsanitize-coverage-type=3 +// CHECK-SANITIZE-COVERAGE-FEATURES: -fsanitize-coverage-indirect-calls +// CHECK-SANITIZE-COVERAGE-FEATURES: -fsanitize-coverage-trace-bb +// CHECK-SANITIZE-COVERAGE-FEATURES: -fsanitize-coverage-trace-cmp +// CHECK-SANITIZE-COVERAGE-FEATURES: -fsanitize-coverage-8bit-counters + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=func,edge,indirect-calls,trace-bb,trace-cmp -fno-sanitize-coverage=edge,indirect-calls,trace-bb %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-MASK +// CHECK-MASK: -fsanitize-coverage-type=1 +// CHECK-MASK: -fsanitize-coverage-trace-cmp +// CHECK-MASK-NOT: -fsanitize-coverage- + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=foobar %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID-VALUE +// CHECK-INVALID-VALUE: error: unsupported argument 'foobar' to option 'fsanitize-coverage=' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=func -fsanitize-coverage=edge %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INCOMPATIBLE +// CHECK-INCOMPATIBLE: error: invalid argument '-fsanitize-coverage=func' not allowed with '-fsanitize-coverage=edge' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=8bit-counters %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-MISSING-TYPE +// CHECK-MISSING-TYPE: error: invalid argument '-fsanitize-coverage=8bit-counters' only allowed with '-fsanitize-coverage=(func|bb|edge)' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=trace-cmp,indirect-calls %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-TYPE-NECESSARY +// CHECK-NO-TYPE-NECESSARY-NOT: error: +// CHECK-NO-TYPE-NECESSARY: -fsanitize-coverage-indirect-calls +// CHECK-NO-TYPE-NECESSARY: -fsanitize-coverage-trace-cmp + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-coverage=1 -fsanitize-coverage=trace-cmp %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-EXTEND-LEGACY +// CHECK-EXTEND-LEGACY: -fsanitize-coverage-type=1 +// CHECK-EXTEND-LEGACY: -fsanitize-coverage-trace-cmp + // RUN: %clang_cl -fsanitize=address -fsanitize-coverage=1 -c -### -- %s 2>&1 | FileCheck %s -check-prefix=CLANG-CL-COVERAGE // CLANG-CL-COVERAGE-NOT: error: // CLANG-CL-COVERAGE-NOT: warning: -- 2.40.0