//===- 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/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 CheckerNameLT = FullNameLT; } // end of anonymous namespace 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) { assert(std::is_sorted(Checkers.begin(), Checkers.end(), CheckerNameLT{}) && "In order to efficiently gather checkers, this function expects them " "to be already sorted!"); // Use a binary search to find the possible start of the package. CheckerRegistry::CheckerInfo PackageInfo(nullptr, nullptr, CmdLineArg, "", ""); auto It = std::lower_bound(Checkers.begin(), Checkers.end(), PackageInfo, CheckerNameLT{}); 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) \ addChecker(register##CLASS, shouldRegister##CLASS, FULLNAME, HELPTEXT, \ DOC_URI); #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(Checkers, CheckerNameLT{}); #define GET_CHECKER_DEPENDENCIES #define CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY) \ addDependency(FULLNAME, DEPENDENCY); #include "clang/StaticAnalyzer/Checkers/Checkers.inc" #undef CHECKER_DEPENDENCY #undef GET_CHECKER_DEPENDENCIES // 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::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::addDependency(StringRef FullName, StringRef dependency) { auto CheckerThatNeedsDeps = [&FullName](const CheckerInfo &Chk) { return Chk.FullName == FullName; }; auto Dependency = [&dependency](const CheckerInfo &Chk) { return Chk.FullName == dependency; }; auto CheckerIt = llvm::find_if(Checkers, CheckerThatNeedsDeps); assert(CheckerIt != Checkers.end() && "Failed to find the checker while attempting to set up its " "dependencies!"); auto DependencyIt = llvm::find_if(Checkers, Dependency); assert(DependencyIt != Checkers.end() && "Failed to find the dependency of a checker!"); CheckerIt->Dependencies.push_back(&*DependencyIt); } 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); } } void CheckerRegistry::validateCheckerOptions() const { for (const auto &Config : AnOpts.Config) { size_t Pos = Config.getKey().find(':'); if (Pos == StringRef::npos) continue; bool HasChecker = false; StringRef CheckerName = Config.getKey().substr(0, Pos); for (const auto &Checker : Checkers) { if (Checker.FullName.startswith(CheckerName) && (Checker.FullName.size() == Pos || Checker.FullName[Pos] == '.')) { HasChecker = true; break; } } if (!HasChecker) Diags.Report(diag::err_unknown_analyzer_checker) << CheckerName; } } 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; for (const auto &Checker : Checkers) { Out.indent(InitialPad) << Checker.FullName; int Pad = OptionFieldWidth - Checker.FullName.size(); // Break on long option names. if (Pad < 0) { Out << '\n'; Pad = OptionFieldWidth + InitialPad; } Out.indent(Pad + 2) << Checker.Desc; Out << '\n'; } } 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'; }