//===- 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 namespace checker_registry; using llvm::sys::DynamicLibrary; //===----------------------------------------------------------------------===// // Utilities. //===----------------------------------------------------------------------===// 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; } static constexpr char PackageSeparator = '.'; //===----------------------------------------------------------------------===// // Methods of CheckerRegistry. //===----------------------------------------------------------------------===// CheckerRegistry::CheckerRegistry( CheckerRegistryData &Data, ArrayRef Plugins, DiagnosticsEngine &Diags, AnalyzerOptions &AnOpts, ArrayRef> CheckerRegistrationFns) : Data(Data), Diags(Diags), AnOpts(AnOpts) { // 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; } using RegisterPluginCheckerFn = void (*)(CheckerRegistry &); // Register its checkers. RegisterPluginCheckerFn 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(Data.Packages, checker_registry::PackageNameLT{}); llvm::sort(Data.Checkers, checker_registry::CheckerNameLT{}); #define GET_CHECKER_DEPENDENCIES #define CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY) \ addDependency(FULLNAME, DEPENDENCY); #define GET_CHECKER_WEAK_DEPENDENCIES #define CHECKER_WEAK_DEPENDENCY(FULLNAME, DEPENDENCY) \ addWeakDependency(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_WEAK_DEPENDENCY #undef GET_CHECKER_WEAK_DEPENDENCIES #undef CHECKER_OPTION #undef GET_CHECKER_OPTIONS #undef PACKAGE_OPTION #undef GET_PACKAGE_OPTIONS resolveDependencies(); resolveDependencies(); #ifndef NDEBUG for (auto &DepPair : Data.Dependencies) { for (auto &WeakDepPair : Data.WeakDependencies) { // Some assertions to enforce that strong dependencies are relations in // between purely modeling checkers, and weak dependencies are about // diagnostics. assert(WeakDepPair != DepPair && "A checker cannot strong and weak depend on the same checker!"); assert(WeakDepPair.first != DepPair.second && "A strong dependency mustn't have weak dependencies!"); assert(WeakDepPair.second != DepPair.second && "A strong dependency mustn't be a weak dependency as well!"); } } #endif resolveCheckerAndPackageOptions(); // Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the // command line. for (const std::pair &Opt : AnOpts.CheckersAndPackages) { CheckerInfoListRange CheckerForCmdLineArg = Data.getMutableCheckersForCmdLineArg(Opt.first); if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) { Diags.Report(diag::err_unknown_analyzer_checker_or_package) << Opt.first; Diags.Report(diag::note_suggest_disabling_all_checkers); } for (CheckerInfo &checker : CheckerForCmdLineArg) { checker.State = Opt.second ? StateFromCmdLine::State_Enabled : StateFromCmdLine::State_Disabled; } } validateCheckerOptions(); } //===----------------------------------------------------------------------===// // Dependency resolving. //===----------------------------------------------------------------------===// template static bool collectStrongDependencies(const ConstCheckerInfoList &Deps, const CheckerManager &Mgr, CheckerInfoSet &Ret, IsEnabledFn IsEnabled); /// Collects weak dependencies in \p enabledData.Checkers. template static void collectWeakDependencies(const ConstCheckerInfoList &Deps, const CheckerManager &Mgr, CheckerInfoSet &Ret, IsEnabledFn IsEnabled); void CheckerRegistry::initializeRegistry(const CheckerManager &Mgr) { // First, we calculate the list of enabled checkers as specified by the // invocation. Weak dependencies will not enable their unspecified strong // depenencies, but its only after resolving strong dependencies for all // checkers when we know whether they will be enabled. CheckerInfoSet Tmp; auto IsEnabledFromCmdLine = [&](const CheckerInfo *Checker) { return !Checker->isDisabled(Mgr); }; for (const CheckerInfo &Checker : Data.Checkers) { if (!Checker.isEnabled(Mgr)) continue; CheckerInfoSet Deps; if (!collectStrongDependencies(Checker.Dependencies, Mgr, Deps, IsEnabledFromCmdLine)) { // If we failed to enable any of the dependencies, don't enable this // checker. continue; } Tmp.insert(Deps.begin(), Deps.end()); // Enable the checker. Tmp.insert(&Checker); } // Calculate enabled checkers with the correct registration order. As this is // done recursively, its arguably cheaper, but for sure less error prone to // recalculate from scratch. auto IsEnabled = [&](const CheckerInfo *Checker) { return llvm::is_contained(Tmp, Checker); }; for (const CheckerInfo &Checker : Data.Checkers) { if (!Checker.isEnabled(Mgr)) continue; CheckerInfoSet Deps; collectWeakDependencies(Checker.WeakDependencies, Mgr, Deps, IsEnabled); if (!collectStrongDependencies(Checker.Dependencies, Mgr, Deps, IsEnabledFromCmdLine)) { // 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. Data.EnabledCheckers.set_union(Deps); Data.EnabledCheckers.insert(&Checker); } } template static bool collectStrongDependencies(const ConstCheckerInfoList &Deps, const CheckerManager &Mgr, CheckerInfoSet &Ret, IsEnabledFn IsEnabled) { for (const CheckerInfo *Dependency : Deps) { if (!IsEnabled(Dependency)) return false; // Collect dependencies recursively. if (!collectStrongDependencies(Dependency->Dependencies, Mgr, Ret, IsEnabled)) return false; Ret.insert(Dependency); } return true; } template static void collectWeakDependencies(const ConstCheckerInfoList &WeakDeps, const CheckerManager &Mgr, CheckerInfoSet &Ret, IsEnabledFn IsEnabled) { for (const CheckerInfo *Dependency : WeakDeps) { // Don't enable this checker if strong dependencies are unsatisfied, but // assume that weak dependencies are transitive. collectWeakDependencies(Dependency->WeakDependencies, Mgr, Ret, IsEnabled); if (IsEnabled(Dependency) && collectStrongDependencies(Dependency->Dependencies, Mgr, Ret, IsEnabled)) Ret.insert(Dependency); } } template void CheckerRegistry::resolveDependencies() { for (const std::pair &Entry : (IsWeak ? Data.WeakDependencies : Data.Dependencies)) { auto CheckerIt = binaryFind(Data.Checkers, Entry.first); assert(CheckerIt != Data.Checkers.end() && CheckerIt->FullName == Entry.first && "Failed to find the checker while attempting to set up its " "dependencies!"); auto DependencyIt = binaryFind(Data.Checkers, Entry.second); assert(DependencyIt != Data.Checkers.end() && DependencyIt->FullName == Entry.second && "Failed to find the dependency of a checker!"); // We do allow diagnostics from unit test/example dependency checkers. assert((DependencyIt->FullName.startswith("test") || DependencyIt->FullName.startswith("example") || IsWeak || DependencyIt->IsHidden) && "Strong dependencies are modeling checkers, and as such " "non-user facing! Mark them hidden in Checkers.td!"); if (IsWeak) CheckerIt->WeakDependencies.emplace_back(&*DependencyIt); else CheckerIt->Dependencies.emplace_back(&*DependencyIt); } } void CheckerRegistry::addDependency(StringRef FullName, StringRef Dependency) { Data.Dependencies.emplace_back(FullName, Dependency); } void CheckerRegistry::addWeakDependency(StringRef FullName, StringRef Dependency) { Data.WeakDependencies.emplace_back(FullName, Dependency); } //===----------------------------------------------------------------------===// // Checker option resolving and validating. //===----------------------------------------------------------------------===// /// 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 CmdLineOption &Option, AnalyzerOptions &AnOpts, DiagnosticsEngine &Diags) { std::string FullOption = (FullName + ":" + Option.OptionName).str(); auto It = AnOpts.Config.insert({FullOption, std::string(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(std::string(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(std::string(Option.DefaultValStr)); } return; } } template static void insertOptionToCollection(StringRef FullName, T &Collection, const 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 : Data.CheckerOptions) { insertOptionToCollection(CheckerOptEntry.first, Data.Checkers, CheckerOptEntry.second, AnOpts, Diags); } for (const std::pair &PackageOptEntry : Data.PackageOptions) { insertOptionToCollection(PackageOptEntry.first, Data.Packages, PackageOptEntry.second, AnOpts, Diags); } } void CheckerRegistry::addPackage(StringRef FullName) { Data.Packages.emplace_back(PackageInfo(FullName)); } void CheckerRegistry::addPackageOption(StringRef OptionType, StringRef PackageFullName, StringRef OptionName, StringRef DefaultValStr, StringRef Description, StringRef DevelopmentStatus, bool IsHidden) { Data.PackageOptions.emplace_back( PackageFullName, CmdLineOption{OptionType, OptionName, DefaultValStr, Description, DevelopmentStatus, IsHidden}); } void CheckerRegistry::addChecker(RegisterCheckerFn Rfn, ShouldRegisterFunction Sfn, StringRef Name, StringRef Desc, StringRef DocsUri, bool IsHidden) { Data.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()) { Data.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) { Data.CheckerOptions.emplace_back( CheckerFullName, CmdLineOption{OptionType, OptionName, DefaultValStr, Description, DevelopmentStatus, IsHidden}); } void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const { // Initialize the CheckerManager with all enabled checkers. for (const auto *Checker : Data.EnabledCheckers) { CheckerMgr.setCurrentCheckerName(CheckerNameRef(Checker->FullName)); Checker->Initialize(CheckerMgr); } } static void isOptionContainedIn(const CmdLineOptionList &OptionList, StringRef SuppliedChecker, StringRef SuppliedOption, const AnalyzerOptions &AnOpts, DiagnosticsEngine &Diags) { if (!AnOpts.ShouldEmitErrorsOnInvalidConfigValue) return; auto SameOptName = [SuppliedOption](const CmdLineOption &Opt) { return Opt.OptionName == SuppliedOption; }; if (llvm::none_of(OptionList, SameOptName)) { Diags.Report(diag::err_analyzer_checker_option_unknown) << SuppliedChecker << SuppliedOption; return; } } void CheckerRegistry::validateCheckerOptions() const { for (const auto &Config : AnOpts.Config) { StringRef SuppliedCheckerOrPackage; StringRef SuppliedOption; std::tie(SuppliedCheckerOrPackage, 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(Data.Checkers, CheckerInfo(SuppliedCheckerOrPackage)); if (CheckerIt != Data.Checkers.end()) { isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedCheckerOrPackage, SuppliedOption, AnOpts, Diags); continue; } const auto *PackageIt = llvm::find(Data.Packages, PackageInfo(SuppliedCheckerOrPackage)); if (PackageIt != Data.Packages.end()) { isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedCheckerOrPackage, SuppliedOption, AnOpts, Diags); continue; } Diags.Report(diag::err_unknown_analyzer_checker_or_package) << SuppliedCheckerOrPackage; } }