brintos

brintos / llvm-project-archived public Read only

0
0
Text · 20.8 KiB · 550f780 Raw
574 lines · cpp
1//===----------------------------------------------------------------------===//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 "ClangTidyOptions.h"10#include "ClangTidyModuleRegistry.h"11#include "clang/Basic/DiagnosticIDs.h"12#include "clang/Basic/LLVM.h"13#include "llvm/ADT/SmallString.h"14#include "llvm/ADT/StringExtras.h"15#include "llvm/Support/Debug.h"16#include "llvm/Support/ErrorOr.h"17#include "llvm/Support/MemoryBufferRef.h"18#include "llvm/Support/Path.h"19#include "llvm/Support/YAMLTraits.h"20#include <algorithm>21#include <optional>22#include <utility>23 24#define DEBUG_TYPE "clang-tidy-options"25 26using clang::tidy::ClangTidyOptions;27using clang::tidy::FileFilter;28using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource;29 30LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)31LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)32 33namespace llvm::yaml {34 35// Map std::pair<int, int> to a JSON array of size 2.36template <> struct SequenceTraits<FileFilter::LineRange> {37  static size_t size(IO &IO, FileFilter::LineRange &Range) {38    return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2;39  }40  static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) {41    if (Index > 1)42      IO.setError("Too many elements in line range.");43    return Index == 0 ? Range.first : Range.second;44  }45};46 47template <> struct MappingTraits<FileFilter> {48  static void mapping(IO &IO, FileFilter &File) {49    IO.mapRequired("name", File.Name);50    IO.mapOptional("lines", File.LineRanges);51  }52  static std::string validate(IO &Io, FileFilter &File) {53    if (File.Name.empty())54      return "No file name specified";55    for (const FileFilter::LineRange &Range : File.LineRanges) {56      if (Range.first <= 0 || Range.second <= 0)57        return "Invalid line range";58    }59    return "";60  }61};62 63template <> struct MappingTraits<ClangTidyOptions::StringPair> {64  static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue) {65    IO.mapRequired("key", KeyValue.first);66    IO.mapRequired("value", KeyValue.second);67  }68};69 70struct NOptionMap {71  NOptionMap(IO &) {}72  NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) {73    Options.reserve(OptionMap.size());74    for (const auto &KeyValue : OptionMap)75      Options.emplace_back(std::string(KeyValue.getKey()),76                           KeyValue.getValue().Value);77  }78  ClangTidyOptions::OptionMap denormalize(IO &) {79    ClangTidyOptions::OptionMap Map;80    for (const auto &KeyValue : Options)81      Map[KeyValue.first] = ClangTidyOptions::ClangTidyValue(KeyValue.second);82    return Map;83  }84  std::vector<ClangTidyOptions::StringPair> Options;85};86 87template <>88void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,89             EmptyContext &Ctx) {90  if (IO.outputting()) {91    // Ensure check options are sorted92    std::vector<std::pair<StringRef, StringRef>> SortedOptions;93    SortedOptions.reserve(Val.size());94    for (auto &Key : Val) {95      SortedOptions.emplace_back(Key.getKey(), Key.getValue().Value);96    }97    std::sort(SortedOptions.begin(), SortedOptions.end());98 99    IO.beginMapping();100    // Only output as a map101    for (auto &Option : SortedOptions) {102      bool UseDefault = false;103      void *SaveInfo = nullptr;104      // Requires 'llvm::yaml::IO' to accept 'StringRef'105      // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)106      IO.preflightKey(Option.first.data(), true, false, UseDefault, SaveInfo);107      IO.scalarString(Option.second, needsQuotes(Option.second));108      IO.postflightKey(SaveInfo);109    }110    IO.endMapping();111  } else {112    // We need custom logic here to support the old method of specifying check113    // options using a list of maps containing key and value keys.114    auto &I = reinterpret_cast<Input &>(IO);115    if (isa<SequenceNode>(I.getCurrentNode())) {116      MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(IO,117                                                                          Val);118      EmptyContext Ctx;119      yamlize(IO, NOpts->Options, true, Ctx);120    } else if (isa<MappingNode>(I.getCurrentNode())) {121      IO.beginMapping();122      for (const StringRef Key : IO.keys()) {123        // Requires 'llvm::yaml::IO' to accept 'StringRef'124        // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)125        IO.mapRequired(Key.data(), Val[Key].Value);126      }127      IO.endMapping();128    } else {129      IO.setError("expected a sequence or map");130    }131  }132}133 134namespace {135struct MultiLineString {136  std::string &S;137};138} // namespace139 140template <> struct BlockScalarTraits<MultiLineString> {141  static void output(const MultiLineString &S, void *Ctxt, raw_ostream &OS) {142    OS << S.S;143  }144  static StringRef input(StringRef Str, void *Ctxt, MultiLineString &S) {145    S.S = Str;146    return "";147  }148};149 150template <> struct ScalarEnumerationTraits<clang::DiagnosticIDs::Level> {151  static void enumeration(IO &IO, clang::DiagnosticIDs::Level &Level) {152    IO.enumCase(Level, "Warning", clang::DiagnosticIDs::Level::Warning);153    IO.enumCase(Level, "Note", clang::DiagnosticIDs::Level::Note);154  }155};156template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckDiag> {157  // NOLINTNEXTLINE(readability-identifier-naming) Defined by YAMLTraits.h158  static const bool flow = false;159};160template <> struct MappingTraits<ClangTidyOptions::CustomCheckDiag> {161  static void mapping(IO &IO, ClangTidyOptions::CustomCheckDiag &D) {162    IO.mapRequired("BindName", D.BindName);163    MultiLineString MLS{D.Message};164    IO.mapRequired("Message", MLS);165    IO.mapOptional("Level", D.Level);166  }167};168template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckValue> {169  // NOLINTNEXTLINE(readability-identifier-naming) Defined by YAMLTraits.h170  static const bool flow = false;171};172template <> struct MappingTraits<ClangTidyOptions::CustomCheckValue> {173  static void mapping(IO &IO, ClangTidyOptions::CustomCheckValue &V) {174    IO.mapRequired("Name", V.Name);175    MultiLineString MLS{V.Query};176    IO.mapRequired("Query", MLS);177    IO.mapRequired("Diagnostic", V.Diags);178  }179};180 181struct ChecksVariant {182  std::optional<std::string> AsString;183  std::optional<std::vector<std::string>> AsVector;184};185 186template <> void yamlize(IO &IO, ChecksVariant &Val, bool, EmptyContext &Ctx) {187  if (!IO.outputting()) {188    // Special case for reading from YAML189    // Must support reading from both a string or a list190    auto &I = reinterpret_cast<Input &>(IO);191    if (isa<ScalarNode, BlockScalarNode>(I.getCurrentNode())) {192      Val.AsString = std::string();193      yamlize(IO, *Val.AsString, true, Ctx);194    } else if (isa<SequenceNode>(I.getCurrentNode())) {195      Val.AsVector = std::vector<std::string>();196      yamlize(IO, *Val.AsVector, true, Ctx);197    } else {198      IO.setError("expected string or sequence");199    }200  }201}202 203static void mapChecks(IO &IO, std::optional<std::string> &Checks) {204  if (IO.outputting()) {205    // Output always a string206    IO.mapOptional("Checks", Checks);207  } else {208    // Input as either a string or a list209    ChecksVariant ChecksAsVariant;210    IO.mapOptional("Checks", ChecksAsVariant);211    if (ChecksAsVariant.AsString)212      Checks = ChecksAsVariant.AsString;213    else if (ChecksAsVariant.AsVector)214      Checks = llvm::join(*ChecksAsVariant.AsVector, ",");215  }216}217 218template <> struct MappingTraits<ClangTidyOptions> {219  static void mapping(IO &IO, ClangTidyOptions &Options) {220    mapChecks(IO, Options.Checks);221    IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors);222    IO.mapOptional("HeaderFileExtensions", Options.HeaderFileExtensions);223    IO.mapOptional("ImplementationFileExtensions",224                   Options.ImplementationFileExtensions);225    IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);226    IO.mapOptional("ExcludeHeaderFilterRegex",227                   Options.ExcludeHeaderFilterRegex);228    IO.mapOptional("FormatStyle", Options.FormatStyle);229    IO.mapOptional("User", Options.User);230    IO.mapOptional("CheckOptions", Options.CheckOptions);231    IO.mapOptional("ExtraArgs", Options.ExtraArgs);232    IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore);233    IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);234    IO.mapOptional("UseColor", Options.UseColor);235    IO.mapOptional("SystemHeaders", Options.SystemHeaders);236    IO.mapOptional("CustomChecks", Options.CustomChecks);237  }238};239 240} // namespace llvm::yaml241 242namespace clang::tidy {243 244ClangTidyOptions ClangTidyOptions::getDefaults() {245  ClangTidyOptions Options;246  Options.Checks = "";247  Options.WarningsAsErrors = "";248  Options.HeaderFileExtensions = {"", "h", "hh", "hpp", "hxx"};249  Options.ImplementationFileExtensions = {"c", "cc", "cpp", "cxx"};250  Options.HeaderFilterRegex = ".*";251  Options.ExcludeHeaderFilterRegex = "";252  Options.SystemHeaders = false;253  Options.FormatStyle = "none";254  Options.User = std::nullopt;255  for (const ClangTidyModuleRegistry::entry &Module :256       ClangTidyModuleRegistry::entries())257    Options.mergeWith(Module.instantiate()->getModuleOptions(), 0);258  return Options;259}260 261template <typename T>262static void mergeVectors(std::optional<T> &Dest, const std::optional<T> &Src) {263  if (Src) {264    if (Dest)265      Dest->insert(Dest->end(), Src->begin(), Src->end());266    else267      Dest = Src;268  }269}270 271static void mergeCommaSeparatedLists(std::optional<std::string> &Dest,272                                     const std::optional<std::string> &Src) {273  if (Src)274    Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src;275}276 277template <typename T>278static void overrideValue(std::optional<T> &Dest, const std::optional<T> &Src) {279  if (Src)280    Dest = Src;281}282 283ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,284                                              unsigned Order) {285  mergeCommaSeparatedLists(Checks, Other.Checks);286  mergeCommaSeparatedLists(WarningsAsErrors, Other.WarningsAsErrors);287  overrideValue(HeaderFileExtensions, Other.HeaderFileExtensions);288  overrideValue(ImplementationFileExtensions,289                Other.ImplementationFileExtensions);290  overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex);291  overrideValue(ExcludeHeaderFilterRegex, Other.ExcludeHeaderFilterRegex);292  overrideValue(SystemHeaders, Other.SystemHeaders);293  overrideValue(FormatStyle, Other.FormatStyle);294  overrideValue(User, Other.User);295  overrideValue(UseColor, Other.UseColor);296  mergeVectors(ExtraArgs, Other.ExtraArgs);297  mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore);298  // FIXME: how to handle duplicate names check?299  mergeVectors(CustomChecks, Other.CustomChecks);300  for (const auto &KeyValue : Other.CheckOptions) {301    CheckOptions.insert_or_assign(302        KeyValue.getKey(),303        ClangTidyValue(KeyValue.getValue().Value,304                       KeyValue.getValue().Priority + Order));305  }306  return *this;307}308 309ClangTidyOptions ClangTidyOptions::merge(const ClangTidyOptions &Other,310                                         unsigned Order) const {311  ClangTidyOptions Result = *this;312  Result.mergeWith(Other, Order);313  return Result;314}315 316const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] =317    "clang-tidy binary";318const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] =319    "command-line option '-checks'";320const char321    ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] =322        "command-line option '-config'";323 324ClangTidyOptions325ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) {326  ClangTidyOptions Result;327  unsigned Priority = 0;328  for (auto &Source : getRawOptions(FileName))329    Result.mergeWith(Source.first, ++Priority);330  return Result;331}332 333std::vector<OptionsSource>334DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) {335  std::vector<OptionsSource> Result;336  Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary);337  return Result;338}339 340ConfigOptionsProvider::ConfigOptionsProvider(341    ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,342    ClangTidyOptions ConfigOptions, ClangTidyOptions OverrideOptions,343    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)344    : FileOptionsBaseProvider(std::move(GlobalOptions),345                              std::move(DefaultOptions),346                              std::move(OverrideOptions), std::move(FS)),347      ConfigOptions(std::move(ConfigOptions)) {}348 349std::vector<OptionsSource>350ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) {351  std::vector<OptionsSource> RawOptions =352      DefaultOptionsProvider::getRawOptions(FileName);353  if (ConfigOptions.InheritParentConfig.value_or(false)) {354    LLVM_DEBUG(llvm::dbgs()355               << "Getting options for file " << FileName << "...\n");356 357    llvm::ErrorOr<llvm::SmallString<128>> AbsoluteFilePath =358        getNormalizedAbsolutePath(FileName);359    if (AbsoluteFilePath) {360      addRawFileOptions(AbsoluteFilePath->str(), RawOptions);361    }362  }363  RawOptions.emplace_back(ConfigOptions,364                          OptionsSourceTypeConfigCommandLineOption);365  RawOptions.emplace_back(OverrideOptions,366                          OptionsSourceTypeCheckCommandLineOption);367  return RawOptions;368}369 370FileOptionsBaseProvider::FileOptionsBaseProvider(371    ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,372    ClangTidyOptions OverrideOptions,373    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)374    : DefaultOptionsProvider(std::move(GlobalOptions),375                             std::move(DefaultOptions)),376      OverrideOptions(std::move(OverrideOptions)), FS(std::move(VFS)) {377  if (!FS)378    FS = llvm::vfs::getRealFileSystem();379  ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);380}381 382FileOptionsBaseProvider::FileOptionsBaseProvider(383    ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,384    ClangTidyOptions OverrideOptions,385    FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)386    : DefaultOptionsProvider(std::move(GlobalOptions),387                             std::move(DefaultOptions)),388      OverrideOptions(std::move(OverrideOptions)),389      ConfigHandlers(std::move(ConfigHandlers)) {}390 391llvm::ErrorOr<llvm::SmallString<128>>392FileOptionsBaseProvider::getNormalizedAbsolutePath(llvm::StringRef Path) {393  assert(FS && "FS must be set.");394  llvm::SmallString<128> NormalizedAbsolutePath = {Path};395  const std::error_code Err = FS->makeAbsolute(NormalizedAbsolutePath);396  if (Err)397    return Err;398  llvm::sys::path::remove_dots(NormalizedAbsolutePath, /*remove_dot_dot=*/true);399  return NormalizedAbsolutePath;400}401 402void FileOptionsBaseProvider::addRawFileOptions(403    llvm::StringRef AbsolutePath, std::vector<OptionsSource> &CurOptions) {404  auto CurSize = CurOptions.size();405  // Look for a suitable configuration file in all parent directories of the406  // file. Start with the immediate parent directory and move up.407  StringRef RootPath = llvm::sys::path::parent_path(AbsolutePath);408  auto MemorizedConfigFile =409      [this, &RootPath](StringRef CurrentPath) -> std::optional<OptionsSource> {410    const auto Iter = CachedOptions.Memorized.find(CurrentPath);411    if (Iter != CachedOptions.Memorized.end())412      return CachedOptions.Storage[Iter->second];413    std::optional<OptionsSource> OptionsSource = tryReadConfigFile(CurrentPath);414    if (OptionsSource) {415      const size_t Index = CachedOptions.Storage.size();416      CachedOptions.Storage.emplace_back(OptionsSource.value());417      while (RootPath != CurrentPath) {418        LLVM_DEBUG(llvm::dbgs()419                   << "Caching configuration for path " << RootPath << ".\n");420        CachedOptions.Memorized[RootPath] = Index;421        RootPath = llvm::sys::path::parent_path(RootPath);422      }423      CachedOptions.Memorized[CurrentPath] = Index;424      RootPath = llvm::sys::path::parent_path(CurrentPath);425    }426    return OptionsSource;427  };428  for (StringRef CurrentPath = RootPath; !CurrentPath.empty();429       CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {430    if (std::optional<OptionsSource> Result =431            MemorizedConfigFile(CurrentPath)) {432      CurOptions.emplace_back(Result.value());433      if (!Result->first.InheritParentConfig.value_or(false))434        break;435    }436  }437  // Reverse order of file configs because closer configs should have higher438  // priority.439  std::reverse(CurOptions.begin() + CurSize, CurOptions.end());440}441 442FileOptionsProvider::FileOptionsProvider(443    ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,444    ClangTidyOptions OverrideOptions,445    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)446    : FileOptionsBaseProvider(std::move(GlobalOptions),447                              std::move(DefaultOptions),448                              std::move(OverrideOptions), std::move(VFS)) {}449 450FileOptionsProvider::FileOptionsProvider(451    ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,452    ClangTidyOptions OverrideOptions,453    FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)454    : FileOptionsBaseProvider(455          std::move(GlobalOptions), std::move(DefaultOptions),456          std::move(OverrideOptions), std::move(ConfigHandlers)) {}457 458// FIXME: This method has some common logic with clang::format::getStyle().459// Consider pulling out common bits to a findParentFileWithName function or460// similar.461std::vector<OptionsSource>462FileOptionsProvider::getRawOptions(StringRef FileName) {463  LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName464                          << "...\n");465 466  const llvm::ErrorOr<llvm::SmallString<128>> AbsoluteFilePath =467      getNormalizedAbsolutePath(FileName);468  if (!AbsoluteFilePath)469    return {};470 471  std::vector<OptionsSource> RawOptions =472      DefaultOptionsProvider::getRawOptions(AbsoluteFilePath->str());473  addRawFileOptions(AbsoluteFilePath->str(), RawOptions);474  const OptionsSource CommandLineOptions(475      OverrideOptions, OptionsSourceTypeCheckCommandLineOption);476 477  RawOptions.push_back(CommandLineOptions);478  return RawOptions;479}480 481std::optional<OptionsSource>482FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) {483  assert(!Directory.empty());484 485  llvm::ErrorOr<llvm::vfs::Status> DirectoryStatus = FS->status(Directory);486 487  if (!DirectoryStatus || !DirectoryStatus->isDirectory()) {488    llvm::errs() << "Error reading configuration from " << Directory489                 << ": directory doesn't exist.\n";490    return std::nullopt;491  }492 493  for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) {494    SmallString<128> ConfigFile(Directory);495    llvm::sys::path::append(ConfigFile, ConfigHandler.first);496    LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");497 498    llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(ConfigFile);499 500    if (!FileStatus || !FileStatus->isRegularFile())501      continue;502 503    llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =504        FS->getBufferForFile(ConfigFile);505    if (const std::error_code EC = Text.getError()) {506      llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message()507                   << "\n";508      continue;509    }510 511    // Skip empty files, e.g. files opened for writing via shell output512    // redirection.513    if ((*Text)->getBuffer().empty())514      continue;515    llvm::ErrorOr<ClangTidyOptions> ParsedOptions =516        ConfigHandler.second({(*Text)->getBuffer(), ConfigFile});517    if (!ParsedOptions) {518      if (ParsedOptions.getError())519        llvm::errs() << "Error parsing " << ConfigFile << ": "520                     << ParsedOptions.getError().message() << "\n";521      continue;522    }523    return OptionsSource(*ParsedOptions, std::string(ConfigFile));524  }525  return std::nullopt;526}527 528/// Parses -line-filter option and stores it to the \c Options.529std::error_code parseLineFilter(StringRef LineFilter,530                                clang::tidy::ClangTidyGlobalOptions &Options) {531  llvm::yaml::Input Input(LineFilter);532  Input >> Options.LineFilter;533  return Input.error();534}535 536llvm::ErrorOr<ClangTidyOptions>537parseConfiguration(llvm::MemoryBufferRef Config) {538  llvm::yaml::Input Input(Config);539  ClangTidyOptions Options;540  Input >> Options;541  if (Input.error())542    return Input.error();543  return Options;544}545 546static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {547  (*reinterpret_cast<DiagCallback *>(Ctx))(Diag);548}549 550llvm::ErrorOr<ClangTidyOptions>551parseConfigurationWithDiags(llvm::MemoryBufferRef Config,552                            DiagCallback Handler) {553  llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr,554                          &Handler);555  ClangTidyOptions Options;556  Input >> Options;557  if (Input.error())558    return Input.error();559  return Options;560}561 562std::string configurationAsText(const ClangTidyOptions &Options) {563  std::string Text;564  llvm::raw_string_ostream Stream(Text);565  llvm::yaml::Output Output(Stream);566  // We use the same mapping method for input and output, so we need a non-const567  // reference here.568  ClangTidyOptions NonConstValue = Options;569  Output << NonConstValue;570  return Stream.str();571}572 573} // namespace clang::tidy574