Index: include/clang/Basic/DiagnosticFrontendKinds.td =================================================================== --- include/clang/Basic/DiagnosticFrontendKinds.td +++ include/clang/Basic/DiagnosticFrontendKinds.td @@ -170,6 +170,8 @@ def err_unknown_analyzer_checker : Error< "no analyzer checkers are associated with '%0'">; +def err_unknown_analyzer_config : Error< + "no analyzer configs are associated with '%0'">; def note_suggest_disabling_all_checkers : Note< "use -analyzer-disable-all-checks to disable all static analyzer checkers">; Index: include/clang/StaticAnalyzer/Checkers/CheckerBase.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/CheckerBase.td +++ include/clang/StaticAnalyzer/Checkers/CheckerBase.td @@ -16,11 +16,18 @@ } class InGroup { CheckerGroup Group = G; } +class Config { + string ConfigName = name; +} + +class GlobalConfig : Config {} + class Package { string PackageName = name; bit Hidden = 0; Package ParentPackage; CheckerGroup Group; + list Configs = []; } class InPackage { Package ParentPackage = P; } @@ -32,8 +39,10 @@ bit Hidden = 0; Package ParentPackage; CheckerGroup Group; + list Configs = []; } class DescFile { string DescFile = filename; } class HelpText { string HelpText = text; } class Hidden { bit Hidden = 1; } +class HasConfigs C> { list Configs = C; } Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -13,6 +13,11 @@ // Packages. //===----------------------------------------------------------------------===// +// Package configs + +def OptimisticConfig : Config<"Optimistic">; +def NoDiagnoseCallsToSystemHeadersConfig : Config<"NoDiagnoseCallsToSystemHeaders">; + // The Alpha package is for checkers that have too many false positives to be // turned on by default. The hierarchy under Alpha should be organized in the // hierarchy checkers would have had if they were truly at the top level. @@ -43,7 +48,8 @@ // development, but unwanted for developers who target only a single platform. def PortabilityOptIn : Package<"portability">, InPackage; -def Nullability : Package<"nullability">; +def Nullability : Package<"nullability">, + HasConfigs<[NoDiagnoseCallsToSystemHeadersConfig]>; def Cplusplus : Package<"cplusplus">; def CplusplusAlpha : Package<"cplusplus">, InPackage, Hidden; @@ -61,7 +67,7 @@ def SecurityAlpha : Package<"security">, InPackage, Hidden; def Taint : Package<"taint">, InPackage, Hidden; -def Unix : Package<"unix">; +def Unix : Package<"unix">, HasConfigs<[OptimisticConfig]>; def UnixAlpha : Package<"unix">, InPackage, Hidden; def CString : Package<"cstring">, InPackage, Hidden; def CStringAlpha : Package<"cstring">, InPackage, Hidden; @@ -95,6 +101,44 @@ def CloneDetectionAlpha : Package<"clone">, InPackage, Hidden; //===----------------------------------------------------------------------===// +// Global configs. +//===----------------------------------------------------------------------===// + +def AllConfig : GlobalConfig<"*">; +def AvoidSuppressingNullArgumentPaths : GlobalConfig<"avoid-suppressing-null-argument-paths">; +def CxxAllocatorInliningConfig : GlobalConfig<"c++-allocator-inlining">; +def CxxcontainerInliningConfig : GlobalConfig<"c++-container-inlining">; +def CfgConditionalStaticInitializersConfig : GlobalConfig<"cfg-conditional-static-initializers">; +def CfgImplicitDtorsConfig : GlobalConfig<"cfg-implicit-dtors">; +def CfgLifetimeConfig : GlobalConfig<"cfg-lifetime">; +def CfgTemporaryDtorsConfig : GlobalConfig<"cfg-temporary-dtors">; +def CxxinliningConfig : GlobalConfig<"c++-inlining">; +def CxxstdlibInliningConfig : GlobalConfig<"c++-stdlib-inlining">; +def CxxtemplateInliningConfig : GlobalConfig<"c++-template-inlining">; +def FauxBodiesConfig : GlobalConfig<"faux-bodies">; +def GraphTrimIntervalConfig : GlobalConfig<"graph-trim-interval">; +def InlineLambdasConfig : GlobalConfig<"inline-lambdas">; +def IpaConfig : GlobalConfig<"ipa">; +def IpaAlwaysInlineSizeConfig : GlobalConfig<"ipa-always-inline-size">; +def LeakDiagnosticsReferenceAllocConfig : GlobalConfig<"leak-diagnostics-reference-allocation">; +def MaxNodesConfig : GlobalConfig<"max-nodes">; +def MaxInlinableSize : GlobalConfig<"max-inlinable-size">; +def MaxTimesInlineLarge : GlobalConfig<"max-times-inline-large">; +def ModeConfig : GlobalConfig<"mode">; +def ModelPathConfig : GlobalConfig<"model-path">; +def NotesSsEventsConfig : GlobalConfig<"notes-as-events">; +def ObjcInliningConfig : GlobalConfig<"objc-inlining">; +def PathDiagnosticsAlternateConfig : GlobalConfig<"path-diagnostics-alternate">; +def PrunePathsConfig : GlobalConfig<"prune-paths">; +def RegionStoreSmallStructLimitConfig : GlobalConfig<"region-store-small-struct-limit">; +def ReportInMainSourceFileConfig : GlobalConfig<"report-in-main-source-file">; +def SuppressCxxStdlibConfig : GlobalConfig<"suppress-c++-stdlib">; +def SuppressInlinedDefensiveChecksConfig : GlobalConfig<"suppress-inlined-defensive-checks">; +def SuppressNullReturnPathsConfig : GlobalConfig<"suppress-null-return-paths">; +def UnrollLoopsConfig : GlobalConfig<"unroll-loops">; +def WidenLoopsConfig : GlobalConfig<"widen-loops">; + +//===----------------------------------------------------------------------===// // Core Checkers. //===----------------------------------------------------------------------===// @@ -274,11 +318,15 @@ } // end: "cplusplus" +def Interprocedural : Config<"Interprocedural">; +def PureOnly : Config<"PureOnly">; + let ParentPackage = CplusplusOptIn in { def VirtualCallChecker : Checker<"VirtualCall">, HelpText<"Check virtual function calls during construction or destruction">, - DescFile<"VirtualCallChecker.cpp">; + DescFile<"VirtualCallChecker.cpp">, + HasConfigs<[Interprocedural, PureOnly]>; } // end: "optin.cplusplus" @@ -339,11 +387,14 @@ // Performance checkers. //===----------------------------------------------------------------------===// +def AllowedPad : Config<"AllowedPad">; + let ParentPackage = Performance in { def PaddingChecker : Checker<"Padding">, HelpText<"Check for excessively padded structs.">, - DescFile<"PaddingChecker.cpp">; + DescFile<"PaddingChecker.cpp">, + HasConfigs<[AllowedPad]>; } // end: "padding" @@ -501,11 +552,14 @@ // Mac OS X, Cocoa, and Core Foundation checkers. //===----------------------------------------------------------------------===// +def Pedantic : Config<"Pedantic">; + let ParentPackage = OSX in { def NumberObjectConversionChecker : Checker<"NumberObjectConversion">, HelpText<"Check for erroneous conversions of objects representing numbers into numbers">, - DescFile<"NumberObjectConversionChecker.cpp">; + DescFile<"NumberObjectConversionChecker.cpp">, + HasConfigs<[Pedantic]>; def MacOSXAPIChecker : Checker<"API">, HelpText<"Check for proper uses of various Apple APIs">, @@ -636,6 +690,9 @@ } +// FIXME: Only used by NonLocalizedStringChecker, should be attached there? +def AggressiveReportConfig : GlobalConfig<"AggressiveReport">; + let ParentPackage = LocalizabilityOptIn in { def NonLocalizedStringChecker : Checker<"NonLocalizedStringChecker">, HelpText<"Warns about uses of non-localized NSStrings passed to UI methods expecting localized NSStrings">, @@ -682,11 +739,20 @@ // Debugging checkers (for analyzer development). //===----------------------------------------------------------------------===// +def Bind : Config<"Bind">; +def PostStmtCastExpr : Config<"PostStmtCastExpr">; +def PreStmtCastExpr : Config<"PreStmtCastExpr">; +def RegionChanges : Config<"RegionChanges">; + let ParentPackage = Debug in { def AnalysisOrderChecker : Checker<"AnalysisOrder">, HelpText<"Print callbacks that are called during analysis in order">, - DescFile<"AnalysisOrder.cpp">; + DescFile<"AnalysisOrder.cpp">, + HasConfigs<[Bind, + PostStmtCastExpr, + PreStmtCastExpr, + RegionChanges]>; def DominatorsTreeDumper : Checker<"DumpDominators">, HelpText<"Print the dominance tree for a given CFG">, @@ -751,11 +817,20 @@ // Clone Detection //===----------------------------------------------------------------------===// +def IgnoredFilesPattern : Config<"IgnoredFilesPattern">; +def MinimumCloneComplexity : Config<"MinimumCloneComplexity">; +def ReportNormalClones : Config<"ReportNormalClones">; +def ReportSuspiciousClones : Config<"ReportSuspiciousClones">; + let ParentPackage = CloneDetectionAlpha in { def CloneChecker : Checker<"CloneChecker">, HelpText<"Reports similar pieces of code.">, - DescFile<"CloneChecker.cpp">; + DescFile<"CloneChecker.cpp">, + HasConfigs<[IgnoredFilesPattern, + MinimumCloneComplexity, + ReportNormalClones, + ReportSuspiciousClones]>; } // end "clone" Index: include/clang/StaticAnalyzer/Core/CheckerRegistry.h =================================================================== --- include/clang/StaticAnalyzer/Core/CheckerRegistry.h +++ include/clang/StaticAnalyzer/Core/CheckerRegistry.h @@ -91,6 +91,15 @@ : Initialize(fn), FullName(name), Desc(desc) {} }; + /// A config value. + struct Config { + StringRef FullName; + // TODO: Supports descriptions, default value etc. here. + }; + + /// All configs known to this registry mapped by their full name. + llvm::StringMap Configs; + typedef std::vector CheckerInfoList; private: @@ -114,6 +123,24 @@ addChecker(&CheckerRegistry::initializeManager, fullName, desc); } + /// Adds a config to the registry that the user can use to modify the behavior + /// of certain checkers. + /// \param fullName The full name of the new config in the same format as the + /// '-analyzer-config' flag of clang expects. For example + /// 'core.pkg:Setting' or 'core.alpha.TestChecker:TestConfig'. + void addConfig(StringRef fullName); + + /// Checks if the given full name of the config is known to this registry. + /// + /// Also checks all parent packages for a config with the given name. The + /// config 'TestConfig' in 'core.alpha.TestChecker:TestConfig' would be + /// searched in 'core', 'core.alpha' and 'core.alpha.TestChecker'. + /// + /// \param fullName The full name of the new config in the same format as the + /// '-analyzer-config' flag of clang expects. For example + /// 'core.pkg:Setting' or 'core.alpha.TestChecker:TestConfig'. + bool hasConfig(StringRef fullName) const; + /// Initializes a CheckerManager by calling the initialization functions for /// all checkers specified by the given CheckerOptInfo list. The order of this /// list is significant; later options can be used to reverse earlier ones. @@ -121,7 +148,8 @@ void initializeManager(CheckerManager &mgr, SmallVectorImpl &opts) const; - /// Check if every option corresponds to a specific checker or package. + /// Check if every option corresponds to a specific checker or package and was + /// previously added to this registry. void validateCheckerOptions(const AnalyzerOptions &opts, DiagnosticsEngine &diags) const; Index: lib/StaticAnalyzer/Checkers/ClangCheckers.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/ClangCheckers.cpp +++ lib/StaticAnalyzer/Checkers/ClangCheckers.cpp @@ -29,4 +29,19 @@ registry.addChecker(register##CLASS, FULLNAME, HELPTEXT); #include "clang/StaticAnalyzer/Checkers/Checkers.inc" #undef GET_CHECKERS + +#define GET_GLOBAL_CONFIGS +#define GLOBAL_CONFIG(NAME) registry.addConfig(NAME); +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef GET_GLOBAL_CONFIGS + +#define GET_CHECKER_CONFIGS +#define CHECKER_CONFIG(CHECKER, NAME) registry.addConfig(CHECKER ":" NAME); +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef GET_CHECKER_CONFIGS + +#define GET_PACKAGE_CONFIGS +#define PACKAGE_CONFIG(PACKAGE, NAME) registry.addConfig(PACKAGE ":" NAME); +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef GET_PACKAGE_CONFIGS } Index: lib/StaticAnalyzer/Core/CheckerRegistry.cpp =================================================================== --- lib/StaticAnalyzer/Core/CheckerRegistry.cpp +++ lib/StaticAnalyzer/Core/CheckerRegistry.cpp @@ -10,9 +10,10 @@ #include "clang/StaticAnalyzer/Core/CheckerRegistry.h" #include "clang/Basic/Diagnostic.h" #include "clang/Frontend/FrontendDiagnostic.h" -#include "clang/StaticAnalyzer/Core/CheckerOptInfo.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "clang/StaticAnalyzer/Core/CheckerOptInfo.h" #include "llvm/ADT/SetVector.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -94,8 +95,46 @@ } } -void CheckerRegistry::initializeManager(CheckerManager &checkerMgr, - SmallVectorImpl &opts) const { +void CheckerRegistry::addConfig(llvm::StringRef fullName) { + Configs[fullName] = {fullName}; +} + +bool CheckerRegistry::hasConfig(llvm::StringRef fullName) const { + // Let's first see if we can directly find the config value in our map, this + // handles all global configs and all configs that are directly specified + // to the packages/checkers they belong to. + // Examples are 'core.MemoryChecker:MemoryVal' if the MemoryChecker is + // declared to have the MemoryVal config. + if (Configs.count(fullName) == 1) + return true; + + // Direct lookup failed, but we still have to check all parent packages below. + + size_t pos = fullName.find(':'); + if (pos == StringRef::npos || pos == fullName.size() - 1) { + // At this point we either have a marformed config key (like 'foo:') or + // we would have been handled in the lookup above, so we can fail now. + return false; + } + // Split the key into checkerName (or package name) and the configName. + StringRef checkerName = fullName.substr(0, pos); + StringRef configName = fullName.substr(pos + 1); + + SmallVector parts; + checkerName.split(parts, '.'); + // Iterate over all parent packages and check if they have such a config. + for (size_t i = 1; i <= parts.size(); ++i) { + std::string newConfig = llvm::join(parts.begin(), parts.begin() + i, "."); + if (Configs.count(newConfig + ":" + configName.str()) == 1) + return true; + } + // No parent package had such a config, so we can assume the option doesn't + // exist in this registry. + return false; +} + +void CheckerRegistry::initializeManager( + CheckerManager &checkerMgr, SmallVectorImpl &opts) const { // Sort checkers for efficient collection. std::sort(Checkers.begin(), Checkers.end(), checkerNameLT); @@ -118,8 +157,13 @@ DiagnosticsEngine &diags) const { for (auto &config : opts.Config) { size_t pos = config.getKey().find(':'); - if (pos == StringRef::npos) + if (pos == StringRef::npos) { + // It's a global config, so no checker configuration required, but we + // still need to verify if the config is valid. + if (!hasConfig(config.getKey())) + diags.Report(diag::err_unknown_analyzer_config) << config.getKey(); continue; + } bool hasChecker = false; StringRef checkerName = config.getKey().substr(0, pos); @@ -130,7 +174,12 @@ break; } } - if (!hasChecker) { + if (hasChecker) { + // We have such a checker, but we still need to verify if the checker + // actually has the given config value. + if (!hasConfig(config.getKey())) + diags.Report(diag::err_unknown_analyzer_config) << config.getKey(); + } else { diags.Report(diag::err_unknown_analyzer_checker) << checkerName; } } Index: test/Analysis/analyzer-checker-config.c =================================================================== --- test/Analysis/analyzer-checker-config.c +++ test/Analysis/analyzer-checker-config.c @@ -1,12 +1,34 @@ -// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config unix.mallo:Optimistic=true 2>&1 | FileCheck %s -// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config uni:Optimistic=true 2>&1 | FileCheck %s -// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config uni.:Optimistic=true 2>&1 | FileCheck %s -// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config ..:Optimistic=true 2>&1 | FileCheck %s -// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config unix.:Optimistic=true 2>&1 | FileCheck %s -// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config unrelated:Optimistic=true 2>&1 | FileCheck %s +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config unix.mallo:Optimistic=true 2>&1 | FileCheck --check-prefix=NO_ANALYZER %s +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config uni:Optimistic=true 2>&1 | FileCheck --check-prefix=NO_ANALYZER %s +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config uni.:Optimistic=true 2>&1 | FileCheck --check-prefix=NO_ANALYZER %s +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config ..:Optimistic=true 2>&1 | FileCheck --check-prefix=NO_ANALYZER %s +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config unix.:Optimistic=true 2>&1 | FileCheck --check-prefix=NO_ANALYZER %s +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config unrelated:Optimistic=true 2>&1 | FileCheck --check-prefix=NO_ANALYZER %s // RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config unix.Malloc:Optimistic=true +// NO_ANALYZER: error: no analyzer checkers are associated with + +// valid global config: +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config ipa=dynamic-bifurcate +// invalid global config: +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config ipaFunnyTypo=true 2>&1 | FileCheck --check-prefix=NO_CONFIG %s + +// valid package config: +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config nullability:NoDiagnoseCallsToSystemHeaders=true +// package config in wrong package: +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config cplusplus:NoDiagnoseCallsToSystemHeaders=true 2>&1 | FileCheck --check-prefix=NO_CONFIG %s +// invalid package config: +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config unix:OptimisticWithTypoSuffix=true 2>&1 | FileCheck --check-prefix=NO_CONFIG %s + +// valid checker config: +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config alpha.clone.CloneChecker:MinimumCloneComplexity=1 +// invalid checker config: +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config alpha.clone.CloneChecker:MinimumCloneComplexityWithTypo=1 2>&1 | FileCheck --check-prefix=NO_CONFIG %s + +// check that we don't do silly stuff on a trailing ':' +// RUN: not %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-config alpha.clone.CloneChecker:=1 2>&1 | FileCheck --check-prefix=NO_CONFIG %s + +// NO_CONFIG: error: no analyzer configs are associated with + // Just to test clang is working. # foo - -// CHECK: error: Index: utils/TableGen/ClangSACheckersEmitter.cpp =================================================================== --- utils/TableGen/ClangSACheckersEmitter.cpp +++ utils/TableGen/ClangSACheckersEmitter.cpp @@ -94,6 +94,67 @@ addPackageToCheckerGroup(*I, group, recordGroupMap); } +typedef std::map SortedRecords; +typedef llvm::DenseMap RecToSortIndex; + +static void emitConfigs(RecordKeeper &Records, raw_ostream &OS, + const std::vector &checkers, + const std::vector &packages) { + OS << "\n#ifdef GET_CHECKER_CONFIGS\n"; + for (unsigned i = 0, e = checkers.size(); i != e; ++i) { + const Record &R = *checkers[i]; + const std::vector Configs = R.getValueAsListOfDefs("Configs"); + for (Record *Config : Configs) { + OS << "CHECKER_CONFIG(" + << "\""; + std::string name; + if (isCheckerNamed(&R)) + name = getCheckerFullName(&R); + OS.write_escaped(name); + OS << "\", \""; + OS.write_escaped(Config->getValueAsString("ConfigName")); + OS << "\")\n"; + } + } + OS << "#endif // GET_CHECKER_CONFIGS\n\n"; + + OS << "\n#ifdef GET_PACKAGE_CONFIGS\n"; + { + SortedRecords sortedPackages; + for (unsigned i = 0, e = packages.size(); i != e; ++i) + sortedPackages[getPackageFullName(packages[i])] = packages[i]; + + for (SortedRecords::iterator I = sortedPackages.begin(), + E = sortedPackages.end(); + I != E; ++I) { + const Record &R = *I->second; + + const std::vector Configs = R.getValueAsListOfDefs("Configs"); + for (Record *Config : Configs) { + OS << "PACKAGE_CONFIG(" + << "\""; + OS.write_escaped(getPackageFullName(&R)); + OS << "\", \""; + OS.write_escaped(Config->getValueAsString("ConfigName")); + OS << "\")\n"; + } + } + } + OS << "#endif // GET_PACKAGE_CONFIGS\n\n"; + + OS << "\n#ifdef GET_GLOBAL_CONFIGS\n"; + std::vector globalConfigs = + Records.getAllDerivedDefinitions("GlobalConfig"); + for (unsigned i = 0, e = globalConfigs.size(); i != e; ++i) { + Record *R = globalConfigs[i]; + OS << "GLOBAL_CONFIG(" + << "\""; + OS.write_escaped(R->getValueAsString("ConfigName")); + OS << "\")\n"; + } + OS << "#endif // GET_GLOBAL_CONFIGS\n\n"; +} + namespace clang { void EmitClangSACheckers(RecordKeeper &Records, raw_ostream &OS) { std::vector checkers = Records.getAllDerivedDefinitions("Checker"); @@ -169,9 +230,6 @@ if (DefInit *DI = dyn_cast(packages[i]->getValueInit("Group"))) addPackageToCheckerGroup(packages[i], DI->getDef(), recordGroupMap); - typedef std::map SortedRecords; - typedef llvm::DenseMap RecToSortIndex; - SortedRecords sortedGroups; RecToSortIndex groupToSortIndex; OS << "\n#ifdef GET_GROUPS\n"; @@ -248,6 +306,8 @@ } OS << "#endif // GET_CHECKERS\n\n"; + emitConfigs(Records, OS, checkers, packages); + unsigned index = 0; for (std::map::iterator I = groupInfoByName.begin(), E = groupInfoByName.end(); I != E; ++I)