]> granicus.if.org Git - clang/commitdiff
[analyzer][NFC] Reimplement checker options
authorKristof Umann <kristof.umann@ericsson.com>
Fri, 19 Apr 2019 12:32:10 +0000 (12:32 +0000)
committerKristof Umann <kristof.umann@ericsson.com>
Fri, 19 Apr 2019 12:32:10 +0000 (12:32 +0000)
TL;DR:

* Add checker and package options to the TableGen files
* Added a new class called CmdLineOption, and both Package and Checker recieved
   a list<CmdLineOption> field.
* Added every existing checker and package option to Checkers.td.
* The CheckerRegistry class
  * Received some comments to most of it's inline classes
  * Received the CmdLineOption and PackageInfo inline classes, a list of
     CmdLineOption was added to CheckerInfo and PackageInfo
  * Added addCheckerOption and addPackageOption
  * Added a new field called Packages, used in addPackageOptions, filled up in
     addPackage

Detailed description:

In the last couple months, a lot of effort was put into tightening the
analyzer's command line interface. The main issue is that it's spectacularly
easy to mess up a lenghty enough invocation of the analyzer, and the user was
given no warnings or errors at all in that case.

We can divide the effort of resolving this into several chapters:

* Non-checker analyzer configurations:
    Gather every analyzer configuration into a dedicated file. Emit errors for
    non-existent configurations or incorrect values. Be able to list these
    configurations. Tighten AnalyzerOptions interface to disallow making such
    a mistake in the future.

* Fix the "Checker Naming Bug" by reimplementing checker dependencies:
    When cplusplus.InnerPointer was enabled, it implicitly registered
    unix.Malloc, which implicitly registered some sort of a modeling checker
    from the CStringChecker family. This resulted in all of these checker
    objects recieving the name "cplusplus.InnerPointer", making AnalyzerOptions
    asking for the wrong checker options from the command line:
      cplusplus.InnerPointer:Optimisic
    istead of
      unix.Malloc:Optimistic.
    This was resolved by making CheckerRegistry responsible for checker
    dependency handling, instead of checkers themselves.

* Checker options: (this patch included!)
    Same as the first item, but for checkers.

(+ minor fixes here and there, and everything else that is yet to come)

There were several issues regarding checker options, that non-checker
configurations didn't suffer from: checker plugins are loaded runtime, and they
could add new checkers and new options, meaning that unlike for non-checker
configurations, we can't collect every checker option purely by generating code.
Also, as seen from the "Checker Naming Bug" issue raised above, they are very
rarely used in practice, and all sorts of skeletons fell out of the closet while
working on this project.

They were extremely problematic for users as well, purely because of how long
they were. Consider the following monster of a checker option:

  alpha.cplusplus.UninitializedObject:CheckPointeeInitialization=false

While we were able to verify whether the checker itself (the part before the
colon) existed, any errors past that point were unreported, easily resulting
in 7+ hours of analyses going to waste.

This patch, similarly to how dependencies were reimplemented, uses TableGen to
register checker options into Checkers.td, so that Checkers.inc now contains
entries for both checker and package options. Using the preprocessor,
Checkers.inc is converted into code in CheckerRegistry, adding every builtin
(checkers and packages that have an entry in the Checkers.td file) checker and
package option to the registry. The new addPackageOption and addCheckerOption
functions expose the same functionality to statically-linked non-builtin and
plugin checkers and packages as well.

Emitting errors for incorrect user input, being able to list these options, and
some other functionalies will land in later patches.

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

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

include/clang/Basic/DiagnosticCommonKinds.td
include/clang/StaticAnalyzer/Checkers/CheckerBase.td
include/clang/StaticAnalyzer/Checkers/Checkers.td
include/clang/StaticAnalyzer/Frontend/CheckerRegistry.h
lib/Frontend/CompilerInvocation.cpp
lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp
test/Analysis/disable-all-checks.c
test/Analysis/invalid-checker-option.c [new file with mode: 0644]
utils/TableGen/ClangSACheckersEmitter.cpp

index de2cb8567197bcdd0026914bac981f2b8694a9db..7cd3b211099af3f07570b1858dc33014fa7ce191 100644 (file)
@@ -292,7 +292,7 @@ def err_omp_more_one_clause : Error<
 
 // Static Analyzer Core
 def err_unknown_analyzer_checker : Error<
-    "no analyzer checkers are associated with '%0'">;
+    "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 aaa1b5560fb5afddb04d2d099bea1e38c6ba7c48..0b0f92ea59edc7ba9c98b2628e6b7ede7264f4ee 100644 (file)
 //
 //===----------------------------------------------------------------------===//
 
+/// Describes a checker or package option type. This is important for validating
+/// user supplied inputs.
+/// New option types can be added by modifying this enum. Note that this
+/// requires changes in the TableGen emitter file ClangSACheckersEmitter.cpp.
+class CmdLineOptionTypeEnum<bits<2> val> {
+  bits<2> Type = val;
+}
+def Integer : CmdLineOptionTypeEnum<0>;
+def String : CmdLineOptionTypeEnum<1>;
+def Boolean : CmdLineOptionTypeEnum<2>;
+
+class Type<CmdLineOptionTypeEnum val> {
+  bits<2> Type = val.Type;
+}
+
+/// Describes an option for a checker or a package.
+class CmdLineOption<CmdLineOptionTypeEnum type, string cmdFlag, string desc,
+                    string defaultVal> {
+  bits<2> Type = type.Type;
+  string CmdFlag = cmdFlag;
+  string Desc = desc;
+  string DefaultVal = defaultVal;
+}
+
+/// Describes a list of package options.
+class PackageOptions<list<CmdLineOption> opts> {
+  list<CmdLineOption> PackageOptions = opts;
+}
+
 /// Describes a package. Every checker is a part of a package, for example,
 /// 'NullDereference' is part of the 'core' package, hence it's full name is
 /// 'core.NullDereference'.
 /// Example:
 ///   def Core : Package<"core">;
 class Package<string name> {
-  string       PackageName = name;
-  Package ParentPackage;
+  string              PackageName = name;
+  // This field is optional.
+  list<CmdLineOption> PackageOptions;
+  Package             ParentPackage;
 }
 
 /// Describes a 'super' package that holds another package inside it. This is
@@ -52,11 +83,19 @@ class Documentation<DocumentationEnum val> {
 ///   def DereferenceChecker : Checker<"NullDereference">,
 ///     HelpText<"Check for dereferences of null pointers">;
 class Checker<string name = ""> {
-  string        CheckerName = name;
-  string        HelpText;
-  list<Checker> Dependencies;
-  bits<2>       Documentation;
-  Package       ParentPackage;
+  string              CheckerName = name;
+  string              HelpText;
+  // This field is optional.
+  list<CmdLineOption> CheckerOptions;
+  // This field is optional.
+  list<Checker>       Dependencies;
+  bits<2>             Documentation;
+  Package             ParentPackage;
+}
+
+/// Describes a list of checker options.
+class CheckerOptions<list<CmdLineOption> opts> {
+  list<CmdLineOption> CheckerOptions = opts;
 }
 
 /// Describes dependencies in between checkers. For example, InnerPointerChecker
index af5edae1ca0ce6bd7bd267cc7fef952d4890b25f..3b2fd11af0fe408190f1fe0064abd2ad5d3e8b3a 100644 (file)
@@ -42,7 +42,17 @@ def OptIn : Package<"optin">;
 // development, but unwanted for developers who target only a single platform.
 def PortabilityOptIn : Package<"portability">, ParentPackage<OptIn>;
 
-def Nullability : Package<"nullability">;
+def Nullability : Package<"nullability">,
+  PackageOptions<[
+    CmdLineOption<Boolean,
+                  "NoDiagnoseCallsToSystemHeaders",
+                  "Suppresses warnings for violating nullability annotations "
+                  "of system header functions. This is useful if you are "
+                  "concerned with your custom nullability annotations more "
+                  "than with following nullability specifications of system "
+                  "header functions.",
+                  "false">
+  ]>;
 
 def Cplusplus : Package<"cplusplus">;
 def CplusplusAlpha : Package<"cplusplus">, ParentPackage<Alpha>;
@@ -371,6 +381,14 @@ def DynamicMemoryModeling: Checker<"DynamicMemoryModeling">,
   HelpText<"The base of several malloc() related checkers. On it's own it "
            "emits no reports, but adds valuable information to the analysis "
            "when enabled.">,
+  CheckerOptions<[
+    CmdLineOption<Boolean,
+                  "Optimistic",
+                  "If set to true, the checker assumes that all the "
+                  "allocating and deallocating functions are annotated with "
+                  "ownership_holds, ownership_takes and ownership_returns.",
+                  "false">
+  ]>,
   Dependencies<[CStringModeling]>,
   Documentation<NotDocumented>;
 
@@ -447,7 +465,28 @@ def CXXSelfAssignmentChecker : Checker<"SelfAssignment">,
   Documentation<NotDocumented>;
 
 def MoveChecker: Checker<"Move">,
-   HelpText<"Find use-after-move bugs in C++">,
+  HelpText<"Find use-after-move bugs in C++">,
+  CheckerOptions<[
+    CmdLineOption<String,
+                  "WarnOn",
+                  "In non-aggressive mode, only warn on use-after-move of "
+                  "local variables (or local rvalue references) and of STL "
+                  "objects. The former is possible because local variables (or "
+                  "local rvalue references) are not tempting their user to "
+                  "re-use the storage. The latter is possible because STL "
+                  "objects are known to end up in a valid but unspecified "
+                  "state after the move and their state-reset methods are also "
+                  "known, which allows us to predict precisely when "
+                  "use-after-move is invalid. Some STL objects are known to "
+                  "conform to additional contracts after move, so they are not "
+                  "tracked. However, smart pointers specifically are tracked "
+                  "because we can perform extra checking over them. In "
+                  "aggressive mode, warn on any use-after-move because the "
+                  "user has intentionally asked us to completely eliminate "
+                  "use-after-move in his code. Values: \"KnownsOnly\", "
+                  "\"KnownsAndLocals\", \"All\".",
+                  "KnownsAndLocals">
+  ]>,
   Documentation<HasDocumentation>;
 
 } // end: "cplusplus"
@@ -456,6 +495,12 @@ let ParentPackage = CplusplusOptIn in {
 
 def VirtualCallChecker : Checker<"VirtualCall">,
   HelpText<"Check virtual function calls during construction or destruction">,
+  CheckerOptions<[
+    CmdLineOption<Boolean,
+                  "PureOnly",
+                  "Whether to only report calls to pure virtual methods.",
+                  "false">
+  ]>,
   Documentation<HasDocumentation>;
 
 } // end: "optin.cplusplus"
@@ -492,7 +537,40 @@ def MismatchedIteratorChecker : Checker<"MismatchedIterator">,
   Documentation<HasAlphaDocumentation>;
 
 def UninitializedObjectChecker: Checker<"UninitializedObject">,
-     HelpText<"Reports uninitialized fields after object construction">,
+  HelpText<"Reports uninitialized fields after object construction">,
+  CheckerOptions<[
+    CmdLineOption<Boolean,
+                  "Pedantic",
+                  "If set to false, the checker won't emit warnings "
+                  "for objects that don't have at least one initialized "
+                  "field.",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "NotesAsWarnings",
+                  "If set to true, the checker will emit a warning "
+                  "for each uninitalized field, as opposed to emitting one "
+                  "warning per constructor call, and listing the uninitialized "
+                  "fields that belongs to it in notes.",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "CheckPointeeInitialization",
+                  "If set to false, the checker will not analyze "
+                  "the pointee of pointer/reference fields, and will only "
+                  "check whether the object itself is initialized.",
+                  "false">,
+    CmdLineOption<String,
+                  "IgnoreRecordsWithField",
+                  "If supplied, the checker will not analyze "
+                  "structures that have a field with a name or type name that "
+                  "matches the given pattern.",
+                  "\"\"">,
+    CmdLineOption<Boolean,
+                  "IgnoreGuardedFields",
+                  "If set to true, the checker will analyze _syntactically_ "
+                  "whether the found uninitialized object is used without a "
+                  "preceding assert call. Defaults to false.",
+                  "false">
+  ]>,
   Documentation<HasAlphaDocumentation>;
 
 } // end: "alpha.cplusplus"
@@ -554,6 +632,13 @@ let ParentPackage = Performance in {
 
 def PaddingChecker : Checker<"Padding">,
   HelpText<"Check for excessively padded structs.">,
+  CheckerOptions<[
+    CmdLineOption<Integer,
+                  "AllowedPad",
+                  "Reports are only generated if the excessive padding exceeds "
+                  "'AllowedPad' in bytes.",
+                  "24">
+  ]>,
   Documentation<NotDocumented>;
 
 } // end: "padding"
@@ -662,11 +747,18 @@ def MallocOverflowSecurityChecker : Checker<"MallocOverflow">,
   HelpText<"Check for overflows in the arguments to malloc()">,
   Documentation<HasAlphaDocumentation>;
 
-// Operating systems specific PROT_READ/PROT_WRITE values is not implemented,
-// the defaults are correct for several common operating systems though,
-// but may need to be overridden via the related analyzer-config flags.
 def MmapWriteExecChecker : Checker<"MmapWriteExec">,
   HelpText<"Warn on mmap() calls that are both writable and executable">,
+  CheckerOptions<[
+    CmdLineOption<Integer,
+                  "MmapProtExec",
+                  "Specifies the value of PROT_EXEC",
+                  "0x04">,
+    CmdLineOption<Integer,
+                  "MmapProtRead",
+                  "Specifies the value of PROT_READ",
+                  "0x01">
+  ]>,
   Documentation<HasAlphaDocumentation>;
 
 } // end "alpha.security"
@@ -704,6 +796,14 @@ def NSOrCFErrorDerefChecker : Checker<"NSOrCFErrorDerefChecker">,
 def NumberObjectConversionChecker : Checker<"NumberObjectConversion">,
   HelpText<"Check for erroneous conversions of objects representing numbers "
            "into numbers">,
+  CheckerOptions<[
+    CmdLineOption<Boolean,
+                  "Pedantic",
+                  "Enables detection of more conversion patterns (which are "
+                  "most likely more harmless, and therefore are more likely to "
+                  "produce false positives).",
+                  "false">
+  ]>,
   Documentation<NotDocumented>;
 
 def MacOSXAPIChecker : Checker<"API">,
@@ -795,6 +895,23 @@ def NSErrorChecker : Checker<"NSError">,
 
 def RetainCountChecker : Checker<"RetainCount">,
   HelpText<"Check for leaks and improper reference count management">,
+  CheckerOptions<[
+    CmdLineOption<Boolean,
+                  "CheckOSObject",
+                  "Find violations of retain-release rules applied to XNU "
+                  "OSObject instances. By default, the checker only checks "
+                  "retain-release rules for Objective-C NSObject instances "
+                  "and CoreFoundation objects.",
+                  "true">,
+    CmdLineOption<Boolean,
+                  "TrackNSCFStartParam",
+                  "Check not only that the code follows retain-release rules "
+                  "with respect to objects it allocates or borrows from "
+                  "elsewhere, but also that it fulfills its own retain count "
+                  "specification with respect to objects that it receives as "
+                  "arguments.",
+                  "false">
+  ]>,
   Dependencies<[RetainCountBase]>,
   Documentation<HasDocumentation>;
 
@@ -903,6 +1020,17 @@ let ParentPackage = LocalizabilityOptIn in {
 def NonLocalizedStringChecker : Checker<"NonLocalizedStringChecker">,
   HelpText<"Warns about uses of non-localized NSStrings passed to UI methods "
            "expecting localized NSStrings">,
+  CheckerOptions<[
+    CmdLineOption<Boolean,
+                  "AggressiveReport",
+                  "Marks a string being returned by any call as localized if "
+                  "it is in LocStringFunctions (LSF) or the function is "
+                  "annotated. Otherwise, we mark it as NonLocalized "
+                  "(Aggressive) or NonLocalized only if it is not backed by a "
+                  "SymRegion (Non-Aggressive), basically leaving only string "
+                  "literals as NonLocalized.",
+                  "false">
+  ]>,
   Documentation<HasDocumentation>;
 
 def EmptyLocalizationContextChecker :
@@ -961,6 +1089,72 @@ let ParentPackage = Debug in {
 
 def AnalysisOrderChecker : Checker<"AnalysisOrder">,
   HelpText<"Print callbacks that are called during analysis in order">,
+  CheckerOptions<[
+    CmdLineOption<Boolean,
+                  "PreStmtCastExpr",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PostStmtCastExpr",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PreStmtArraySubscriptExpr",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PostStmtArraySubscriptExpr",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PreStmtCXXNewExpr",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PostStmtCXXNewExpr",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PreStmtOffsetOfExpr",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PostStmtOffsetOfExpr",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PreCall",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "PostCall",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "EndFunction",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "NewAllocator",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "Bind",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "LiveSymbols",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "RegionChanges",
+                  "",
+                  "false">,
+    CmdLineOption<Boolean,
+                  "*",
+                  "Enables all callbacks.",
+                  "false">
+  ]>,
   Documentation<NotDocumented>;
 
 def DominatorsTreeDumper : Checker<"DumpDominators">,
@@ -1034,6 +1228,25 @@ let ParentPackage = CloneDetectionAlpha in {
 
 def CloneChecker : Checker<"CloneChecker">,
   HelpText<"Reports similar pieces of code.">,
+  CheckerOptions<[
+    CmdLineOption<Integer,
+                  "MinimumCloneComplexity",
+                  "Ensures that every clone has at least the given complexity. "
+                  "Complexity is here defined as the total amount of children "
+                  "of a statement. This constraint assumes the first statement "
+                  "in the group is representative for all other statements in "
+                  "the group in terms of complexity.",
+                  "50">,
+    CmdLineOption<Boolean,
+                  "ReportNormalClones",
+                  "Report all clones, even less suspicious ones.",
+                  "true">,
+    CmdLineOption<String,
+                  "IgnoredFilesPattern",
+                  "If supplied, the checker wont analyze files with a filename "
+                  "that matches the given pattern.",
+                  "\"\"">
+  ]>,
   Documentation<HasAlphaDocumentation>;
 
 } // end "clone"
index 052ef66e60e3648c8b6560ef98072dbbce6da9ea..8550baa43e79af64a9f989c7cc71f84cac1476e8 100644 (file)
@@ -91,6 +91,27 @@ public:
   using InitializationFunction = void (*)(CheckerManager &);
   using ShouldRegisterFunction = bool (*)(const LangOptions &);
 
+  /// Specifies a command line option. It may either belong to a checker or a
+  /// package.
+  struct CmdLineOption {
+    StringRef OptionType;
+    StringRef OptionName;
+    StringRef DefaultValStr;
+    StringRef Description;
+
+    CmdLineOption(StringRef OptionType, StringRef OptionName,
+                  StringRef DefaultValStr, StringRef Description)
+        : OptionType(OptionType), OptionName(OptionName),
+          DefaultValStr(DefaultValStr), Description(Description) {
+
+      assert((OptionType == "bool" || OptionType == "string" ||
+              OptionType == "int") &&
+             "Unknown command line option type!");
+    }
+  };
+
+  using CmdLineOptionList = llvm::SmallVector<CmdLineOption, 0>;
+
   struct CheckerInfo;
 
   using CheckerInfoList = std::vector<CheckerInfo>;
@@ -98,6 +119,8 @@ public:
   using ConstCheckerInfoList = llvm::SmallVector<const CheckerInfo *, 0>;
   using CheckerInfoSet = llvm::SetVector<const CheckerInfo *>;
 
+  /// Specifies a checker. Note that this isn't what we call a checker object,
+  /// it merely contains everything required to create one.
   struct CheckerInfo {
     enum class StateFromCmdLine {
       // This checker wasn't explicitly enabled or disabled.
@@ -113,6 +136,7 @@ public:
     StringRef FullName;
     StringRef Desc;
     StringRef DocumentationUri;
+    CmdLineOptionList CmdLineOptions;
     StateFromCmdLine State = StateFromCmdLine::State_Unspecified;
 
     ConstCheckerInfoList Dependencies;
@@ -136,6 +160,23 @@ public:
 
   using StateFromCmdLine = CheckerInfo::StateFromCmdLine;
 
+  /// Specifies a package. Each package option is implicitly an option for all
+  /// checkers within the package.
+  struct PackageInfo {
+    StringRef FullName;
+    CmdLineOptionList CmdLineOptions;
+
+    // Since each package must have a different full name, we can identify
+    // CheckerInfo objects by them.
+    bool operator==(const PackageInfo &Rhs) const {
+      return FullName == Rhs.FullName;
+    }
+
+    explicit PackageInfo(StringRef FullName) : FullName(FullName) {}
+  };
+
+  using PackageInfoList = llvm::SmallVector<PackageInfo, 0>;
+
 private:
   template <typename T> static void initializeManager(CheckerManager &mgr) {
     mgr.registerChecker<T>();
@@ -165,6 +206,35 @@ public:
   /// called \p dependency.
   void addDependency(StringRef FullName, StringRef Dependency);
 
+  /// Registers an option to a given checker. A checker option will always have
+  /// the following format:
+  ///   CheckerFullName:OptionName=Value
+  /// And can be specified from the command line like this:
+  ///   -analyzer-config CheckerFullName:OptionName=Value
+  ///
+  /// Options for unknown checkers, or unknown options for a given checker, or
+  /// invalid value types for that given option are reported as an error in
+  /// non-compatibility mode.
+  void addCheckerOption(StringRef OptionType, StringRef CheckerFullName,
+                        StringRef OptionName, StringRef DefaultValStr,
+                        StringRef Description);
+
+  /// Adds a package to the registry.
+  void addPackage(StringRef FullName);
+
+  /// Registers an option to a given package. A package option will always have
+  /// the following format:
+  ///   PackageFullName:OptionName=Value
+  /// And can be specified from the command line like this:
+  ///   -analyzer-config PackageFullName:OptionName=Value
+  ///
+  /// Options for unknown packages, or unknown options for a given package, or
+  /// invalid value types for that given option are reported as an error in
+  /// non-compatibility mode.
+  void addPackageOption(StringRef OptionType, StringRef PackageFullName,
+                        StringRef OptionName, StringRef DefaultValStr,
+                        StringRef Description);
+
   // FIXME: This *really* should be added to the frontend flag descriptions.
   /// Initializes a CheckerManager by calling the initialization functions for
   /// all checkers specified by the given CheckerOptInfo list. The order of this
@@ -193,21 +263,30 @@ private:
   CheckerInfoListRange getMutableCheckersForCmdLineArg(StringRef CmdLineArg);
 
   CheckerInfoList Checkers;
+  PackageInfoList Packages;
+  /// Used for couting how many checkers belong to a certain package in the
+  /// \c Checkers field. For convenience purposes.
   llvm::StringMap<size_t> PackageSizes;
 
   /// Contains all (Dependendent checker, Dependency) pairs. We need this, as
   /// we'll resolve dependencies after all checkers were added first.
   llvm::SmallVector<std::pair<StringRef, StringRef>, 0> Dependencies;
-
   void resolveDependencies();
 
+  /// Contains all (FullName, CmdLineOption) pairs. Similarly to dependencies,
+  /// we only modify the actual CheckerInfo and PackageInfo objects once all
+  /// of them have been added.
+  llvm::SmallVector<std::pair<StringRef, CmdLineOption>, 0> PackageOptions;
+  llvm::SmallVector<std::pair<StringRef, CmdLineOption>, 0> CheckerOptions;
+
+  void resolveCheckerAndPackageOptions();
+
   DiagnosticsEngine &Diags;
   AnalyzerOptions &AnOpts;
   const LangOptions &LangOpts;
 };
 
 } // namespace ento
-
 } // namespace clang
 
 #endif // LLVM_CLANG_STATICANALYZER_CORE_CHECKERREGISTRY_H
index 5caf8b4e10edf285441565418fac479d173cd2ae..cc7d5b62a84c3d37ed705667e6467b6c50c3cab7 100644 (file)
@@ -422,7 +422,7 @@ static void initOption(AnalyzerOptions::ConfigTable &Config,
 
   OptionField = DefaultVal;
   bool HasFailed = getStringOption(Config, Name, std::to_string(DefaultVal))
-                     .getAsInteger(10, OptionField);
+                     .getAsInteger(0, OptionField);
   if (Diags && HasFailed)
     Diags->Report(diag::err_analyzer_config_invalid_input)
       << Name << "an unsigned";
index 8f1c87a92ee55f902041ac203f4a4f91c158c5da..f1ee454f632816697af97a34708300ffae757928 100644 (file)
@@ -45,6 +45,7 @@ template <class T> struct FullNameLT {
   }
 };
 
+using PackageNameLT = FullNameLT<CheckerRegistry::PackageInfo>;
 using CheckerNameLT = FullNameLT<CheckerRegistry::CheckerInfo>;
 } // end of anonymous namespace
 
@@ -118,6 +119,9 @@ CheckerRegistry::CheckerRegistry(
   addChecker(register##CLASS, shouldRegister##CLASS, FULLNAME, HELPTEXT,       \
              DOC_URI);
 
+#define GET_PACKAGES
+#define PACKAGE(FULLNAME) addPackage(FULLNAME);
+
 #include "clang/StaticAnalyzer/Checkers/Checkers.inc"
 #undef CHECKER
 #undef GET_CHECKERS
@@ -166,6 +170,7 @@ CheckerRegistry::CheckerRegistry(
   // FIXME: Alphabetical sort puts 'experimental' in the middle.
   // Would it be better to name it '~experimental' or something else
   // that's ASCIIbetically last?
+  llvm::sort(Packages, PackageNameLT{});
   llvm::sort(Checkers, CheckerNameLT{});
 
 #define GET_CHECKER_DEPENDENCIES
@@ -173,11 +178,24 @@ CheckerRegistry::CheckerRegistry(
 #define CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY)                               \
   addDependency(FULLNAME, DEPENDENCY);
 
+#define GET_CHECKER_OPTIONS
+#define CHECKER_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL)             \
+  addCheckerOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC);
+
+#define GET_PACKAGE_OPTIONS
+#define PACKAGE_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL)             \
+  addPackageOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC);
+
 #include "clang/StaticAnalyzer/Checkers/Checkers.inc"
 #undef CHECKER_DEPENDENCY
 #undef GET_CHECKER_DEPENDENCIES
+#undef CHECKER_OPTION
+#undef GET_CHECKER_OPTIONS
+#undef PACKAGE_OPTION
+#undef GET_PACKAGE_OPTIONS
 
   resolveDependencies();
+  resolveCheckerAndPackageOptions();
 
   // Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the
   // command line.
@@ -266,20 +284,6 @@ CheckerRegistry::CheckerInfoSet CheckerRegistry::getEnabledCheckers() const {
   return EnabledCheckers;
 }
 
-void CheckerRegistry::addChecker(InitializationFunction Rfn,
-                                 ShouldRegisterFunction Sfn, StringRef Name,
-                                 StringRef Desc, StringRef DocsUri) {
-  Checkers.emplace_back(Rfn, Sfn, Name, Desc, DocsUri);
-
-  // Record the presence of the checker in its packages.
-  StringRef PackageName, LeafName;
-  std::tie(PackageName, LeafName) = Name.rsplit(PackageSeparator);
-  while (!LeafName.empty()) {
-    PackageSizes[PackageName] += 1;
-    std::tie(PackageName, LeafName) = PackageName.rsplit(PackageSeparator);
-  }
-}
-
 void CheckerRegistry::resolveDependencies() {
   for (const std::pair<StringRef, StringRef> &Entry : Dependencies) {
     auto CheckerIt = binaryFind(Checkers, Entry.first);
@@ -302,6 +306,72 @@ void CheckerRegistry::addDependency(StringRef FullName, StringRef Dependency) {
   Dependencies.emplace_back(FullName, Dependency);
 }
 
+template <class T>
+static void
+insertOptionToCollection(StringRef FullName, T &Collection,
+                         const CheckerRegistry::CmdLineOption &&Option) {
+  auto It = binaryFind(Collection, FullName);
+  assert(It != Collection.end() &&
+         "Failed to find the checker while attempting to add a command line "
+         "option to it!");
+
+  It->CmdLineOptions.emplace_back(std::move(Option));
+}
+
+void CheckerRegistry::resolveCheckerAndPackageOptions() {
+  for (const std::pair<StringRef, CmdLineOption> &CheckerOptEntry :
+       CheckerOptions) {
+    insertOptionToCollection(CheckerOptEntry.first, Checkers,
+                             std::move(CheckerOptEntry.second));
+  }
+  CheckerOptions.clear();
+
+  for (const std::pair<StringRef, CmdLineOption> &PackageOptEntry :
+       PackageOptions) {
+    insertOptionToCollection(PackageOptEntry.first, Checkers,
+                             std::move(PackageOptEntry.second));
+  }
+  PackageOptions.clear();
+}
+
+void CheckerRegistry::addPackage(StringRef FullName) {
+  Packages.emplace_back(PackageInfo(FullName));
+}
+
+void CheckerRegistry::addPackageOption(StringRef OptionType,
+                                       StringRef PackageFullName,
+                                       StringRef OptionName,
+                                       StringRef DefaultValStr,
+                                       StringRef Description) {
+  PackageOptions.emplace_back(
+      PackageFullName,
+      CmdLineOption{OptionType, OptionName, DefaultValStr, Description});
+}
+
+void CheckerRegistry::addChecker(InitializationFunction Rfn,
+                                 ShouldRegisterFunction Sfn, StringRef Name,
+                                 StringRef Desc, StringRef DocsUri) {
+  Checkers.emplace_back(Rfn, Sfn, Name, Desc, DocsUri);
+
+  // Record the presence of the checker in its packages.
+  StringRef PackageName, LeafName;
+  std::tie(PackageName, LeafName) = Name.rsplit(PackageSeparator);
+  while (!LeafName.empty()) {
+    PackageSizes[PackageName] += 1;
+    std::tie(PackageName, LeafName) = PackageName.rsplit(PackageSeparator);
+  }
+}
+
+void CheckerRegistry::addCheckerOption(StringRef OptionType,
+                                       StringRef CheckerFullName,
+                                       StringRef OptionName,
+                                       StringRef DefaultValStr,
+                                       StringRef Description) {
+  CheckerOptions.emplace_back(
+      CheckerFullName,
+      CmdLineOption{OptionType, OptionName, DefaultValStr, Description});
+}
+
 void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const {
   // Collect checkers enabled by the options.
   CheckerInfoSet enabledCheckers = getEnabledCheckers();
index fba53429ab5eb84a380fdfd05ec0ad5499a1d3ae..4d1c625ef1f1ab238004ebc370721d0b48340892 100644 (file)
@@ -12,7 +12,7 @@
 //
 // expected-no-diagnostics
 
-// CHECK: no analyzer checkers are associated with 'non.existant.Checker'
+// CHECK: no analyzer checkers or packages are associated with 'non.existant.Checker'
 // CHECK: use -analyzer-disable-all-checks to disable all static analyzer checkers
 int buggy() {
   int x = 0;
diff --git a/test/Analysis/invalid-checker-option.c b/test/Analysis/invalid-checker-option.c
new file mode 100644 (file)
index 0000000..4ce7835
--- /dev/null
@@ -0,0 +1,19 @@
+// RUN: not %clang_analyze_cc1 -verify %s \
+// RUN:   -analyzer-checker=core \
+// RUN:   -analyzer-config RetainOneTwoThree:CheckOSObject=false \
+// RUN:   2>&1 | FileCheck %s -check-prefix=CHECK-NON-EXISTENT-CHECKER
+
+// Note that non-existent packages and checkers were always reported.
+
+// RUN: not %clang_analyze_cc1 -verify %s \
+// RUN:   -analyzer-checker=core \
+// RUN:   -analyzer-config-compatibility-mode=true \
+// RUN:   -analyzer-config RetainOneTwoThree:CheckOSObject=false \
+// RUN:   2>&1 | FileCheck %s -check-prefix=CHECK-NON-EXISTENT-CHECKER
+
+// CHECK-NON-EXISTENT-CHECKER: (frontend): no analyzer checkers or packages
+// CHECK-NON-EXISTENT-CHECKER-SAME: are associated with 'RetainOneTwoThree'
+
+// expected-no-diagnostics
+
+int main() {}
index 259df5b138f6d12c13b9813c589dc70621794553..03c3b3b9fb9600e4dc65336d54d3a80eb321e0d6 100644 (file)
@@ -90,6 +90,26 @@ static std::string getCheckerDocs(const Record &R) {
       .str();
 }
 
+/// Retrieves the type from a CmdOptionTypeEnum typed Record object. Note that
+/// the class itself has to be modified for adding a new option type in
+/// CheckerBase.td.
+static std::string getCheckerOptionType(const Record &R) {
+  if (BitsInit *BI = R.getValueAsBitsInit("Type")) {
+    switch(getValueFromBitsInit(BI, R)) {
+    case 0:
+      return "int";
+    case 1:
+      return "string";
+    case 2:
+      return "bool";
+    }
+  }
+  PrintFatalError(R.getLoc(),
+                  "unable to parse command line option type for "
+                  + getCheckerFullName(&R));
+  return "";
+}
+
 static void printChecker(llvm::raw_ostream &OS, const Record &R) {
     OS << "CHECKER(" << "\"";
     OS.write_escaped(getCheckerFullName(&R)) << "\", ";
@@ -134,6 +154,45 @@ void EmitClangSACheckers(RecordKeeper &Records, raw_ostream &OS) {
   OS << "#endif // GET_PACKAGES\n"
         "\n";
 
+  // Emit a package option.
+  //
+  // PACKAGE_OPTION(OPTIONTYPE, PACKAGENAME, OPTIONNAME, DESCRIPTION, DEFAULT)
+  //   - OPTIONTYPE: Type of the option, whether it's integer or boolean etc.
+  //                 This is important for validating user input. Note that
+  //                 it's a string, rather than an actual type: since we can
+  //                 load checkers runtime, we can't use template hackery for
+  //                 sorting this out compile-time.
+  //   - PACKAGENAME: Name of the package.
+  //   - OPTIONNAME: Name of the option.
+  //   - DESCRIPTION
+  //   - DEFAULT: The default value for this option.
+  //
+  // The full option can be specified in the command like like this:
+  //   -analyzer-config PACKAGENAME:OPTIONNAME=VALUE
+  OS << "\n"
+        "#ifdef GET_PACKAGE_OPTIONS\n";
+  for (const Record *Package : packages) {
+
+    if (Package->isValueUnset("PackageOptions"))
+      continue;
+
+    std::vector<Record *> PackageOptions = Package
+                                       ->getValueAsListOfDefs("PackageOptions");
+    for (Record *PackageOpt : PackageOptions) {
+      OS << "PACKAGE_OPTION(\"";
+      OS.write_escaped(getCheckerOptionType(*PackageOpt)) << "\", \"";
+      OS.write_escaped(getPackageFullName(Package)) << "\", ";
+      OS << '\"' << getStringValue(*PackageOpt, "CmdFlag") << "\", ";
+      OS << '\"';
+      OS.write_escaped(getStringValue(*PackageOpt, "Desc")) << "\", ";
+      OS << '\"';
+      OS.write_escaped(getStringValue(*PackageOpt, "DefaultVal")) << "\"";
+      OS << ")\n";
+    }
+  }
+  OS << "#endif // GET_PACKAGE_OPTIONS\n"
+        "\n";
+
   // Emit checkers.
   //
   // CHECKER(FULLNAME, CLASS, HELPTEXT)
@@ -160,15 +219,15 @@ void EmitClangSACheckers(RecordKeeper &Records, raw_ostream &OS) {
   //   - DEPENDENCY: The full name of the checker FULLNAME depends on.
   OS << "\n"
         "#ifdef GET_CHECKER_DEPENDENCIES\n";
-  for (const Record *checker : checkers) {
-    if (checker->isValueUnset("Dependencies"))
+  for (const Record *Checker : checkers) {
+    if (Checker->isValueUnset("Dependencies"))
       continue;
 
     for (const Record *Dependency :
-                            checker->getValueAsListOfDefs("Dependencies")) {
+                            Checker->getValueAsListOfDefs("Dependencies")) {
       OS << "CHECKER_DEPENDENCY(";
       OS << '\"';
-      OS.write_escaped(getCheckerFullName(checker)) << "\", ";
+      OS.write_escaped(getCheckerFullName(Checker)) << "\", ";
       OS << '\"';
       OS.write_escaped(getCheckerFullName(Dependency)) << '\"';
       OS << ")\n";
@@ -176,5 +235,45 @@ void EmitClangSACheckers(RecordKeeper &Records, raw_ostream &OS) {
   }
   OS << "\n"
         "#endif // GET_CHECKER_DEPENDENCIES\n";
+
+  // Emit a package option.
+  //
+  // CHECKER_OPTION(OPTIONTYPE, CHECKERNAME, OPTIONNAME, DESCRIPTION, DEFAULT)
+  //   - OPTIONTYPE: Type of the option, whether it's integer or boolean etc.
+  //                 This is important for validating user input. Note that
+  //                 it's a string, rather than an actual type: since we can
+  //                 load checkers runtime, we can't use template hackery for
+  //                 sorting this out compile-time.
+  //   - CHECKERNAME: Name of the package.
+  //   - OPTIONNAME: Name of the option.
+  //   - DESCRIPTION
+  //   - DEFAULT: The default value for this option.
+  //
+  // The full option can be specified in the command like like this:
+  //   -analyzer-config CHECKERNAME:OPTIONNAME=VALUE
+  OS << "\n"
+        "#ifdef GET_CHECKER_OPTIONS\n";
+  for (const Record *Checker : checkers) {
+
+    if (Checker->isValueUnset("CheckerOptions"))
+      continue;
+
+    std::vector<Record *> CheckerOptions = Checker
+                                       ->getValueAsListOfDefs("CheckerOptions");
+    for (Record *CheckerOpt : CheckerOptions) {
+      OS << "CHECKER_OPTION(\"";
+      OS << getCheckerOptionType(*CheckerOpt) << "\", \"";
+      OS.write_escaped(getCheckerFullName(Checker)) << "\", ";
+      OS << '\"' << getStringValue(*CheckerOpt, "CmdFlag") << "\", ";
+      OS << '\"';
+      OS.write_escaped(getStringValue(*CheckerOpt, "Desc")) << "\", ";
+      OS << '\"';
+      OS.write_escaped(getStringValue(*CheckerOpt, "DefaultVal")) << "\"";
+      OS << ")";
+      OS << '\n';
+    }
+  }
+  OS << "#endif // GET_CHECKER_OPTIONS\n"
+        "\n";
 }
 } // end namespace clang