//===- 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/CheckerManager.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.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, it's 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 bool checkerNameLT(const CheckerRegistry::CheckerInfo &a, const CheckerRegistry::CheckerInfo &b) { return a.FullName < b.FullName; } 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 = Packages.find(CmdLineArg); if (packageSize != Packages.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 // Register checkers from plugins. for (ArrayRef::iterator i = plugins.begin(), e = plugins.end(); i != e; ++i) { // Get access to the plugin. std::string err; DynamicLibrary lib = DynamicLibrary::getPermanentLibrary(i->c_str(), &err); if (!lib.isValid()) { diags.Report(diag::err_fe_unable_to_load_plugin) << *i << err; continue; } // See if it's compatible with this build of clang. const char *pluginAPIVersion = (const char *) lib.getAddressOfSymbol("clang_analyzerAPIVersionString"); if (!isCompatibleAPIVersion(pluginAPIVersion)) { Diags.Report(diag::warn_incompatible_analyzer_plugin_api) << llvm::sys::path::filename(*i); Diags.Report(diag::note_incompatible_analyzer_plugin_api) << CLANG_ANALYZER_API_VERSION_STRING << pluginAPIVersion; continue; } // Register its checkers. RegisterCheckersFn registerPluginCheckers = (RegisterCheckersFn) (intptr_t) 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 checkersForCmdLineArg = getMutableCheckersForCmdLineArg(opt.first); if (checkersForCmdLineArg.begin() == checkersForCmdLineArg.end()) { Diags.Report(diag::err_unknown_analyzer_checker) << opt.first; Diags.Report(diag::note_suggest_disabling_all_checkers); } for (CheckerInfo &checker : checkersForCmdLineArg) { 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 it's 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()) { Packages[packageName] += 1; std::tie(packageName, leafName) = packageName.rsplit(PackageSeparator); } } 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 *i : enabledCheckers) { checkerMgr.setCurrentCheckName(CheckName(i->FullName)); i->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::printHelp(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 &i : Checkers) { // Limit the amount of padding we are willing to give up for alignment. // Package.Name Description [Hidden] size_t nameLength = i.FullName.size(); if (nameLength <= maxNameChars) optionFieldWidth = std::max(optionFieldWidth, nameLength); } const size_t initialPad = 2; for (const auto &i : Checkers) { out.indent(initialPad) << i.FullName; int pad = optionFieldWidth - i.FullName.size(); // Break on long option names. if (pad < 0) { out << '\n'; pad = optionFieldWidth + initialPad; } out.indent(pad + 2) << i.Desc; out << '\n'; } } void CheckerRegistry::printList(raw_ostream &out) const { // Collect checkers enabled by the options. CheckerInfoSet enabledCheckers = getEnabledCheckers(); for (const auto *i : enabledCheckers) out << i->FullName << '\n'; }