Index: include/clang/Basic/DiagnosticDriverKinds.td =================================================================== --- include/clang/Basic/DiagnosticDriverKinds.td +++ include/clang/Basic/DiagnosticDriverKinds.td @@ -178,6 +178,8 @@ "analyzer-config option '%0' has a key but no value">; def err_analyzer_config_multiple_values : Error< "analyzer-config option '%0' should contain only one '='">; +def err_analyzer_config_invalid_key : Error< + "analyzer-config key '%0' should have '(id.)*id:id' format">; def err_drv_modules_validate_once_requires_timestamp : Error< "option '-fmodules-validate-once-per-build-session' requires " Index: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h =================================================================== --- include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -28,6 +28,10 @@ class Preprocessor; class LangOptions; +namespace ento { +class CheckerBase; +} + /// Analysis - Set of available source code analyses. enum Analyses { #define ANALYSIS(NAME, CMDFLAG, DESC, SCOPE) NAME, @@ -176,6 +180,15 @@ /// \brief The mode of function selection used during inlining. AnalysisInliningMode InliningMode; + /// \brief Describes the kind of the option. + enum class OptionKind { + Global, + Checker, + CheckerInherited, + Package, + PackageInherited + }; + private: /// \brief Describes the kinds for high-level analyzer mode. enum UserModeKind { @@ -252,17 +265,80 @@ /// \sa getMaxNodesPerTopLevelFunction Optional MaxNodesPerTopLevelFunction; + /// A helper function that retrieves option for a given full-qualified + /// checker name. + /// Options for checkers can be specified via 'analyzer-config' command-line + /// option. + /// Example: + /// @code-analyzer-config unix.Malloc:OptionName=CheckerOptionValue @endcode + /// or @code-analyzer-config unix:OptionName=GroupOptionValue @endcode + /// for groups of checkers + /// @param [in] CheckerName Full-qualified checker name, like + /// alpha.unix.StreamChecker + /// @param [in] OptionName Name of the option to get + /// @param [in] Default Default value if no option is specified + /// @param [in] Kind This enum determines wether the option corresponds to the + /// static analyzer, the checker or a package. It also determines wether to + /// use inheritance. + /// @retval CheckerOptionValue An option for a checker if it was specified + /// @retval GroupOptionValue An option for group if it was specified and no + /// checker-specific options were found. The closer group to checker, + /// the more priority it has. For example, @c coregroup.subgroup has more + /// priority than @c coregroup for @c coregroup.subgroup.CheckerName checker + /// @retval Default If nor checker option, nor group option was found + StringRef getCheckerOption(StringRef CheckerName, StringRef OptionName, + StringRef Default, + OptionKind Kind = OptionKind::Global); + public: /// Interprets an option's string value as a boolean. /// /// Accepts the strings "true" and "false". /// If an option value is not provided, returns the given \p DefaultVal. + /// @param [in] Name Name for option to retrieve + /// @param [in] DefaultVal Default value returned if no such option was + /// specified + /// @param [in] C Optional parameter that may be used to retrieve + /// checker-related option for a given checker + /// @param [in] Kind This enum determines wether the option corresponds to the + /// static analyzer, the checker or a package. It also determines wether to + /// use inheritance. + bool getBooleanOption(StringRef Name, bool DefaultVal, + const ento::CheckerBase *C, + OptionKind Kind = OptionKind::Checker); bool getBooleanOption(StringRef Name, bool DefaultVal); /// Variant that accepts a Optional value to cache the result. + /// + /// @param [in,out] V Return value storage, returned if parameter contains + /// an existing valid option, else it is used to store a return value + /// @param [in] Name Name for option to retrieve + /// @param [in] DefaultVal Default value returned if no such option was + /// specified + /// @param [in] C Optional parameter that may be used to retrieve + /// checker-related option for a given checker + /// @param [in] Kind This enum determines wether the option corresponds to the + /// static analyzer, the checker or a package. It also determines wether to + /// use inheritance. + bool getBooleanOption(Optional &V, StringRef Name, bool DefaultVal, + const ento::CheckerBase *C, + OptionKind Kind = OptionKind::Checker); bool getBooleanOption(Optional &V, StringRef Name, bool DefaultVal); /// Interprets an option's string value as an integer value. + /// + /// If an option value is not provided, returns the given \p DefaultVal. + /// @param [in] Name Name for option to retrieve + /// @param [in] DefaultVal Default value returned if no such option was + /// specified + /// @param [in] C Optional parameter that may be used to retrieve + /// checker-related option for a given checker + /// @param [in] Kind This enum determines wether the option corresponds to the + /// static analyzer, the checker or a package. It also determines wether to + /// use inheritance. + int getOptionAsInteger(StringRef Name, int DefaultVal, + const ento::CheckerBase *C, + OptionKind Kind = OptionKind::Checker); int getOptionAsInteger(StringRef Name, int DefaultVal); /// \brief Retrieves and sets the UserMode. This is a high-level option, Index: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -35,6 +35,7 @@ #include "llvm/Support/Host.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" +#include "llvm/Support/Regex.h" #include #include #include @@ -131,6 +132,12 @@ } } +static bool IsValidCheckerOption(StringRef Key) { + llvm::Regex Re("^([_a-z][_a-z0-9]*\\.)*[_a-z][_a-z0-9]*:[_a-z][_a-z0-9]*$", + llvm::Regex::IgnoreCase); + return Re.match(Key); +} + static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args, DiagnosticsEngine &Diags) { using namespace options; @@ -280,6 +287,11 @@ Success = false; break; } + // Validate checker and package specific arguments. + if (key.find(':') != StringRef::npos && !IsValidCheckerOption(key)) { + Diags.Report(SourceLocation(), diag::err_analyzer_config_invalid_key) + << key; + } Opts.Config[key] = val; } } Index: lib/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- lib/StaticAnalyzer/Checkers/Checkers.td +++ lib/StaticAnalyzer/Checkers/Checkers.td @@ -295,7 +295,7 @@ HelpText<"Check calls to various UNIX/Posix functions">, DescFile<"UnixAPIChecker.cpp">; -def MallocPessimistic : Checker<"Malloc">, +def MallocChecker: Checker<"Malloc">, HelpText<"Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free().">, DescFile<"MallocChecker.cpp">; @@ -315,10 +315,6 @@ HelpText<"Check improper use of chroot">, DescFile<"ChrootChecker.cpp">; -def MallocOptimistic : Checker<"MallocWithAnnotations">, - HelpText<"Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free(). Assumes that all user-defined functions which might free a pointer are annotated.">, - DescFile<"MallocChecker.cpp">; - def PthreadLockChecker : Checker<"PthreadLock">, HelpText<"Simple lock -> unlock checker">, DescFile<"PthreadLockChecker.cpp">; Index: lib/StaticAnalyzer/Checkers/MallocChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -170,8 +170,7 @@ /// In pessimistic mode, the checker assumes that it does not know which /// functions might free the memory. enum CheckKind { - CK_MallocPessimistic, - CK_MallocOptimistic, + CK_MallocChecker, CK_NewDeleteChecker, CK_NewDeleteLeaksChecker, CK_MismatchedDeallocatorChecker, @@ -184,6 +183,8 @@ MOK_Any }; + DefaultBool IsOptimistic; + DefaultBool ChecksEnabled[CK_NumCheckKinds]; CheckName CheckNames[CK_NumCheckKinds]; typedef llvm::SmallVector CKVecTy; @@ -585,7 +586,7 @@ if (Family != AF_Malloc) return false; - if (ChecksEnabled[CK_MallocOptimistic] && FD->hasAttrs()) { + if (IsOptimistic && FD->hasAttrs()) { for (const auto *I : FD->specific_attrs()) { OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind(); if(OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) { @@ -792,8 +793,7 @@ } } - if (ChecksEnabled[CK_MallocOptimistic] || - ChecksEnabled[CK_MismatchedDeallocatorChecker]) { + if (IsOptimistic || ChecksEnabled[CK_MismatchedDeallocatorChecker]) { // Check all the attributes, if there are any. // There can be multiple of these attributes. if (FD->hasAttrs()) @@ -1358,8 +1358,7 @@ case AF_IfNameIndex: case AF_Alloca: { // C checkers. - if (CK == CK_MallocOptimistic || - CK == CK_MallocPessimistic) { + if (CK == CK_MallocChecker) { return CK; } return Optional(); @@ -1512,8 +1511,7 @@ SourceRange Range, const Expr *DeallocExpr) const { - auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocOptimistic, - CK_MallocPessimistic, + auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocChecker, CK_NewDeleteChecker), C, DeallocExpr); if (!CheckKind.hasValue()) @@ -1555,8 +1553,7 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const { - auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocOptimistic, - CK_MallocPessimistic, + auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocChecker, CK_MismatchedDeallocatorChecker), AF_Alloca); if (!CheckKind.hasValue()) @@ -1635,8 +1632,7 @@ const Expr *AllocExpr) const { - auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocOptimistic, - CK_MallocPessimistic, + auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocChecker, CK_NewDeleteChecker), C, AllocExpr); if (!CheckKind.hasValue()) @@ -1688,8 +1684,7 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, SymbolRef Sym) const { - auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocOptimistic, - CK_MallocPessimistic, + auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocChecker, CK_NewDeleteChecker), C, Sym); if (!CheckKind.hasValue()) @@ -1714,8 +1709,7 @@ bool Released, SymbolRef Sym, SymbolRef PrevSym) const { - auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocOptimistic, - CK_MallocPessimistic, + auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocChecker, CK_NewDeleteChecker), C, Sym); if (!CheckKind.hasValue()) @@ -1930,8 +1924,7 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const { - auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocOptimistic, - CK_MallocPessimistic, + auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocChecker, CK_NewDeleteLeaksChecker), C, Sym); if (!CheckKind.hasValue()) @@ -2054,8 +2047,7 @@ return; ASTContext &Ctx = C.getASTContext(); - if ((ChecksEnabled[CK_MallocOptimistic] || - ChecksEnabled[CK_MallocPessimistic]) && + if (ChecksEnabled[CK_MallocChecker] && (isCMemFunction(FD, Ctx, AF_Malloc, MemoryOperationKind::MOK_Free) || isCMemFunction(FD, Ctx, AF_IfNameIndex, MemoryOperationKind::MOK_Free))) @@ -2547,8 +2539,7 @@ for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) { const RefState *RefS = State->get(I.getKey()); AllocationFamily Family = RefS->getAllocationFamily(); - auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocOptimistic, - CK_MallocPessimistic, + auto CheckKind = getCheckIfTracked(MakeVecFromCK(CK_MallocChecker, CK_NewDeleteChecker), Family); I.getKey()->dumpToStream(Out); @@ -2564,6 +2555,8 @@ void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) { registerCStringCheckerBasic(mgr); MallocChecker *checker = mgr.registerChecker(); + checker->IsOptimistic = mgr.getAnalyzerOptions().getBooleanOption( + "Optimistic", false, checker); checker->ChecksEnabled[MallocChecker::CK_NewDeleteLeaksChecker] = true; checker->CheckNames[MallocChecker::CK_NewDeleteLeaksChecker] = mgr.getCurrentCheckName(); @@ -2577,11 +2570,12 @@ void ento::register##name(CheckerManager &mgr) { \ registerCStringCheckerBasic(mgr); \ MallocChecker *checker = mgr.registerChecker(); \ + checker->IsOptimistic = mgr.getAnalyzerOptions().getBooleanOption( \ + "Optimistic", false, checker); \ checker->ChecksEnabled[MallocChecker::CK_##name] = true; \ checker->CheckNames[MallocChecker::CK_##name] = mgr.getCurrentCheckName(); \ } -REGISTER_CHECKER(MallocPessimistic) -REGISTER_CHECKER(MallocOptimistic) +REGISTER_CHECKER(MallocChecker) REGISTER_CHECKER(NewDeleteChecker) REGISTER_CHECKER(MismatchedDeallocatorChecker) Index: lib/StaticAnalyzer/Core/AnalyzerOptions.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "clang/StaticAnalyzer/Core/Checker.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ErrorHandling.h" @@ -19,6 +20,7 @@ #include "llvm/Support/raw_ostream.h" using namespace clang; +using namespace ento; using namespace llvm; AnalyzerOptions::UserModeKind AnalyzerOptions::getUserMode() { @@ -100,6 +102,37 @@ static StringRef toString(bool b) { return b ? "true" : "false"; } +StringRef AnalyzerOptions::getCheckerOption(StringRef CheckerName, + StringRef OptionName, + StringRef Default, + OptionKind Kind) { + if (Kind == OptionKind::Global) { + return Config.insert(std::make_pair(OptionName, Default)).first->second; + } + + if (Kind == OptionKind::Package || Kind == OptionKind::PackageInherited) { + size_t Pos = CheckerName.rfind('.'); + assert(Pos != StringRef::npos && "Checker without package."); + CheckerName = CheckerName.substr(0, Pos); + } + + // Search for a category option if option for checker is not specified and + // inheritance is enabled. + ConfigTable::const_iterator E = Config.end(); + do { + ConfigTable::const_iterator I = + Config.find((Twine(CheckerName) + ":" + OptionName).str()); + if (I != E) + return StringRef(I->getValue()); + size_t Pos = CheckerName.rfind('.'); + if (Pos == StringRef::npos) + return Default; + CheckerName = CheckerName.substr(0, Pos); + } while (!CheckerName.empty() && (Kind == OptionKind::CheckerInherited || + Kind == OptionKind::PackageInherited)); + return Default; +} + bool AnalyzerOptions::getBooleanOption(StringRef Name, bool DefaultVal) { // FIXME: We should emit a warning here if the value is something other than // "true", "false", or the empty string (meaning the default value), @@ -112,6 +145,17 @@ .Default(DefaultVal); } +bool AnalyzerOptions::getBooleanOption(StringRef Name, bool DefaultVal, + const CheckerBase *C, OptionKind Kind) { + assert(C); + StringRef Default = toString(DefaultVal); + StringRef V = getCheckerOption(C->getTagDescription(), Name, Default, Kind); + return llvm::StringSwitch(V) + .Case("true", true) + .Case("false", false) + .Default(DefaultVal); +} + bool AnalyzerOptions::getBooleanOption(Optional &V, StringRef Name, bool DefaultVal) { if (!V.hasValue()) @@ -119,6 +163,14 @@ return V.getValue(); } +bool AnalyzerOptions::getBooleanOption(Optional &V, StringRef Name, + bool DefaultVal, const CheckerBase *C, + OptionKind Kind) { + if (!V.hasValue()) + V = getBooleanOption(Name, DefaultVal, C, Kind); + return V.getValue(); +} + bool AnalyzerOptions::includeTemporaryDtorsInCFG() { return getBooleanOption(IncludeTemporaryDtorsInCFG, "cfg-temporary-dtors", @@ -205,6 +257,7 @@ OS << DefaultVal; StringRef V = Config.insert(std::make_pair(Name, OS.str())).first->second; + int Res = DefaultVal; bool b = V.getAsInteger(10, Res); assert(!b && "analyzer-config option should be numeric"); @@ -212,6 +265,22 @@ return Res; } +int AnalyzerOptions::getOptionAsInteger(StringRef Name, int DefaultVal, + const CheckerBase *C, OptionKind Kind) { + assert(C); + SmallString<10> StrBuf; + llvm::raw_svector_ostream OS(StrBuf); + OS << DefaultVal; + + StringRef V = getCheckerOption(C->getTagDescription(), Name, OS.str(), Kind); + + int Res = DefaultVal; + bool b = V.getAsInteger(10, Res); + assert(!b && "analyzer-config option should be numeric"); + (void)b; + return Res; +} + unsigned AnalyzerOptions::getAlwaysInlineSize() { if (!AlwaysInlineSize.hasValue()) AlwaysInlineSize = getOptionAsInteger("ipa-always-inline-size", 3); @@ -281,4 +350,3 @@ bool AnalyzerOptions::shouldConditionalizeStaticInitializers() { return getBooleanOption("cfg-conditional-static-initializers", true); } - Index: test/Analysis/free.c =================================================================== --- test/Analysis/free.c +++ test/Analysis/free.c @@ -1,5 +1,5 @@ // RUN: %clang_cc1 -analyze -analyzer-store=region -analyzer-checker=core,unix.Malloc -fblocks -verify %s -// RUN: %clang_cc1 -analyze -analyzer-store=region -analyzer-checker=core,alpha.unix.MallocWithAnnotations -fblocks -verify %s +// RUN: %clang_cc1 -analyze -analyzer-store=region -analyzer-checker=core,unix.Malloc -fblocks -verify -analyzer-config unix.Malloc:Optimistic=true %s typedef __typeof(sizeof(int)) size_t; void free(void *); void *alloca(size_t); Index: test/Analysis/malloc-annotations.c =================================================================== --- test/Analysis/malloc-annotations.c +++ test/Analysis/malloc-annotations.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.deadcode.UnreachableCode,alpha.core.CastSize,alpha.unix.MallocWithAnnotations -analyzer-store=region -verify %s +// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.deadcode.UnreachableCode,alpha.core.CastSize,unix.Malloc -analyzer-store=region -verify -analyzer-config unix.Malloc:Optimistic=true %s typedef __typeof(sizeof(int)) size_t; void *malloc(size_t); void free(void *); Index: test/Analysis/outofbound.c =================================================================== --- test/Analysis/outofbound.c +++ test/Analysis/outofbound.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -Wno-array-bounds -analyze -analyzer-checker=core,alpha.unix,alpha.security.ArrayBound -analyzer-store=region -verify %s +// RUN: %clang_cc1 -Wno-array-bounds -analyze -analyzer-checker=core,unix,alpha.security.ArrayBound -analyzer-store=region -verify -analyzer-config unix:Optimistic=true %s typedef __typeof(sizeof(int)) size_t; void *malloc(size_t); Index: test/Analysis/undef-buffers.c =================================================================== --- test/Analysis/undef-buffers.c +++ test/Analysis/undef-buffers.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.unix,core.uninitialized -analyzer-store=region -verify %s +// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix,core.uninitialized -analyzer-store=region -verify -analyzer-config unix:Optimistic=true %s typedef __typeof(sizeof(int)) size_t; void *malloc(size_t); void free(void *);