brintos

brintos / llvm-project-archived public Read only

0
0
Text · 12.0 KiB · 1d79a7a Raw
335 lines · cpp
1//===--- TidyProvider.cpp - create options for running clang-tidy----------===//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 "TidyProvider.h"10#include "../clang-tidy/ClangTidyModuleRegistry.h"11#include "../clang-tidy/ClangTidyOptions.h"12#include "Config.h"13#include "support/FileCache.h"14#include "support/Logger.h"15#include "support/Path.h"16#include "support/ThreadsafeFS.h"17#include "llvm/ADT/STLExtras.h"18#include "llvm/ADT/SmallString.h"19#include "llvm/ADT/StringExtras.h"20#include "llvm/ADT/StringSet.h"21#include "llvm/Support/Allocator.h"22#include "llvm/Support/Process.h"23#include "llvm/Support/SourceMgr.h"24#include <memory>25#include <optional>26 27namespace clang {28namespace clangd {29namespace {30 31// Access to config from a .clang-tidy file, caching IO and parsing.32class DotClangTidyCache : private FileCache {33  // We cache and expose shared_ptr to avoid copying the value on every lookup34  // when we're ultimately just going to pass it to mergeWith.35  mutable std::shared_ptr<const tidy::ClangTidyOptions> Value;36 37public:38  DotClangTidyCache(PathRef Path) : FileCache(Path) {}39 40  std::shared_ptr<const tidy::ClangTidyOptions>41  get(const ThreadsafeFS &TFS,42      std::chrono::steady_clock::time_point FreshTime) const {43    std::shared_ptr<const tidy::ClangTidyOptions> Result;44    read(45        TFS, FreshTime,46        [this](std::optional<llvm::StringRef> Data) {47          Value.reset();48          if (Data && !Data->empty()) {49            auto Diagnostics = [](const llvm::SMDiagnostic &D) {50              switch (D.getKind()) {51              case llvm::SourceMgr::DK_Error:52                elog("tidy-config error at {0}:{1}:{2}: {3}", D.getFilename(),53                     D.getLineNo(), D.getColumnNo(), D.getMessage());54                break;55              case llvm::SourceMgr::DK_Warning:56                log("tidy-config warning at {0}:{1}:{2}: {3}", D.getFilename(),57                    D.getLineNo(), D.getColumnNo(), D.getMessage());58                break;59              case llvm::SourceMgr::DK_Note:60              case llvm::SourceMgr::DK_Remark:61                vlog("tidy-config note at {0}:{1}:{2}: {3}", D.getFilename(),62                     D.getLineNo(), D.getColumnNo(), D.getMessage());63                break;64              }65            };66            if (auto Parsed = tidy::parseConfigurationWithDiags(67                    llvm::MemoryBufferRef(*Data, path()), Diagnostics))68              Value = std::make_shared<const tidy::ClangTidyOptions>(69                  std::move(*Parsed));70            else71              elog("Error parsing clang-tidy configuration in {0}: {1}", path(),72                   Parsed.getError().message());73          }74        },75        [&]() { Result = Value; });76    return Result;77  }78};79 80// Access to combined config from .clang-tidy files governing a source file.81// Each config file is cached and the caches are shared for affected sources.82//83// FIXME: largely duplicates config::Provider::fromAncestorRelativeYAMLFiles.84// Potentially useful for compile_commands.json too. Extract?85class DotClangTidyTree {86  const ThreadsafeFS &FS;87  std::string RelPath;88  std::chrono::steady_clock::duration MaxStaleness;89 90  mutable std::mutex Mu;91  // Keys are the ancestor directory, not the actual config path within it.92  // We only insert into this map, so pointers to values are stable forever.93  // Mutex guards the map itself, not the values (which are threadsafe).94  mutable llvm::StringMap<DotClangTidyCache> Cache;95 96public:97  DotClangTidyTree(const ThreadsafeFS &FS)98      : FS(FS), RelPath(".clang-tidy"), MaxStaleness(std::chrono::seconds(5)) {}99 100  void apply(tidy::ClangTidyOptions &Result, PathRef AbsPath) {101    namespace path = llvm::sys::path;102    assert(path::is_absolute(AbsPath));103 104    // Compute absolute paths to all ancestors (substrings of P.Path).105    // Ensure cache entries for each ancestor exist in the map.106    llvm::SmallVector<DotClangTidyCache *> Caches;107    {108      std::lock_guard<std::mutex> Lock(Mu);109      for (auto Ancestor = absoluteParent(AbsPath); !Ancestor.empty();110           Ancestor = absoluteParent(Ancestor)) {111        auto It = Cache.find(Ancestor);112        // Assemble the actual config file path only if needed.113        if (It == Cache.end()) {114          llvm::SmallString<256> ConfigPath = Ancestor;115          path::append(ConfigPath, RelPath);116          It = Cache.try_emplace(Ancestor, ConfigPath.str()).first;117        }118        Caches.push_back(&It->second);119      }120    }121    // Finally query each individual file.122    // This will take a (per-file) lock for each file that actually exists.123    std::chrono::steady_clock::time_point FreshTime =124        std::chrono::steady_clock::now() - MaxStaleness;125    llvm::SmallVector<std::shared_ptr<const tidy::ClangTidyOptions>>126        OptionStack;127    for (const DotClangTidyCache *Cache : Caches)128      if (auto Config = Cache->get(FS, FreshTime)) {129        OptionStack.push_back(std::move(Config));130        if (!OptionStack.back()->InheritParentConfig.value_or(false))131          break;132      }133    unsigned Order = 1u;134    for (auto &Option : llvm::reverse(OptionStack))135      Result.mergeWith(*Option, Order++);136  }137};138 139} // namespace140 141static void mergeCheckList(std::optional<std::string> &Checks,142                           llvm::StringRef List) {143  if (List.empty())144    return;145  if (!Checks || Checks->empty()) {146    Checks.emplace(List);147    return;148  }149  *Checks = llvm::join_items(",", *Checks, List);150}151 152TidyProvider provideEnvironment() {153  static const std::optional<std::string> User = [] {154    std::optional<std::string> Ret = llvm::sys::Process::GetEnv("USER");155#ifdef _WIN32156    if (!Ret)157      return llvm::sys::Process::GetEnv("USERNAME");158#endif159    return Ret;160  }();161 162  if (User)163    return164        [](tidy::ClangTidyOptions &Opts, llvm::StringRef) { Opts.User = User; };165  // FIXME: Once function_ref and unique_function operator= operators handle166  // null values, this can return null.167  return [](tidy::ClangTidyOptions &, llvm::StringRef) {};168}169 170TidyProvider provideDefaultChecks() {171  // These default checks are chosen for:172  //  - low false-positive rate173  //  - providing a lot of value174  //  - being reasonably efficient175  static const std::string DefaultChecks = llvm::join_items(176      ",", "readability-misleading-indentation", "readability-deleted-default",177      "bugprone-integer-division", "bugprone-sizeof-expression",178      "bugprone-suspicious-missing-comma", "bugprone-unused-raii",179      "bugprone-unused-return-value", "misc-unused-using-decls",180      "misc-unused-alias-decls", "misc-definitions-in-headers");181  return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {182    if (!Opts.Checks || Opts.Checks->empty())183      Opts.Checks = DefaultChecks;184  };185}186 187TidyProvider addTidyChecks(llvm::StringRef Checks,188                           llvm::StringRef WarningsAsErrors) {189  return [Checks = std::string(Checks),190          WarningsAsErrors = std::string(WarningsAsErrors)](191             tidy::ClangTidyOptions &Opts, llvm::StringRef) {192    mergeCheckList(Opts.Checks, Checks);193    mergeCheckList(Opts.WarningsAsErrors, WarningsAsErrors);194  };195}196 197TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) {198  constexpr llvm::StringLiteral Separator(",");199  static const std::string BadChecks = llvm::join_items(200      Separator,201      // We want this list to start with a separator to202      // simplify appending in the lambda. So including an203      // empty string here will force that.204      "",205      // include-cleaner is directly integrated in IncludeCleaner.cpp206      "-misc-include-cleaner",207 208      // ----- False Positives -----209 210      // Check relies on seeing ifndef/define/endif directives,211      // clangd doesn't replay those when using a preamble.212      "-llvm-header-guard", "-modernize-macro-to-enum",213      "-cppcoreguidelines-macro-to-enum",214 215      // ----- Crashing Checks -----216 217      // Check can choke on invalid (intermediate) c++218      // code, which is often the case when clangd219      // tries to build an AST.220      "-bugprone-use-after-move",221      // Alias for bugprone-use-after-move.222      "-hicpp-invalid-access-moved",223      // Check uses dataflow analysis, which might hang/crash unexpectedly on224      // incomplete code.225      "-bugprone-unchecked-optional-access");226 227  size_t Size = BadChecks.size();228  for (const std::string &Str : ExtraBadChecks) {229    if (Str.empty())230      continue;231    Size += Separator.size();232    if (LLVM_LIKELY(Str.front() != '-'))233      ++Size;234    Size += Str.size();235  }236  std::string DisableGlob;237  DisableGlob.reserve(Size);238  DisableGlob += BadChecks;239  for (const std::string &Str : ExtraBadChecks) {240    if (Str.empty())241      continue;242    DisableGlob += Separator;243    if (LLVM_LIKELY(Str.front() != '-'))244      DisableGlob.push_back('-');245    DisableGlob += Str;246  }247 248  return [DisableList(std::move(DisableGlob))](tidy::ClangTidyOptions &Opts,249                                               llvm::StringRef) {250    if (Opts.Checks && !Opts.Checks->empty())251      Opts.Checks->append(DisableList);252  };253}254 255TidyProvider provideClangdConfig() {256  return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {257    const auto &CurTidyConfig = Config::current().Diagnostics.ClangTidy;258    if (!CurTidyConfig.Checks.empty())259      mergeCheckList(Opts.Checks, CurTidyConfig.Checks);260 261    for (const auto &CheckOption : CurTidyConfig.CheckOptions)262      Opts.CheckOptions.insert_or_assign(CheckOption.getKey(),263                                         tidy::ClangTidyOptions::ClangTidyValue(264                                             CheckOption.getValue(), 10000U));265  };266}267 268TidyProvider provideClangTidyFiles(ThreadsafeFS &TFS) {269  return [Tree = std::make_unique<DotClangTidyTree>(TFS)](270             tidy::ClangTidyOptions &Opts, llvm::StringRef Filename) {271    Tree->apply(Opts, Filename);272  };273}274 275TidyProvider combine(std::vector<TidyProvider> Providers) {276  // FIXME: Once function_ref and unique_function operator= operators handle277  // null values, we should filter out any Providers that are null. Right now we278  // have to ensure we dont pass any providers that are null.279  return [Providers(std::move(Providers))](tidy::ClangTidyOptions &Opts,280                                           llvm::StringRef Filename) {281    for (const auto &Provider : Providers)282      Provider(Opts, Filename);283  };284}285 286tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider,287                                             llvm::StringRef Filename) {288  // getDefaults instantiates all check factories, which are registered at link289  // time. So cache the results once.290  static const auto *DefaultOpts = [] {291    auto *Opts = new tidy::ClangTidyOptions;292    *Opts = tidy::ClangTidyOptions::getDefaults();293    Opts->Checks->clear();294    return Opts;295  }();296  auto Opts = *DefaultOpts;297  if (Provider)298    Provider(Opts, Filename);299  return Opts;300}301 302bool isRegisteredTidyCheck(llvm::StringRef Check) {303  assert(!Check.empty());304  assert(!Check.contains('*') && !Check.contains(',') &&305         "isRegisteredCheck doesn't support globs");306  assert(Check.ltrim().front() != '-');307 308  static const llvm::StringSet<llvm::BumpPtrAllocator> AllChecks = [] {309    llvm::StringSet<llvm::BumpPtrAllocator> Result;310    tidy::ClangTidyCheckFactories Factories;311    for (tidy::ClangTidyModuleRegistry::entry E :312         tidy::ClangTidyModuleRegistry::entries())313      E.instantiate()->addCheckFactories(Factories);314    for (const auto &Factory : Factories)315      Result.insert(Factory.getKey());316    return Result;317  }();318 319  return AllChecks.contains(Check);320}321 322std::optional<bool> isFastTidyCheck(llvm::StringRef Check) {323  static auto &Fast = *new llvm::StringMap<bool>{324#define FAST(CHECK, TIME) {#CHECK,true},325#define SLOW(CHECK, TIME) {#CHECK,false},326#include "TidyFastChecks.inc"327  };328  if (auto It = Fast.find(Check); It != Fast.end())329    return It->second;330  return std::nullopt;331}332 333} // namespace clangd334} // namespace clang335