//===- CheckerRegistry.cpp - Maintains all available checkers -------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/LLVM.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include using namespace clang; using namespace ento; using llvm::sys::DynamicLibrary; using RegisterCheckersFn = void (*)(CheckerRegistry &); static bool isCompatibleAPIVersion(const char *VersionString) { // If the version string is null, its not an analyzer plugin. if (!VersionString) return false; // For now, none of the static analyzer API is considered stable. // Versions must match exactly. return strcmp(VersionString, CLANG_ANALYZER_API_VERSION_STRING) == 0; } namespace { template struct FullNameLT { bool operator()(const T &Lhs, const T &Rhs) { return Lhs.FullName < Rhs.FullName; } }; using PackageNameLT = FullNameLT; using CheckerNameLT = FullNameLT; } // end of anonymous namespace template static typename std::conditional::value, typename CheckerOrPackageInfoList::const_iterator, typename CheckerOrPackageInfoList::iterator>::type binaryFind(CheckerOrPackageInfoList &Collection, StringRef FullName) { using CheckerOrPackage = typename CheckerOrPackageInfoList::value_type; using CheckerOrPackageFullNameLT = FullNameLT; assert(std::is_sorted(Collection.begin(), Collection.end(), CheckerOrPackageFullNameLT{}) && "In order to efficiently gather checkers/packages, this function " "expects them to be already sorted!"); return llvm::lower_bound(Collection, CheckerOrPackage(FullName), CheckerOrPackageFullNameLT{}); } static constexpr char PackageSeparator = '.'; static bool isInPackage(const CheckerRegistry::CheckerInfo &Checker, StringRef PackageName) { // Does the checker's full name have the package as a prefix? if (!Checker.FullName.startswith(PackageName)) return false; // Is the package actually just the name of a specific checker? if (Checker.FullName.size() == PackageName.size()) return true; // Is the checker in the package (or a subpackage)? if (Checker.FullName[PackageName.size()] == PackageSeparator) return true; return false; } CheckerRegistry::CheckerInfoListRange CheckerRegistry::getMutableCheckersForCmdLineArg(StringRef CmdLineArg) { auto It = binaryFind(Checkers, CmdLineArg); if (!isInPackage(*It, CmdLineArg)) return {Checkers.end(), Checkers.end()}; // See how large the package is. // If the package doesn't exist, assume the option refers to a single // checker. size_t Size = 1; llvm::StringMap::const_iterator PackageSize = PackageSizes.find(CmdLineArg); if (PackageSize != PackageSizes.end()) Size = PackageSize->getValue(); return {It, It + Size}; } CheckerRegistry::CheckerRegistry( ArrayRef Plugins, DiagnosticsEngine &Diags, AnalyzerOptions &AnOpts, const LangOptions &LangOpts, ArrayRef> CheckerRegistrationFns) : Diags(Diags), AnOpts(AnOpts), LangOpts(LangOpts) { // Register builtin checkers. #define GET_CHECKERS #define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ addChecker(register##CLASS, shouldRegister##CLASS, FULLNAME, HELPTEXT, \ DOC_URI, IS_HIDDEN); #define GET_PACKAGES #define PACKAGE(FULLNAME) addPackage(FULLNAME); #include "clang/StaticAnalyzer/Checkers/Checkers.inc" #undef CHECKER #undef GET_CHECKERS #undef PACKAGE #undef GET_PACKAGES // Register checkers from plugins. for (const std::string &Plugin : Plugins) { // Get access to the plugin. std::string ErrorMsg; DynamicLibrary Lib = DynamicLibrary::getPermanentLibrary(Plugin.c_str(), &ErrorMsg); if (!Lib.isValid()) { Diags.Report(diag::err_fe_unable_to_load_plugin) << Plugin << ErrorMsg; continue; } // See if its compatible with this build of clang. const char *PluginAPIVersion = static_cast( Lib.getAddressOfSymbol("clang_analyzerAPIVersionString")); if (!isCompatibleAPIVersion(PluginAPIVersion)) { Diags.Report(diag::warn_incompatible_analyzer_plugin_api) << llvm::sys::path::filename(Plugin); Diags.Report(diag::note_incompatible_analyzer_plugin_api) << CLANG_ANALYZER_API_VERSION_STRING << PluginAPIVersion; continue; } // Register its checkers. RegisterCheckersFn RegisterPluginCheckers = reinterpret_cast( Lib.getAddressOfSymbol("clang_registerCheckers")); if (RegisterPluginCheckers) RegisterPluginCheckers(*this); } // Register statically linked checkers, that aren't generated from the tblgen // file, but rather passed their registry function as a parameter in // checkerRegistrationFns. for (const auto &Fn : CheckerRegistrationFns) Fn(*this); // Sort checkers for efficient collection. // 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 #define CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY) \ addDependency(FULLNAME, DEPENDENCY); #define GET_CHECKER_OPTIONS #define CHECKER_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, DEVELOPMENT_STATUS, IS_HIDDEN) \ addCheckerOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, DEVELOPMENT_STATUS, IS_HIDDEN); #define GET_PACKAGE_OPTIONS #define PACKAGE_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, DEVELOPMENT_STATUS, IS_HIDDEN) \ addPackageOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, DEVELOPMENT_STATUS, IS_HIDDEN); #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. for (const std::pair &Opt : AnOpts.CheckersControlList) { CheckerInfoListRange CheckerForCmdLineArg = getMutableCheckersForCmdLineArg(Opt.first); if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) { Diags.Report(diag::err_unknown_analyzer_checker) << Opt.first; Diags.Report(diag::note_suggest_disabling_all_checkers); } for (CheckerInfo &checker : CheckerForCmdLineArg) { checker.State = Opt.second ? StateFromCmdLine::State_Enabled : StateFromCmdLine::State_Disabled; } } } /// Collects dependencies in \p ret, returns false on failure. static bool collectDependenciesImpl(const CheckerRegistry::ConstCheckerInfoList &Deps, const LangOptions &LO, CheckerRegistry::CheckerInfoSet &Ret); /// Collects dependenies in \p enabledCheckers. Return None on failure. LLVM_NODISCARD static llvm::Optional collectDependencies(const CheckerRegistry::CheckerInfo &checker, const LangOptions &LO) { CheckerRegistry::CheckerInfoSet Ret; // Add dependencies to the enabled checkers only if all of them can be // enabled. if (!collectDependenciesImpl(checker.Dependencies, LO, Ret)) return None; return Ret; } static bool collectDependenciesImpl(const CheckerRegistry::ConstCheckerInfoList &Deps, const LangOptions &LO, CheckerRegistry::CheckerInfoSet &Ret) { for (const CheckerRegistry::CheckerInfo *Dependency : Deps) { if (Dependency->isDisabled(LO)) return false; // Collect dependencies recursively. if (!collectDependenciesImpl(Dependency->Dependencies, LO, Ret)) return false; Ret.insert(Dependency); } return true; } CheckerRegistry::CheckerInfoSet CheckerRegistry::getEnabledCheckers() const { CheckerInfoSet EnabledCheckers; for (const CheckerInfo &Checker : Checkers) { if (!Checker.isEnabled(LangOpts)) continue; // Recursively enable its dependencies. llvm::Optional Deps = collectDependencies(Checker, LangOpts); if (!Deps) { // If we failed to enable any of the dependencies, don't enable this // checker. continue; } // Note that set_union also preserves the order of insertion. EnabledCheckers.set_union(*Deps); // Enable the checker. EnabledCheckers.insert(&Checker); } return EnabledCheckers; } void CheckerRegistry::resolveDependencies() { for (const std::pair &Entry : Dependencies) { auto CheckerIt = binaryFind(Checkers, Entry.first); assert(CheckerIt != Checkers.end() && CheckerIt->FullName == Entry.first && "Failed to find the checker while attempting to set up its " "dependencies!"); auto DependencyIt = binaryFind(Checkers, Entry.second); assert(DependencyIt != Checkers.end() && DependencyIt->FullName == Entry.second && "Failed to find the dependency of a checker!"); CheckerIt->Dependencies.emplace_back(&*DependencyIt); } Dependencies.clear(); } void CheckerRegistry::addDependency(StringRef FullName, StringRef Dependency) { Dependencies.emplace_back(FullName, Dependency); } /// Insert the checker/package option to AnalyzerOptions' config table, and /// validate it, if the user supplied it on the command line. static void insertAndValidate(StringRef FullName, const CheckerRegistry::CmdLineOption &Option, AnalyzerOptions &AnOpts, DiagnosticsEngine &Diags) { std::string FullOption = (FullName + ":" + Option.OptionName).str(); auto It = AnOpts.Config.insert({FullOption, Option.DefaultValStr}); // Insertation was successful -- CmdLineOption's constructor will validate // whether values received from plugins or TableGen files are correct. if (It.second) return; // Insertion failed, the user supplied this package/checker option on the // command line. If the supplied value is invalid, we'll restore the option // to it's default value, and if we're in non-compatibility mode, we'll also // emit an error. StringRef SuppliedValue = It.first->getValue(); if (Option.OptionType == "bool") { if (SuppliedValue != "true" && SuppliedValue != "false") { if (AnOpts.ShouldEmitErrorsOnInvalidConfigValue) { Diags.Report(diag::err_analyzer_checker_option_invalid_input) << FullOption << "a boolean value"; } It.first->setValue(Option.DefaultValStr); } return; } if (Option.OptionType == "int") { int Tmp; bool HasFailed = SuppliedValue.getAsInteger(0, Tmp); if (HasFailed) { if (AnOpts.ShouldEmitErrorsOnInvalidConfigValue) { Diags.Report(diag::err_analyzer_checker_option_invalid_input) << FullOption << "an integer value"; } It.first->setValue(Option.DefaultValStr); } return; } } template static void insertOptionToCollection(StringRef FullName, T &Collection, const CheckerRegistry::CmdLineOption &Option, AnalyzerOptions &AnOpts, DiagnosticsEngine &Diags) { auto It = binaryFind(Collection, FullName); assert(It != Collection.end() && "Failed to find the checker while attempting to add a command line " "option to it!"); insertAndValidate(FullName, Option, AnOpts, Diags); It->CmdLineOptions.emplace_back(Option); } void CheckerRegistry::resolveCheckerAndPackageOptions() { for (const std::pair &CheckerOptEntry : CheckerOptions) { insertOptionToCollection(CheckerOptEntry.first, Checkers, CheckerOptEntry.second, AnOpts, Diags); } CheckerOptions.clear(); for (const std::pair &PackageOptEntry : PackageOptions) { insertOptionToCollection(PackageOptEntry.first, Packages, PackageOptEntry.second, AnOpts, Diags); } 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, StringRef DevelopmentStatus, bool IsHidden) { PackageOptions.emplace_back( PackageFullName, CmdLineOption{OptionType, OptionName, DefaultValStr, Description, DevelopmentStatus, IsHidden}); } void CheckerRegistry::addChecker(InitializationFunction Rfn, ShouldRegisterFunction Sfn, StringRef Name, StringRef Desc, StringRef DocsUri, bool IsHidden) { Checkers.emplace_back(Rfn, Sfn, Name, Desc, DocsUri, IsHidden); // 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, StringRef DevelopmentStatus, bool IsHidden) { CheckerOptions.emplace_back( CheckerFullName, CmdLineOption{OptionType, OptionName, DefaultValStr, Description, DevelopmentStatus, IsHidden}); } void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const { // Collect checkers enabled by the options. CheckerInfoSet enabledCheckers = getEnabledCheckers(); // Initialize the CheckerManager with all enabled checkers. for (const auto *Checker : enabledCheckers) { CheckerMgr.setCurrentCheckName(CheckName(Checker->FullName)); Checker->Initialize(CheckerMgr); } } static void isOptionContainedIn(const CheckerRegistry::CmdLineOptionList &OptionList, StringRef SuppliedChecker, StringRef SuppliedOption, const AnalyzerOptions &AnOpts, DiagnosticsEngine &Diags) { if (!AnOpts.ShouldEmitErrorsOnInvalidConfigValue) return; using CmdLineOption = CheckerRegistry::CmdLineOption; auto SameOptName = [SuppliedOption](const CmdLineOption &Opt) { return Opt.OptionName == SuppliedOption; }; auto OptionIt = llvm::find_if(OptionList, SameOptName); if (OptionIt == OptionList.end()) { Diags.Report(diag::err_analyzer_checker_option_unknown) << SuppliedChecker << SuppliedOption; return; } } void CheckerRegistry::validateCheckerOptions() const { for (const auto &Config : AnOpts.Config) { StringRef SuppliedChecker; StringRef SuppliedOption; std::tie(SuppliedChecker, SuppliedOption) = Config.getKey().split(':'); if (SuppliedOption.empty()) continue; // AnalyzerOptions' config table contains the user input, so an entry could // look like this: // // cor:NoFalsePositives=true // // 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)); if (CheckerIt != Checkers.end()) { isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedChecker, SuppliedOption, AnOpts, Diags); continue; } auto PackageIt = llvm::find(Packages, PackageInfo(SuppliedChecker)); if (PackageIt != Packages.end()) { isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedChecker, SuppliedOption, AnOpts, Diags); continue; } Diags.Report(diag::err_unknown_analyzer_checker) << SuppliedChecker; } } void CheckerRegistry::printCheckerWithDescList(raw_ostream &Out, size_t MaxNameChars) const { // FIXME: Print available packages. Out << "CHECKERS:\n"; // Find the maximum option length. size_t OptionFieldWidth = 0; for (const auto &Checker : Checkers) { // Limit the amount of padding we are willing to give up for alignment. // Package.Name Description [Hidden] size_t NameLength = Checker.FullName.size(); if (NameLength <= MaxNameChars) OptionFieldWidth = std::max(OptionFieldWidth, NameLength); } const size_t InitialPad = 2; auto Print = [=](llvm::raw_ostream &Out, const CheckerInfo &Checker, StringRef Description) { AnalyzerOptions::printFormattedEntry(Out, {Checker.FullName, Description}, InitialPad, OptionFieldWidth); Out << '\n'; }; for (const auto &Checker : Checkers) { // The order of this if branches is significant, we wouldn't like to display // developer checkers even in the alpha output. For example, // alpha.cplusplus.IteratorModeling is a modeling checker, hence it's hidden // by default, and users (even when the user is a developer of an alpha // checker) shouldn't normally tinker with whether they should be enabled. if (Checker.IsHidden) { if (AnOpts.ShowCheckerHelpDeveloper) Print(Out, Checker, Checker.Desc); continue; } if (Checker.FullName.startswith("alpha")) { if (AnOpts.ShowCheckerHelpAlpha) Print(Out, Checker, ("(Enable only for development!) " + Checker.Desc).str()); continue; } if (AnOpts.ShowCheckerHelp) Print(Out, Checker, Checker.Desc); } } void CheckerRegistry::printEnabledCheckerList(raw_ostream &Out) const { // Collect checkers enabled by the options. CheckerInfoSet EnabledCheckers = getEnabledCheckers(); for (const auto *i : EnabledCheckers) Out << i->FullName << '\n'; } void CheckerRegistry::printCheckerOptionList(raw_ostream &Out) const { Out << "OVERVIEW: Clang Static Analyzer Checker and Package Option List\n\n"; Out << "USAGE: -analyzer-config \n\n"; Out << " -analyzer-config OPTION1=VALUE, -analyzer-config " "OPTION2=VALUE, ...\n\n"; Out << "OPTIONS:\n\n"; std::multimap OptionMap; for (const CheckerInfo &Checker : Checkers) { for (const CmdLineOption &Option : Checker.CmdLineOptions) { OptionMap.insert({Checker.FullName, Option}); } } for (const PackageInfo &Package : Packages) { for (const CmdLineOption &Option : Package.CmdLineOptions) { OptionMap.insert({Package.FullName, Option}); } } auto Print = [] (llvm::raw_ostream &Out, StringRef FullOption, StringRef Desc) { AnalyzerOptions::printFormattedEntry(Out, {FullOption, Desc}, /*InitialPad*/ 2, /*EntryWidth*/ 50, /*MinLineWidth*/ 90); Out << "\n\n"; }; for (const std::pair &Entry : OptionMap) { const CmdLineOption &Option = Entry.second; std::string FullOption = (Entry.first + ":" + Option.OptionName).str(); std::string Desc = ("(" + Option.OptionType + ") " + Option.Description + " (default: " + (Option.DefaultValStr.empty() ? "\"\"" : Option.DefaultValStr) + ")") .str(); // The list of these if branches is significant, we wouldn't like to // display hidden alpha checker options for // -analyzer-checker-option-help-alpha. if (Option.IsHidden) { if (AnOpts.ShowCheckerOptionDeveloperList) Print(Out, FullOption, Desc); continue; } if (Option.DevelopmentStatus == "alpha" || Entry.first.startswith("alpha")) { if (AnOpts.ShowCheckerOptionAlphaList) Print(Out, FullOption, llvm::Twine("(Enable only for development!) " + Desc).str()); continue; } if (AnOpts.ShowCheckerOptionList) Print(Out, FullOption, Desc); } }