Path: blob/main/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp
35266 views
//===- CheckerRegistry.cpp - Maintains all available checkers -------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"9#include "clang/Basic/Diagnostic.h"10#include "clang/Basic/LLVM.h"11#include "clang/Driver/DriverDiagnostic.h"12#include "clang/Frontend/FrontendDiagnostic.h"13#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"14#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"15#include "clang/StaticAnalyzer/Core/CheckerManager.h"16#include "llvm/ADT/STLExtras.h"17#include "llvm/ADT/StringMap.h"18#include "llvm/ADT/StringRef.h"19#include "llvm/Support/DynamicLibrary.h"20#include "llvm/Support/Path.h"21#include "llvm/Support/raw_ostream.h"22#include <algorithm>2324using namespace clang;25using namespace ento;26using namespace checker_registry;27using llvm::sys::DynamicLibrary;2829//===----------------------------------------------------------------------===//30// Utilities.31//===----------------------------------------------------------------------===//3233static bool isCompatibleAPIVersion(const char *VersionString) {34// If the version string is null, its not an analyzer plugin.35if (!VersionString)36return false;3738// For now, none of the static analyzer API is considered stable.39// Versions must match exactly.40return strcmp(VersionString, CLANG_ANALYZER_API_VERSION_STRING) == 0;41}4243static constexpr char PackageSeparator = '.';4445//===----------------------------------------------------------------------===//46// Methods of CheckerRegistry.47//===----------------------------------------------------------------------===//4849CheckerRegistry::CheckerRegistry(50CheckerRegistryData &Data, ArrayRef<std::string> Plugins,51DiagnosticsEngine &Diags, AnalyzerOptions &AnOpts,52ArrayRef<std::function<void(CheckerRegistry &)>> CheckerRegistrationFns)53: Data(Data), Diags(Diags), AnOpts(AnOpts) {5455// Register builtin checkers.56#define GET_CHECKERS57#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \58addChecker(register##CLASS, shouldRegister##CLASS, FULLNAME, HELPTEXT, \59DOC_URI, IS_HIDDEN);6061#define GET_PACKAGES62#define PACKAGE(FULLNAME) addPackage(FULLNAME);6364#include "clang/StaticAnalyzer/Checkers/Checkers.inc"65#undef CHECKER66#undef GET_CHECKERS67#undef PACKAGE68#undef GET_PACKAGES6970// Register checkers from plugins.71for (const std::string &Plugin : Plugins) {72// Get access to the plugin.73std::string ErrorMsg;74DynamicLibrary Lib =75DynamicLibrary::getPermanentLibrary(Plugin.c_str(), &ErrorMsg);76if (!Lib.isValid()) {77Diags.Report(diag::err_fe_unable_to_load_plugin) << Plugin << ErrorMsg;78continue;79}8081// See if its compatible with this build of clang.82const char *PluginAPIVersion = static_cast<const char *>(83Lib.getAddressOfSymbol("clang_analyzerAPIVersionString"));8485if (!isCompatibleAPIVersion(PluginAPIVersion)) {86Diags.Report(diag::warn_incompatible_analyzer_plugin_api)87<< llvm::sys::path::filename(Plugin);88Diags.Report(diag::note_incompatible_analyzer_plugin_api)89<< CLANG_ANALYZER_API_VERSION_STRING << PluginAPIVersion;90continue;91}9293using RegisterPluginCheckerFn = void (*)(CheckerRegistry &);94// Register its checkers.95RegisterPluginCheckerFn RegisterPluginCheckers =96reinterpret_cast<RegisterPluginCheckerFn>(97Lib.getAddressOfSymbol("clang_registerCheckers"));98if (RegisterPluginCheckers)99RegisterPluginCheckers(*this);100}101102// Register statically linked checkers, that aren't generated from the tblgen103// file, but rather passed their registry function as a parameter in104// checkerRegistrationFns.105106for (const auto &Fn : CheckerRegistrationFns)107Fn(*this);108109// Sort checkers for efficient collection.110// FIXME: Alphabetical sort puts 'experimental' in the middle.111// Would it be better to name it '~experimental' or something else112// that's ASCIIbetically last?113llvm::sort(Data.Packages, checker_registry::PackageNameLT{});114llvm::sort(Data.Checkers, checker_registry::CheckerNameLT{});115116#define GET_CHECKER_DEPENDENCIES117118#define CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY) \119addDependency(FULLNAME, DEPENDENCY);120121#define GET_CHECKER_WEAK_DEPENDENCIES122123#define CHECKER_WEAK_DEPENDENCY(FULLNAME, DEPENDENCY) \124addWeakDependency(FULLNAME, DEPENDENCY);125126#define GET_CHECKER_OPTIONS127#define CHECKER_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, \128DEVELOPMENT_STATUS, IS_HIDDEN) \129addCheckerOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, \130DEVELOPMENT_STATUS, IS_HIDDEN);131132#define GET_PACKAGE_OPTIONS133#define PACKAGE_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, \134DEVELOPMENT_STATUS, IS_HIDDEN) \135addPackageOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, \136DEVELOPMENT_STATUS, IS_HIDDEN);137138#include "clang/StaticAnalyzer/Checkers/Checkers.inc"139#undef CHECKER_DEPENDENCY140#undef GET_CHECKER_DEPENDENCIES141#undef CHECKER_WEAK_DEPENDENCY142#undef GET_CHECKER_WEAK_DEPENDENCIES143#undef CHECKER_OPTION144#undef GET_CHECKER_OPTIONS145#undef PACKAGE_OPTION146#undef GET_PACKAGE_OPTIONS147148resolveDependencies<true>();149resolveDependencies<false>();150151#ifndef NDEBUG152for (auto &DepPair : Data.Dependencies) {153for (auto &WeakDepPair : Data.WeakDependencies) {154// Some assertions to enforce that strong dependencies are relations in155// between purely modeling checkers, and weak dependencies are about156// diagnostics.157assert(WeakDepPair != DepPair &&158"A checker cannot strong and weak depend on the same checker!");159assert(WeakDepPair.first != DepPair.second &&160"A strong dependency mustn't have weak dependencies!");161assert(WeakDepPair.second != DepPair.second &&162"A strong dependency mustn't be a weak dependency as well!");163}164}165#endif166167resolveCheckerAndPackageOptions();168169// Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the170// command line.171for (const std::pair<std::string, bool> &Opt : AnOpts.CheckersAndPackages) {172CheckerInfoListRange CheckerForCmdLineArg =173Data.getMutableCheckersForCmdLineArg(Opt.first);174175if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) {176Diags.Report(diag::err_unknown_analyzer_checker_or_package) << Opt.first;177Diags.Report(diag::note_suggest_disabling_all_checkers);178}179180for (CheckerInfo &checker : CheckerForCmdLineArg) {181checker.State = Opt.second ? StateFromCmdLine::State_Enabled182: StateFromCmdLine::State_Disabled;183}184}185validateCheckerOptions();186}187188//===----------------------------------------------------------------------===//189// Dependency resolving.190//===----------------------------------------------------------------------===//191192template <typename IsEnabledFn>193static bool collectStrongDependencies(const ConstCheckerInfoList &Deps,194const CheckerManager &Mgr,195CheckerInfoSet &Ret,196IsEnabledFn IsEnabled);197198/// Collects weak dependencies in \p enabledData.Checkers.199template <typename IsEnabledFn>200static void collectWeakDependencies(const ConstCheckerInfoList &Deps,201const CheckerManager &Mgr,202CheckerInfoSet &Ret, IsEnabledFn IsEnabled);203204void CheckerRegistry::initializeRegistry(const CheckerManager &Mgr) {205// First, we calculate the list of enabled checkers as specified by the206// invocation. Weak dependencies will not enable their unspecified strong207// depenencies, but its only after resolving strong dependencies for all208// checkers when we know whether they will be enabled.209CheckerInfoSet Tmp;210auto IsEnabledFromCmdLine = [&](const CheckerInfo *Checker) {211return !Checker->isDisabled(Mgr);212};213for (const CheckerInfo &Checker : Data.Checkers) {214if (!Checker.isEnabled(Mgr))215continue;216217CheckerInfoSet Deps;218if (!collectStrongDependencies(Checker.Dependencies, Mgr, Deps,219IsEnabledFromCmdLine)) {220// If we failed to enable any of the dependencies, don't enable this221// checker.222continue;223}224225Tmp.insert(Deps.begin(), Deps.end());226227// Enable the checker.228Tmp.insert(&Checker);229}230231// Calculate enabled checkers with the correct registration order. As this is232// done recursively, its arguably cheaper, but for sure less error prone to233// recalculate from scratch.234auto IsEnabled = [&](const CheckerInfo *Checker) {235return Tmp.contains(Checker);236};237for (const CheckerInfo &Checker : Data.Checkers) {238if (!Checker.isEnabled(Mgr))239continue;240241CheckerInfoSet Deps;242243collectWeakDependencies(Checker.WeakDependencies, Mgr, Deps, IsEnabled);244245if (!collectStrongDependencies(Checker.Dependencies, Mgr, Deps,246IsEnabledFromCmdLine)) {247// If we failed to enable any of the dependencies, don't enable this248// checker.249continue;250}251252// Note that set_union also preserves the order of insertion.253Data.EnabledCheckers.set_union(Deps);254Data.EnabledCheckers.insert(&Checker);255}256}257258template <typename IsEnabledFn>259static bool collectStrongDependencies(const ConstCheckerInfoList &Deps,260const CheckerManager &Mgr,261CheckerInfoSet &Ret,262IsEnabledFn IsEnabled) {263264for (const CheckerInfo *Dependency : Deps) {265if (!IsEnabled(Dependency))266return false;267268// Collect dependencies recursively.269if (!collectStrongDependencies(Dependency->Dependencies, Mgr, Ret,270IsEnabled))271return false;272Ret.insert(Dependency);273}274275return true;276}277278template <typename IsEnabledFn>279static void collectWeakDependencies(const ConstCheckerInfoList &WeakDeps,280const CheckerManager &Mgr,281CheckerInfoSet &Ret,282IsEnabledFn IsEnabled) {283284for (const CheckerInfo *Dependency : WeakDeps) {285// Don't enable this checker if strong dependencies are unsatisfied, but286// assume that weak dependencies are transitive.287collectWeakDependencies(Dependency->WeakDependencies, Mgr, Ret, IsEnabled);288289if (IsEnabled(Dependency) &&290collectStrongDependencies(Dependency->Dependencies, Mgr, Ret,291IsEnabled))292Ret.insert(Dependency);293}294}295296template <bool IsWeak> void CheckerRegistry::resolveDependencies() {297for (const std::pair<StringRef, StringRef> &Entry :298(IsWeak ? Data.WeakDependencies : Data.Dependencies)) {299300auto CheckerIt = binaryFind(Data.Checkers, Entry.first);301assert(CheckerIt != Data.Checkers.end() &&302CheckerIt->FullName == Entry.first &&303"Failed to find the checker while attempting to set up its "304"dependencies!");305306auto DependencyIt = binaryFind(Data.Checkers, Entry.second);307assert(DependencyIt != Data.Checkers.end() &&308DependencyIt->FullName == Entry.second &&309"Failed to find the dependency of a checker!");310311// We do allow diagnostics from unit test/example dependency checkers.312assert((DependencyIt->FullName.starts_with("test") ||313DependencyIt->FullName.starts_with("example") || IsWeak ||314DependencyIt->IsHidden) &&315"Strong dependencies are modeling checkers, and as such "316"non-user facing! Mark them hidden in Checkers.td!");317318if (IsWeak)319CheckerIt->WeakDependencies.emplace_back(&*DependencyIt);320else321CheckerIt->Dependencies.emplace_back(&*DependencyIt);322}323}324325void CheckerRegistry::addDependency(StringRef FullName, StringRef Dependency) {326Data.Dependencies.emplace_back(FullName, Dependency);327}328329void CheckerRegistry::addWeakDependency(StringRef FullName,330StringRef Dependency) {331Data.WeakDependencies.emplace_back(FullName, Dependency);332}333334//===----------------------------------------------------------------------===//335// Checker option resolving and validating.336//===----------------------------------------------------------------------===//337338/// Insert the checker/package option to AnalyzerOptions' config table, and339/// validate it, if the user supplied it on the command line.340static void insertAndValidate(StringRef FullName, const CmdLineOption &Option,341AnalyzerOptions &AnOpts,342DiagnosticsEngine &Diags) {343344std::string FullOption = (FullName + ":" + Option.OptionName).str();345346auto It =347AnOpts.Config.insert({FullOption, std::string(Option.DefaultValStr)});348349// Insertation was successful -- CmdLineOption's constructor will validate350// whether values received from plugins or TableGen files are correct.351if (It.second)352return;353354// Insertion failed, the user supplied this package/checker option on the355// command line. If the supplied value is invalid, we'll restore the option356// to it's default value, and if we're in non-compatibility mode, we'll also357// emit an error.358359StringRef SuppliedValue = It.first->getValue();360361if (Option.OptionType == "bool") {362if (SuppliedValue != "true" && SuppliedValue != "false") {363if (AnOpts.ShouldEmitErrorsOnInvalidConfigValue) {364Diags.Report(diag::err_analyzer_checker_option_invalid_input)365<< FullOption << "a boolean value";366}367368It.first->setValue(std::string(Option.DefaultValStr));369}370return;371}372373if (Option.OptionType == "int") {374int Tmp;375bool HasFailed = SuppliedValue.getAsInteger(0, Tmp);376if (HasFailed) {377if (AnOpts.ShouldEmitErrorsOnInvalidConfigValue) {378Diags.Report(diag::err_analyzer_checker_option_invalid_input)379<< FullOption << "an integer value";380}381382It.first->setValue(std::string(Option.DefaultValStr));383}384return;385}386}387388template <class T>389static void insertOptionToCollection(StringRef FullName, T &Collection,390const CmdLineOption &Option,391AnalyzerOptions &AnOpts,392DiagnosticsEngine &Diags) {393auto It = binaryFind(Collection, FullName);394assert(It != Collection.end() &&395"Failed to find the checker while attempting to add a command line "396"option to it!");397398insertAndValidate(FullName, Option, AnOpts, Diags);399400It->CmdLineOptions.emplace_back(Option);401}402403void CheckerRegistry::resolveCheckerAndPackageOptions() {404for (const std::pair<StringRef, CmdLineOption> &CheckerOptEntry :405Data.CheckerOptions) {406insertOptionToCollection(CheckerOptEntry.first, Data.Checkers,407CheckerOptEntry.second, AnOpts, Diags);408}409410for (const std::pair<StringRef, CmdLineOption> &PackageOptEntry :411Data.PackageOptions) {412insertOptionToCollection(PackageOptEntry.first, Data.Packages,413PackageOptEntry.second, AnOpts, Diags);414}415}416417void CheckerRegistry::addPackage(StringRef FullName) {418Data.Packages.emplace_back(PackageInfo(FullName));419}420421void CheckerRegistry::addPackageOption(StringRef OptionType,422StringRef PackageFullName,423StringRef OptionName,424StringRef DefaultValStr,425StringRef Description,426StringRef DevelopmentStatus,427bool IsHidden) {428Data.PackageOptions.emplace_back(429PackageFullName, CmdLineOption{OptionType, OptionName, DefaultValStr,430Description, DevelopmentStatus, IsHidden});431}432433void CheckerRegistry::addChecker(RegisterCheckerFn Rfn,434ShouldRegisterFunction Sfn, StringRef Name,435StringRef Desc, StringRef DocsUri,436bool IsHidden) {437Data.Checkers.emplace_back(Rfn, Sfn, Name, Desc, DocsUri, IsHidden);438439// Record the presence of the checker in its packages.440StringRef PackageName, LeafName;441std::tie(PackageName, LeafName) = Name.rsplit(PackageSeparator);442while (!LeafName.empty()) {443Data.PackageSizes[PackageName] += 1;444std::tie(PackageName, LeafName) = PackageName.rsplit(PackageSeparator);445}446}447448void CheckerRegistry::addCheckerOption(StringRef OptionType,449StringRef CheckerFullName,450StringRef OptionName,451StringRef DefaultValStr,452StringRef Description,453StringRef DevelopmentStatus,454bool IsHidden) {455Data.CheckerOptions.emplace_back(456CheckerFullName, CmdLineOption{OptionType, OptionName, DefaultValStr,457Description, DevelopmentStatus, IsHidden});458}459460void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const {461// Initialize the CheckerManager with all enabled checkers.462for (const auto *Checker : Data.EnabledCheckers) {463CheckerMgr.setCurrentCheckerName(CheckerNameRef(Checker->FullName));464Checker->Initialize(CheckerMgr);465}466}467468static void isOptionContainedIn(const CmdLineOptionList &OptionList,469StringRef SuppliedChecker,470StringRef SuppliedOption,471const AnalyzerOptions &AnOpts,472DiagnosticsEngine &Diags) {473474if (!AnOpts.ShouldEmitErrorsOnInvalidConfigValue)475return;476477auto SameOptName = [SuppliedOption](const CmdLineOption &Opt) {478return Opt.OptionName == SuppliedOption;479};480481if (llvm::none_of(OptionList, SameOptName)) {482Diags.Report(diag::err_analyzer_checker_option_unknown)483<< SuppliedChecker << SuppliedOption;484return;485}486}487488void CheckerRegistry::validateCheckerOptions() const {489for (const auto &Config : AnOpts.Config) {490491StringRef SuppliedCheckerOrPackage;492StringRef SuppliedOption;493std::tie(SuppliedCheckerOrPackage, SuppliedOption) =494Config.getKey().split(':');495496if (SuppliedOption.empty())497continue;498499// AnalyzerOptions' config table contains the user input, so an entry could500// look like this:501//502// cor:NoFalsePositives=true503//504// Since lower_bound would look for the first element *not less* than "cor",505// it would return with an iterator to the first checker in the core, so we506// we really have to use find here, which uses operator==.507auto CheckerIt =508llvm::find(Data.Checkers, CheckerInfo(SuppliedCheckerOrPackage));509if (CheckerIt != Data.Checkers.end()) {510isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedCheckerOrPackage,511SuppliedOption, AnOpts, Diags);512continue;513}514515const auto *PackageIt =516llvm::find(Data.Packages, PackageInfo(SuppliedCheckerOrPackage));517if (PackageIt != Data.Packages.end()) {518isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedCheckerOrPackage,519SuppliedOption, AnOpts, Diags);520continue;521}522523Diags.Report(diag::err_unknown_analyzer_checker_or_package)524<< SuppliedCheckerOrPackage;525}526}527528529