526 lines · cpp
1//===- CheckerRegistry.cpp - Maintains all available checkers -------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"10#include "clang/Basic/Diagnostic.h"11#include "clang/Basic/DiagnosticFrontend.h"12#include "clang/Basic/LLVM.h"13#include "clang/Driver/DriverDiagnostic.h"14#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"15#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"16#include "clang/StaticAnalyzer/Core/CheckerManager.h"17#include "llvm/ADT/STLExtras.h"18#include "llvm/ADT/StringMap.h"19#include "llvm/ADT/StringRef.h"20#include "llvm/Support/DynamicLibrary.h"21#include "llvm/Support/Path.h"22 23using namespace clang;24using namespace ento;25using namespace checker_registry;26using llvm::sys::DynamicLibrary;27 28//===----------------------------------------------------------------------===//29// Utilities.30//===----------------------------------------------------------------------===//31 32static bool isCompatibleAPIVersion(const char *VersionString) {33 // If the version string is null, its not an analyzer plugin.34 if (!VersionString)35 return false;36 37 // For now, none of the static analyzer API is considered stable.38 // Versions must match exactly.39 return strcmp(VersionString, CLANG_ANALYZER_API_VERSION_STRING) == 0;40}41 42static constexpr char PackageSeparator = '.';43 44//===----------------------------------------------------------------------===//45// Methods of CheckerRegistry.46//===----------------------------------------------------------------------===//47 48CheckerRegistry::CheckerRegistry(49 CheckerRegistryData &Data, ArrayRef<std::string> Plugins,50 DiagnosticsEngine &Diags, AnalyzerOptions &AnOpts,51 ArrayRef<std::function<void(CheckerRegistry &)>> CheckerRegistrationFns)52 : Data(Data), Diags(Diags), AnOpts(AnOpts) {53 54 // Register builtin checkers.55#define GET_CHECKERS56#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \57 addChecker(register##CLASS, shouldRegister##CLASS, FULLNAME, HELPTEXT, \58 DOC_URI, IS_HIDDEN);59 60#define GET_PACKAGES61#define PACKAGE(FULLNAME) addPackage(FULLNAME);62 63#include "clang/StaticAnalyzer/Checkers/Checkers.inc"64#undef CHECKER65#undef GET_CHECKERS66#undef PACKAGE67#undef GET_PACKAGES68 69 // Register checkers from plugins.70 for (const std::string &Plugin : Plugins) {71 // Get access to the plugin.72 std::string ErrorMsg;73 DynamicLibrary Lib =74 DynamicLibrary::getPermanentLibrary(Plugin.c_str(), &ErrorMsg);75 if (!Lib.isValid()) {76 Diags.Report(diag::err_fe_unable_to_load_plugin) << Plugin << ErrorMsg;77 continue;78 }79 80 // See if its compatible with this build of clang.81 const char *PluginAPIVersion = static_cast<const char *>(82 Lib.getAddressOfSymbol("clang_analyzerAPIVersionString"));83 84 if (!isCompatibleAPIVersion(PluginAPIVersion)) {85 Diags.Report(diag::warn_incompatible_analyzer_plugin_api)86 << llvm::sys::path::filename(Plugin);87 Diags.Report(diag::note_incompatible_analyzer_plugin_api)88 << CLANG_ANALYZER_API_VERSION_STRING << PluginAPIVersion;89 continue;90 }91 92 using RegisterPluginCheckerFn = void (*)(CheckerRegistry &);93 // Register its checkers.94 RegisterPluginCheckerFn RegisterPluginCheckers =95 reinterpret_cast<RegisterPluginCheckerFn>(96 Lib.getAddressOfSymbol("clang_registerCheckers"));97 if (RegisterPluginCheckers)98 RegisterPluginCheckers(*this);99 }100 101 // Register statically linked checkers, that aren't generated from the tblgen102 // file, but rather passed their registry function as a parameter in103 // checkerRegistrationFns.104 105 for (const auto &Fn : CheckerRegistrationFns)106 Fn(*this);107 108 // Sort checkers for efficient collection.109 // FIXME: Alphabetical sort puts 'experimental' in the middle.110 // Would it be better to name it '~experimental' or something else111 // that's ASCIIbetically last?112 llvm::sort(Data.Packages, checker_registry::PackageNameLT{});113 llvm::sort(Data.Checkers, checker_registry::CheckerNameLT{});114 115#define GET_CHECKER_DEPENDENCIES116 117#define CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY) \118 addDependency(FULLNAME, DEPENDENCY);119 120#define GET_CHECKER_WEAK_DEPENDENCIES121 122#define CHECKER_WEAK_DEPENDENCY(FULLNAME, DEPENDENCY) \123 addWeakDependency(FULLNAME, DEPENDENCY);124 125#define GET_CHECKER_OPTIONS126#define CHECKER_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, \127 DEVELOPMENT_STATUS, IS_HIDDEN) \128 addCheckerOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, \129 DEVELOPMENT_STATUS, IS_HIDDEN);130 131#define GET_PACKAGE_OPTIONS132#define PACKAGE_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, \133 DEVELOPMENT_STATUS, IS_HIDDEN) \134 addPackageOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, \135 DEVELOPMENT_STATUS, IS_HIDDEN);136 137#include "clang/StaticAnalyzer/Checkers/Checkers.inc"138#undef CHECKER_DEPENDENCY139#undef GET_CHECKER_DEPENDENCIES140#undef CHECKER_WEAK_DEPENDENCY141#undef GET_CHECKER_WEAK_DEPENDENCIES142#undef CHECKER_OPTION143#undef GET_CHECKER_OPTIONS144#undef PACKAGE_OPTION145#undef GET_PACKAGE_OPTIONS146 147 resolveDependencies<true>();148 resolveDependencies<false>();149 150#ifndef NDEBUG151 for (auto &DepPair : Data.Dependencies) {152 for (auto &WeakDepPair : Data.WeakDependencies) {153 // Some assertions to enforce that strong dependencies are relations in154 // between purely modeling checkers, and weak dependencies are about155 // diagnostics.156 assert(WeakDepPair != DepPair &&157 "A checker cannot strong and weak depend on the same checker!");158 assert(WeakDepPair.first != DepPair.second &&159 "A strong dependency mustn't have weak dependencies!");160 assert(WeakDepPair.second != DepPair.second &&161 "A strong dependency mustn't be a weak dependency as well!");162 }163 }164#endif165 166 resolveCheckerAndPackageOptions();167 168 // Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the169 // command line.170 for (const std::pair<std::string, bool> &Opt : AnOpts.CheckersAndPackages) {171 CheckerInfoListRange CheckerForCmdLineArg =172 Data.getMutableCheckersForCmdLineArg(Opt.first);173 174 if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) {175 Diags.Report(diag::err_unknown_analyzer_checker_or_package) << Opt.first;176 Diags.Report(diag::note_suggest_disabling_all_checkers);177 }178 179 for (CheckerInfo &checker : CheckerForCmdLineArg) {180 checker.State = Opt.second ? StateFromCmdLine::State_Enabled181 : StateFromCmdLine::State_Disabled;182 }183 }184 validateCheckerOptions();185}186 187//===----------------------------------------------------------------------===//188// Dependency resolving.189//===----------------------------------------------------------------------===//190 191template <typename IsEnabledFn>192static bool collectStrongDependencies(const ConstCheckerInfoList &Deps,193 const CheckerManager &Mgr,194 CheckerInfoSet &Ret,195 IsEnabledFn IsEnabled);196 197/// Collects weak dependencies in \p enabledData.Checkers.198template <typename IsEnabledFn>199static void collectWeakDependencies(const ConstCheckerInfoList &Deps,200 const CheckerManager &Mgr,201 CheckerInfoSet &Ret, IsEnabledFn IsEnabled);202 203void CheckerRegistry::initializeRegistry(const CheckerManager &Mgr) {204 // First, we calculate the list of enabled checkers as specified by the205 // invocation. Weak dependencies will not enable their unspecified strong206 // depenencies, but its only after resolving strong dependencies for all207 // checkers when we know whether they will be enabled.208 CheckerInfoSet Tmp;209 auto IsEnabledFromCmdLine = [&](const CheckerInfo *Checker) {210 return !Checker->isDisabled(Mgr);211 };212 for (const CheckerInfo &Checker : Data.Checkers) {213 if (!Checker.isEnabled(Mgr))214 continue;215 216 CheckerInfoSet Deps;217 if (!collectStrongDependencies(Checker.Dependencies, Mgr, Deps,218 IsEnabledFromCmdLine)) {219 // If we failed to enable any of the dependencies, don't enable this220 // checker.221 continue;222 }223 224 Tmp.insert_range(Deps);225 226 // Enable the checker.227 Tmp.insert(&Checker);228 }229 230 // Calculate enabled checkers with the correct registration order. As this is231 // done recursively, its arguably cheaper, but for sure less error prone to232 // recalculate from scratch.233 auto IsEnabled = [&](const CheckerInfo *Checker) {234 return Tmp.contains(Checker);235 };236 for (const CheckerInfo &Checker : Data.Checkers) {237 if (!Checker.isEnabled(Mgr))238 continue;239 240 CheckerInfoSet Deps;241 242 collectWeakDependencies(Checker.WeakDependencies, Mgr, Deps, IsEnabled);243 244 if (!collectStrongDependencies(Checker.Dependencies, Mgr, Deps,245 IsEnabledFromCmdLine)) {246 // If we failed to enable any of the dependencies, don't enable this247 // checker.248 continue;249 }250 251 // Note that set_union also preserves the order of insertion.252 Data.EnabledCheckers.set_union(Deps);253 Data.EnabledCheckers.insert(&Checker);254 }255}256 257template <typename IsEnabledFn>258static bool collectStrongDependencies(const ConstCheckerInfoList &Deps,259 const CheckerManager &Mgr,260 CheckerInfoSet &Ret,261 IsEnabledFn IsEnabled) {262 263 for (const CheckerInfo *Dependency : Deps) {264 if (!IsEnabled(Dependency))265 return false;266 267 // Collect dependencies recursively.268 if (!collectStrongDependencies(Dependency->Dependencies, Mgr, Ret,269 IsEnabled))270 return false;271 Ret.insert(Dependency);272 }273 274 return true;275}276 277template <typename IsEnabledFn>278static void collectWeakDependencies(const ConstCheckerInfoList &WeakDeps,279 const CheckerManager &Mgr,280 CheckerInfoSet &Ret,281 IsEnabledFn IsEnabled) {282 283 for (const CheckerInfo *Dependency : WeakDeps) {284 // Don't enable this checker if strong dependencies are unsatisfied, but285 // assume that weak dependencies are transitive.286 collectWeakDependencies(Dependency->WeakDependencies, Mgr, Ret, IsEnabled);287 288 if (IsEnabled(Dependency) &&289 collectStrongDependencies(Dependency->Dependencies, Mgr, Ret,290 IsEnabled))291 Ret.insert(Dependency);292 }293}294 295template <bool IsWeak> void CheckerRegistry::resolveDependencies() {296 for (const std::pair<StringRef, StringRef> &Entry :297 (IsWeak ? Data.WeakDependencies : Data.Dependencies)) {298 299 auto CheckerIt = binaryFind(Data.Checkers, Entry.first);300 assert(CheckerIt != Data.Checkers.end() &&301 CheckerIt->FullName == Entry.first &&302 "Failed to find the checker while attempting to set up its "303 "dependencies!");304 305 auto DependencyIt = binaryFind(Data.Checkers, Entry.second);306 assert(DependencyIt != Data.Checkers.end() &&307 DependencyIt->FullName == Entry.second &&308 "Failed to find the dependency of a checker!");309 310 // We do allow diagnostics from unit test/example dependency checkers.311 assert((DependencyIt->FullName.starts_with("test") ||312 DependencyIt->FullName.starts_with("example") || IsWeak ||313 DependencyIt->IsHidden) &&314 "Strong dependencies are modeling checkers, and as such "315 "non-user facing! Mark them hidden in Checkers.td!");316 317 if (IsWeak)318 CheckerIt->WeakDependencies.emplace_back(&*DependencyIt);319 else320 CheckerIt->Dependencies.emplace_back(&*DependencyIt);321 }322}323 324void CheckerRegistry::addDependency(StringRef FullName, StringRef Dependency) {325 Data.Dependencies.emplace_back(FullName, Dependency);326}327 328void CheckerRegistry::addWeakDependency(StringRef FullName,329 StringRef Dependency) {330 Data.WeakDependencies.emplace_back(FullName, Dependency);331}332 333//===----------------------------------------------------------------------===//334// Checker option resolving and validating.335//===----------------------------------------------------------------------===//336 337/// Insert the checker/package option to AnalyzerOptions' config table, and338/// validate it, if the user supplied it on the command line.339static void insertAndValidate(StringRef FullName, const CmdLineOption &Option,340 AnalyzerOptions &AnOpts,341 DiagnosticsEngine &Diags) {342 343 std::string FullOption = (FullName + ":" + Option.OptionName).str();344 345 auto It =346 AnOpts.Config.insert({FullOption, std::string(Option.DefaultValStr)});347 348 // Insertation was successful -- CmdLineOption's constructor will validate349 // whether values received from plugins or TableGen files are correct.350 if (It.second)351 return;352 353 // Insertion failed, the user supplied this package/checker option on the354 // command line. If the supplied value is invalid, we'll restore the option355 // to it's default value, and if we're in non-compatibility mode, we'll also356 // emit an error.357 358 StringRef SuppliedValue = It.first->getValue();359 360 if (Option.OptionType == "bool") {361 if (SuppliedValue != "true" && SuppliedValue != "false") {362 if (AnOpts.ShouldEmitErrorsOnInvalidConfigValue) {363 Diags.Report(diag::err_analyzer_checker_option_invalid_input)364 << FullOption << "a boolean value";365 }366 367 It.first->setValue(std::string(Option.DefaultValStr));368 }369 return;370 }371 372 if (Option.OptionType == "int") {373 int Tmp;374 bool HasFailed = SuppliedValue.getAsInteger(0, Tmp);375 if (HasFailed) {376 if (AnOpts.ShouldEmitErrorsOnInvalidConfigValue) {377 Diags.Report(diag::err_analyzer_checker_option_invalid_input)378 << FullOption << "an integer value";379 }380 381 It.first->setValue(std::string(Option.DefaultValStr));382 }383 return;384 }385}386 387template <class T>388static void insertOptionToCollection(StringRef FullName, T &Collection,389 const CmdLineOption &Option,390 AnalyzerOptions &AnOpts,391 DiagnosticsEngine &Diags) {392 auto It = binaryFind(Collection, FullName);393 assert(It != Collection.end() &&394 "Failed to find the checker while attempting to add a command line "395 "option to it!");396 397 insertAndValidate(FullName, Option, AnOpts, Diags);398 399 It->CmdLineOptions.emplace_back(Option);400}401 402void CheckerRegistry::resolveCheckerAndPackageOptions() {403 for (const std::pair<StringRef, CmdLineOption> &CheckerOptEntry :404 Data.CheckerOptions) {405 insertOptionToCollection(CheckerOptEntry.first, Data.Checkers,406 CheckerOptEntry.second, AnOpts, Diags);407 }408 409 for (const std::pair<StringRef, CmdLineOption> &PackageOptEntry :410 Data.PackageOptions) {411 insertOptionToCollection(PackageOptEntry.first, Data.Packages,412 PackageOptEntry.second, AnOpts, Diags);413 }414}415 416void CheckerRegistry::addPackage(StringRef FullName) {417 Data.Packages.emplace_back(PackageInfo(FullName));418}419 420void CheckerRegistry::addPackageOption(StringRef OptionType,421 StringRef PackageFullName,422 StringRef OptionName,423 StringRef DefaultValStr,424 StringRef Description,425 StringRef DevelopmentStatus,426 bool IsHidden) {427 Data.PackageOptions.emplace_back(428 PackageFullName, CmdLineOption{OptionType, OptionName, DefaultValStr,429 Description, DevelopmentStatus, IsHidden});430}431 432void CheckerRegistry::addChecker(RegisterCheckerFn Rfn,433 ShouldRegisterFunction Sfn, StringRef Name,434 StringRef Desc, StringRef DocsUri,435 bool IsHidden) {436 Data.Checkers.emplace_back(Rfn, Sfn, Name, Desc, DocsUri, IsHidden);437 438 // Record the presence of the checker in its packages.439 StringRef PackageName, LeafName;440 std::tie(PackageName, LeafName) = Name.rsplit(PackageSeparator);441 while (!LeafName.empty()) {442 Data.PackageSizes[PackageName] += 1;443 std::tie(PackageName, LeafName) = PackageName.rsplit(PackageSeparator);444 }445}446 447void CheckerRegistry::addCheckerOption(StringRef OptionType,448 StringRef CheckerFullName,449 StringRef OptionName,450 StringRef DefaultValStr,451 StringRef Description,452 StringRef DevelopmentStatus,453 bool IsHidden) {454 Data.CheckerOptions.emplace_back(455 CheckerFullName, CmdLineOption{OptionType, OptionName, DefaultValStr,456 Description, DevelopmentStatus, IsHidden});457}458 459void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const {460 // Initialize the CheckerManager with all enabled checkers.461 for (const auto *Checker : Data.EnabledCheckers) {462 CheckerMgr.setCurrentCheckerName(CheckerNameRef(Checker->FullName));463 Checker->Initialize(CheckerMgr);464 }465}466 467static void isOptionContainedIn(const CmdLineOptionList &OptionList,468 StringRef SuppliedChecker,469 StringRef SuppliedOption,470 const AnalyzerOptions &AnOpts,471 DiagnosticsEngine &Diags) {472 473 if (!AnOpts.ShouldEmitErrorsOnInvalidConfigValue)474 return;475 476 auto SameOptName = [SuppliedOption](const CmdLineOption &Opt) {477 return Opt.OptionName == SuppliedOption;478 };479 480 if (llvm::none_of(OptionList, SameOptName)) {481 Diags.Report(diag::err_analyzer_checker_option_unknown)482 << SuppliedChecker << SuppliedOption;483 return;484 }485}486 487void CheckerRegistry::validateCheckerOptions() const {488 for (const auto &Config : AnOpts.Config) {489 490 StringRef SuppliedCheckerOrPackage;491 StringRef SuppliedOption;492 std::tie(SuppliedCheckerOrPackage, SuppliedOption) =493 Config.getKey().split(':');494 495 if (SuppliedOption.empty())496 continue;497 498 // AnalyzerOptions' config table contains the user input, so an entry could499 // look like this:500 //501 // cor:NoFalsePositives=true502 //503 // Since lower_bound would look for the first element *not less* than "cor",504 // it would return with an iterator to the first checker in the core, so we505 // we really have to use find here, which uses operator==.506 auto CheckerIt =507 llvm::find(Data.Checkers, CheckerInfo(SuppliedCheckerOrPackage));508 if (CheckerIt != Data.Checkers.end()) {509 isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedCheckerOrPackage,510 SuppliedOption, AnOpts, Diags);511 continue;512 }513 514 const auto *PackageIt =515 llvm::find(Data.Packages, PackageInfo(SuppliedCheckerOrPackage));516 if (PackageIt != Data.Packages.end()) {517 isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedCheckerOrPackage,518 SuppliedOption, AnOpts, Diags);519 continue;520 }521 522 Diags.Report(diag::err_unknown_analyzer_checker_or_package)523 << SuppliedCheckerOrPackage;524 }525}526