]> granicus.if.org Git - clang/commitdiff
[analyzer] Analysis: Silence checkers
authorCsaba Dabis <dabis.csaba98@gmail.com>
Fri, 16 Aug 2019 01:53:14 +0000 (01:53 +0000)
committerCsaba Dabis <dabis.csaba98@gmail.com>
Fri, 16 Aug 2019 01:53:14 +0000 (01:53 +0000)
Summary:
This patch introduces a new `analyzer-config` configuration:
`-analyzer-config silence-checkers`
which could be used to silence the given checkers.

It accepts a semicolon separated list, packed into quotation marks, e.g:
`-analyzer-config silence-checkers="core.DivideZero;core.NullDereference"`

It could be used to "disable" core checkers, so they model the analysis as
before, just if some of them are too noisy it prevents to emit reports.

This patch also adds support for that new option to the scan-build.
Passing the option `-disable-checker core.DivideZero` to the scan-build
will be transferred to `-analyzer-config silence-checkers=core.DivideZero`.

Reviewed By: NoQ, Szelethus

Differential Revision: https://reviews.llvm.org/D66042

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@369078 91177308-0d34-0410-b5e6-96231b3b80d8

12 files changed:
include/clang/Basic/DiagnosticCommonKinds.td
include/clang/StaticAnalyzer/Core/AnalyzerOptions.def
include/clang/StaticAnalyzer/Core/AnalyzerOptions.h
lib/Frontend/CompilerInvocation.cpp
lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
lib/StaticAnalyzer/Core/BugReporter.cpp
lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp
test/Analysis/analyzer-config.c
test/Analysis/silence-checkers-and-packages-core-all.cpp [new file with mode: 0644]
test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp [new file with mode: 0644]
tools/scan-build/bin/scan-build
unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp

index ca2faf59d70f9739a7cd2640a0d06e26501afb08..e5ad50fd0f1c6a393d871f8656146c8c53ec0342 100644 (file)
@@ -300,7 +300,7 @@ def err_omp_more_one_clause : Error<
   "directive '#pragma omp %0' cannot contain more than one '%1' clause%select{| with '%3' name modifier| with 'source' dependence}2">;
 
 // Static Analyzer Core
-def err_unknown_analyzer_checker : Error<
+def err_unknown_analyzer_checker_or_package : Error<
     "no analyzer checkers or packages are associated with '%0'">;
 def note_suggest_disabling_all_checkers : Note<
     "use -analyzer-disable-all-checks to disable all static analyzer checkers">;
index 70bd476b6c438e632dee0a06e75091315678b6fd..95dc3524ce3904ec222de17d9deed43a60b7042a 100644 (file)
@@ -380,12 +380,6 @@ ANALYZER_OPTION(
     "Value: \"constructors\", \"destructors\", \"methods\".",
     "destructors")
 
-ANALYZER_OPTION_DEPENDS_ON_USER_MODE(
-    StringRef, IPAMode, "ipa",
-    "Controls the mode of inter-procedural analysis. Value: \"none\", "
-    "\"basic-inlining\", \"inlining\", \"dynamic\", \"dynamic-bifurcate\".",
-    /* SHALLOW_VAL */ "inlining", /* DEEP_VAL */ "dynamic-bifurcate")
-
 ANALYZER_OPTION(
     StringRef, ExplorationStrategy, "exploration_strategy",
     "Value: \"dfs\", \"bfs\", \"unexplored_first\", "
@@ -393,5 +387,17 @@ ANALYZER_OPTION(
     "\"bfs_block_dfs_contents\".",
     "unexplored_first_queue")
 
+ANALYZER_OPTION(
+    StringRef, RawSilencedCheckersAndPackages, "silence-checkers",
+    "A semicolon separated list of checker and package names to silence. "
+    "Silenced checkers will not emit reports, but the modeling remain enabled.",
+    "")
+
+ANALYZER_OPTION_DEPENDS_ON_USER_MODE(
+    StringRef, IPAMode, "ipa",
+    "Controls the mode of inter-procedural analysis. Value: \"none\", "
+    "\"basic-inlining\", \"inlining\", \"dynamic\", \"dynamic-bifurcate\".",
+    /* SHALLOW_VAL */ "inlining", /* DEEP_VAL */ "dynamic-bifurcate")
+
 #undef ANALYZER_OPTION_DEPENDS_ON_USER_MODE
 #undef ANALYZER_OPTION
index 9630a229bd3bf8cdf9afdbdf2df47d74c030f05a..609932ad70e21baab2b878fcf12016477d446622 100644 (file)
@@ -164,7 +164,40 @@ public:
   using ConfigTable = llvm::StringMap<std::string>;
 
   static std::vector<StringRef>
-  getRegisteredCheckers(bool IncludeExperimental = false);
+  getRegisteredCheckers(bool IncludeExperimental = false) {
+    static const StringRef StaticAnalyzerChecks[] = {
+#define GET_CHECKERS
+#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) FULLNAME,
+#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
+#undef CHECKER
+#undef GET_CHECKERS
+    };
+    std::vector<StringRef> Checkers;
+    for (StringRef CheckerName : StaticAnalyzerChecks) {
+      if (!CheckerName.startswith("debug.") &&
+          (IncludeExperimental || !CheckerName.startswith("alpha.")))
+        Checkers.push_back(CheckerName);
+    }
+    return Checkers;
+  }
+
+  static std::vector<StringRef>
+  getRegisteredPackages(bool IncludeExperimental = false) {
+    static const StringRef StaticAnalyzerPackages[] = {
+#define GET_PACKAGES
+#define PACKAGE(FULLNAME) FULLNAME,
+#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
+#undef PACKAGE
+#undef GET_PACKAGES
+    };
+    std::vector<StringRef> Packages;
+    for (StringRef PackageName : StaticAnalyzerPackages) {
+      if (PackageName != "debug" &&
+          (IncludeExperimental || PackageName != "alpha"))
+        Packages.push_back(PackageName);
+    }
+    return Packages;
+  }
 
   /// Convenience function for printing options or checkers and their
   /// description in a formatted manner. If \p MinLineWidth is set to 0, no line
@@ -188,9 +221,11 @@ public:
       std::pair<StringRef, StringRef> EntryDescPair,
       size_t EntryWidth, size_t InitialPad, size_t MinLineWidth = 0);
 
+  /// Pairs of checker/package name and enable/disable.
+  std::vector<std::pair<std::string, bool>> CheckersAndPackages;
 
-  /// Pair of checker name and enable/disable.
-  std::vector<std::pair<std::string, bool>> CheckersControlList;
+  /// Vector of checker/package names which will not emit warnings.
+  std::vector<std::string> SilencedCheckersAndPackages;
 
   /// A key-value table of use-specified configuration values.
   // TODO: This shouldn't be public.
index fb5f1cc5ac739ee2319a963da85a2c63c7831263..66684e5c961ddfcf80228c8c004c5dd912e17024 100644 (file)
@@ -324,18 +324,18 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args,
       getLastArgIntValue(Args, OPT_analyzer_inline_max_stack_depth,
                          Opts.InlineMaxStackDepth, Diags);
 
-  Opts.CheckersControlList.clear();
+  Opts.CheckersAndPackages.clear();
   for (const Arg *A :
        Args.filtered(OPT_analyzer_checker, OPT_analyzer_disable_checker)) {
     A->claim();
-    bool enable = (A->getOption().getID() == OPT_analyzer_checker);
+    bool IsEnabled = A->getOption().getID() == OPT_analyzer_checker;
     // We can have a list of comma separated checker names, e.g:
     // '-analyzer-checker=cocoa,unix'
-    StringRef checkerList = A->getValue();
-    SmallVector<StringRef, 4> checkers;
-    checkerList.split(checkers, ",");
-    for (auto checker : checkers)
-      Opts.CheckersControlList.emplace_back(checker, enable);
+    StringRef CheckerAndPackageList = A->getValue();
+    SmallVector<StringRef, 16> CheckersAndPackages;
+    CheckerAndPackageList.split(CheckersAndPackages, ",");
+    for (const StringRef CheckerOrPackage : CheckersAndPackages)
+      Opts.CheckersAndPackages.emplace_back(CheckerOrPackage, IsEnabled);
   }
 
   // Go through the analyzer configuration options.
@@ -479,6 +479,32 @@ static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts,
       !llvm::sys::fs::is_directory(AnOpts.ModelPath))
     Diags->Report(diag::err_analyzer_config_invalid_input) << "model-path"
                                                            << "a filename";
+
+  // FIXME: Here we try to validate the silenced checkers or packages are valid.
+  // The current approach only validates the registered checkers which does not
+  // contain the runtime enabled checkers and optimally we would validate both.
+  if (!AnOpts.RawSilencedCheckersAndPackages.empty()) {
+    std::vector<StringRef> Checkers =
+        AnOpts.getRegisteredCheckers(/*IncludeExperimental=*/true);
+    std::vector<StringRef> Packages =
+        AnOpts.getRegisteredPackages(/*IncludeExperimental=*/true);
+
+    SmallVector<StringRef, 16> CheckersAndPackages;
+    AnOpts.RawSilencedCheckersAndPackages.split(CheckersAndPackages, ";");
+
+    for (const StringRef CheckerOrPackage : CheckersAndPackages) {
+      bool IsChecker = CheckerOrPackage.contains('.');
+      bool IsValidName =
+          IsChecker ? llvm::find(Checkers, CheckerOrPackage) != Checkers.end()
+                    : llvm::find(Packages, CheckerOrPackage) != Packages.end();
+
+      if (!IsValidName)
+        Diags->Report(diag::err_unknown_analyzer_checker_or_package)
+            << CheckerOrPackage;
+
+      AnOpts.SilencedCheckersAndPackages.emplace_back(CheckerOrPackage);
+    }
+  }
 }
 
 static bool ParseMigratorArgs(MigratorOptions &Opts, ArgList &Args) {
index 71abe2ae6c0e85aae6b53e1bde1980d23365db81..01ac2bc83bb6b06427ed84d33e0dd99b34828a90 100644 (file)
@@ -30,25 +30,6 @@ using namespace clang;
 using namespace ento;
 using namespace llvm;
 
-std::vector<StringRef>
-AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental /* = false */) {
-  static const StringRef StaticAnalyzerChecks[] = {
-#define GET_CHECKERS
-#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)                 \
-  FULLNAME,
-#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
-#undef CHECKER
-#undef GET_CHECKERS
-  };
-  std::vector<StringRef> Result;
-  for (StringRef CheckName : StaticAnalyzerChecks) {
-    if (!CheckName.startswith("debug.") &&
-        (IncludeExperimental || !CheckName.startswith("alpha.")))
-      Result.push_back(CheckName);
-  }
-  return Result;
-}
-
 void AnalyzerOptions::printFormattedEntry(
     llvm::raw_ostream &Out,
     std::pair<StringRef, StringRef> EntryDescPair,
index 4cfcdee1c3a121e7c656661f0fbf1498e75cae54..37a3ddecf7a2a2846d5849db254acbe5b5013124 100644 (file)
@@ -1924,15 +1924,22 @@ PathDiagnosticBuilder::PathDiagnosticBuilder(
 
 std::unique_ptr<PathDiagnostic>
 PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const {
-
-  if (!PDC->shouldGenerateDiagnostics())
-    return generateEmptyDiagnosticForReport(R, getSourceManager());
-
   PathDiagnosticConstruct Construct(PDC, ErrorNode, R);
 
   const SourceManager &SM = getSourceManager();
   const BugReport *R = getBugReport();
   const AnalyzerOptions &Opts = getAnalyzerOptions();
+  StringRef ErrorTag = ErrorNode->getLocation().getTag()->getTagDescription();
+
+  // See whether we need to silence the checker/package.
+  // FIXME: This will not work if the report was emitted with an incorrect tag.
+  for (const std::string &CheckerOrPackage : Opts.SilencedCheckersAndPackages) {
+    if (ErrorTag.startswith(CheckerOrPackage))
+      return nullptr;
+  }
+
+  if (!PDC->shouldGenerateDiagnostics())
+    return generateEmptyDiagnosticForReport(R, getSourceManager());
 
   // Construct the final (warning) event for the bug report.
   auto EndNotes = VisitorsDiagnostics->find(ErrorNode);
@@ -2029,7 +2036,6 @@ PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const {
   return std::move(Construct.PD);
 }
 
-
 //===----------------------------------------------------------------------===//
 // Methods for BugType and subclasses.
 //===----------------------------------------------------------------------===//
@@ -2646,9 +2652,13 @@ GRBugReporter::generatePathDiagnostics(
   Optional<PathDiagnosticBuilder> PDB =
       PathDiagnosticBuilder::findValidReport(bugReports, *this);
 
-  if (PDB)
-    for (PathDiagnosticConsumer *PC : consumers)
-      (*Out)[PC] = PDB->generate(PC);
+  if (PDB) {
+    for (PathDiagnosticConsumer *PC : consumers) {
+      if (std::unique_ptr<PathDiagnostic> PD = PDB->generate(PC)) {
+        (*Out)[PC] = std::move(PD);
+      }
+    }
+  }
 
   return Out;
 }
index 3fd4c36947cbb031dbcb8406800e302b95470837..322304b0fb70ec3bf1f7c6c33c84356d262a11c8 100644 (file)
@@ -200,12 +200,12 @@ CheckerRegistry::CheckerRegistry(
 
   // Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the
   // command line.
-  for (const std::pair<std::string, bool> &Opt : AnOpts.CheckersControlList) {
+  for (const std::pair<std::string, bool> &Opt : AnOpts.CheckersAndPackages) {
     CheckerInfoListRange CheckerForCmdLineArg =
         getMutableCheckersForCmdLineArg(Opt.first);
 
     if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) {
-      Diags.Report(diag::err_unknown_analyzer_checker) << Opt.first;
+      Diags.Report(diag::err_unknown_analyzer_checker_or_package) << Opt.first;
       Diags.Report(diag::note_suggest_disabling_all_checkers);
     }
 
@@ -468,9 +468,10 @@ isOptionContainedIn(const CheckerRegistry::CmdLineOptionList &OptionList,
 void CheckerRegistry::validateCheckerOptions() const {
   for (const auto &Config : AnOpts.Config) {
 
-    StringRef SuppliedChecker;
+    StringRef SuppliedCheckerOrPackage;
     StringRef SuppliedOption;
-    std::tie(SuppliedChecker, SuppliedOption) = Config.getKey().split(':');
+    std::tie(SuppliedCheckerOrPackage, SuppliedOption) =
+        Config.getKey().split(':');
 
     if (SuppliedOption.empty())
       continue;
@@ -483,21 +484,24 @@ void CheckerRegistry::validateCheckerOptions() const {
     // Since lower_bound would look for the first element *not less* than "cor",
     // it would return with an iterator to the first checker in the core, so we
     // we really have to use find here, which uses operator==.
-    auto CheckerIt = llvm::find(Checkers, CheckerInfo(SuppliedChecker));
+    auto CheckerIt =
+        llvm::find(Checkers, CheckerInfo(SuppliedCheckerOrPackage));
     if (CheckerIt != Checkers.end()) {
-      isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedChecker,
+      isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedCheckerOrPackage,
                           SuppliedOption, AnOpts, Diags);
       continue;
     }
 
-    auto PackageIt = llvm::find(Packages, PackageInfo(SuppliedChecker));
+    auto PackageIt =
+        llvm::find(Packages, PackageInfo(SuppliedCheckerOrPackage));
     if (PackageIt != Packages.end()) {
-      isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedChecker,
+      isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedCheckerOrPackage,
                           SuppliedOption, AnOpts, Diags);
       continue;
     }
 
-    Diags.Report(diag::err_unknown_analyzer_checker) << SuppliedChecker;
+    Diags.Report(diag::err_unknown_analyzer_checker_or_package)
+        << SuppliedCheckerOrPackage;
   }
 }
 
index 7cba52fdaba2a1b994006939361a0f95a8b8965b..99e1173d5d8c1d08bf0c0a8c212549487ae34925 100644 (file)
@@ -82,6 +82,7 @@
 // CHECK-NEXT: region-store-small-struct-limit = 2
 // CHECK-NEXT: report-in-main-source-file = false
 // CHECK-NEXT: serialize-stats = false
+// CHECK-NEXT: silence-checkers = ""
 // CHECK-NEXT: stable-report-filename = false
 // CHECK-NEXT: suppress-c++-stdlib = true
 // CHECK-NEXT: suppress-inlined-defensive-checks = true
@@ -92,4 +93,4 @@
 // CHECK-NEXT: unroll-loops = false
 // CHECK-NEXT: widen-loops = false
 // CHECK-NEXT: [stats]
-// CHECK-NEXT: num-entries = 89
+// CHECK-NEXT: num-entries = 90
diff --git a/test/Analysis/silence-checkers-and-packages-core-all.cpp b/test/Analysis/silence-checkers-and-packages-core-all.cpp
new file mode 100644 (file)
index 0000000..0805cfc
--- /dev/null
@@ -0,0 +1,39 @@
+// RUN: %clang_analyze_cc1 \
+// RUN:  -analyzer-checker=core -analyzer-config \
+// RUN:   silence-checkers=core \
+// RUN:  -verify %s
+
+// RUN: %clang_analyze_cc1 \
+// RUN:  -analyzer-checker=core -analyzer-config \
+// RUN:   silence-checkers="core.DivideZero;core.NullDereference" \
+// RUN:  -verify %s
+
+// RUN: not %clang_analyze_cc1 -verify %s \
+// RUN:  -analyzer-checker=core -analyzer-config \
+// RUN:   silence-checkers=core.NullDeref \
+// RUN:  2>&1 | FileCheck %s -check-prefix=CHECK-CHECKER-ERROR
+
+// CHECK-CHECKER-ERROR:      (frontend): no analyzer checkers or packages
+// CHECK-CHECKER-ERROR-SAME:             are associated with 'core.NullDeref'
+
+// RUN: not %clang_analyze_cc1 -verify %s \
+// RUN:  -analyzer-checker=core -analyzer-config \
+// RUN:   silence-checkers=coreModeling \
+// RUN:  2>&1 | FileCheck %s -check-prefix=CHECK-PACKAGE-ERROR
+
+// CHECK-PACKAGE-ERROR:      (frontend): no analyzer checkers or packages
+// CHECK-PACKAGE-ERROR-SAME:             are associated with 'coreModeling'
+
+void test_disable_core_div_by_zero() {
+  (void)(1 / 0);
+  // expected-warning@-1 {{division by zero is undefined}}
+  // no-warning: 'Division by zero'
+}
+
+void test_disable_null_deref(int *p) {
+  if (p)
+    return;
+
+  int x = p[0];
+  // no-warning: Array access (from variable 'p') results in a null pointer dereference
+}
diff --git a/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp b/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp
new file mode 100644 (file)
index 0000000..3930f5a
--- /dev/null
@@ -0,0 +1,18 @@
+// RUN: %clang_analyze_cc1 \
+// RUN:  -analyzer-checker=core -analyzer-config \
+// RUN:   silence-checkers=core.DivideZero \
+// RUN:  -verify %s
+
+void test_disable_core_div_by_zero() {
+  (void)(1 / 0);
+  // expected-warning@-1 {{division by zero is undefined}}
+  // no-warning: 'Division by zero'
+}
+
+void test_disable_null_deref(int *p) {
+  if (p)
+    return;
+
+  int x = p[0];
+  // expected-warning@-1 {{Array access (from variable 'p') results in a null pointer dereference}}
+}
index 903e19a2909a68c968886547130e72c59a31b24c..37c94d5ec36e539dff18c304df84d8b61341665e 100755 (executable)
@@ -57,6 +57,7 @@ my %Options = (
   KeepEmpty => 0,            # Don't remove output directory even with 0 results.
   EnableCheckers => {},
   DisableCheckers => {},
+  SilenceCheckers => {},
   Excludes => [],
   UseCC => undef,            # C compiler to use for compilation.
   UseCXX => undef,           # C++ compiler to use for compilation.
@@ -1742,9 +1743,15 @@ sub ProcessArgs {
     if ($arg eq "-disable-checker") {
       shift @$Args;
       my $Checker = shift @$Args;
-      # Store $NumArgs to preserve the order the checkers were disabled.
-      $Options{DisableCheckers}{$Checker} = $NumArgs;
-      delete $Options{EnableCheckers}{$Checker};
+      # Store $NumArgs to preserve the order the checkers are disabled/silenced.
+      # See whether it is a core checker to disable. That means we do not want
+      # to emit a report from that checker so we have to silence it.
+      if (index($Checker, "core") == 0) {
+        $Options{SilenceCheckers}{$Checker} = $NumArgs;
+      } else {
+        $Options{DisableCheckers}{$Checker} = $NumArgs;
+        delete $Options{EnableCheckers}{$Checker};
+      }
       next;
     }
 
@@ -1882,6 +1889,11 @@ foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b}
   # Push checkers in order they were disabled.
   push @AnalysesToRun, "-analyzer-disable-checker", $_;
 }
+foreach (sort { $Options{SilenceCheckers}{$a} <=> $Options{SilenceCheckers}{$b} }
+         keys %{$Options{SilenceCheckers}}) {
+  # Push checkers in order they were silenced.
+  push @AnalysesToRun, "-analyzer-config silence-checker", $_;
+}
 if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; }
 if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; }
 if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; }
index d8988a0ee30ef0907fda274e797d13a5d24d929a..109e76289263697074368343b1abcd2df462bdbf 100644 (file)
@@ -46,7 +46,7 @@ public:
     std::unique_ptr<AnalysisASTConsumer> AnalysisConsumer =
         CreateAnalysisConsumer(Compiler);
     AnalysisConsumer->AddDiagnosticConsumer(new DiagConsumer(DiagsOutput));
-    Compiler.getAnalyzerOpts()->CheckersControlList = {
+    Compiler.getAnalyzerOpts()->CheckersAndPackages = {
         {"custom.CustomChecker", true}};
     AnalysisConsumer->AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
       Registry.addChecker<CheckerT>("custom.CustomChecker", "Description", "");