brintos

brintos / llvm-project-archived public Read only

0
0
Text · 31.6 KiB · 6716d90 Raw
842 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///  \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext10///  and ClangTidyError classes.11///12///  This tool uses the Clang Tooling infrastructure, see13///    https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html14///  for details on setting it up with LLVM source tree.15///16//===----------------------------------------------------------------------===//17 18#include "ClangTidyDiagnosticConsumer.h"19#include "ClangTidyOptions.h"20#include "GlobList.h"21#include "NoLintDirectiveHandler.h"22#include "clang/AST/ASTContext.h"23#include "clang/AST/ASTDiagnostic.h"24#include "clang/AST/Attr.h"25#include "clang/AST/Expr.h"26#include "clang/Basic/CharInfo.h"27#include "clang/Basic/Diagnostic.h"28#include "clang/Basic/DiagnosticOptions.h"29#include "clang/Basic/FileManager.h"30#include "clang/Basic/SourceManager.h"31#include "clang/Frontend/DiagnosticRenderer.h"32#include "clang/Lex/Lexer.h"33#include "clang/Tooling/Core/Diagnostic.h"34#include "clang/Tooling/Core/Replacement.h"35#include "llvm/ADT/BitVector.h"36#include "llvm/ADT/STLExtras.h"37#include "llvm/ADT/StringMap.h"38#include "llvm/Support/FormatVariadic.h"39#include "llvm/Support/Regex.h"40#include <optional>41#include <tuple>42#include <utility>43#include <vector>44using namespace clang;45using namespace tidy;46 47namespace {48class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {49public:50  ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,51                              DiagnosticOptions &DiagOpts,52                              ClangTidyError &Error)53      : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}54 55protected:56  void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc,57                             DiagnosticsEngine::Level Level, StringRef Message,58                             ArrayRef<CharSourceRange> Ranges,59                             DiagOrStoredDiag Info) override {60    // Remove check name from the message.61    // FIXME: Remove this once there's a better way to pass check names than62    // appending the check name to the message in ClangTidyContext::diag and63    // using getCustomDiagID.64    const std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]";65    Message.consume_back(CheckNameInMessage);66 67    auto TidyMessage =68        Loc.isValid()69            ? tooling::DiagnosticMessage(Message, Loc.getManager(), Loc)70            : tooling::DiagnosticMessage(Message);71 72    // Make sure that if a TokenRange is received from the check it is unfurled73    // into a real CharRange for the diagnostic printer later.74    // Whatever we store here gets decoupled from the current SourceManager, so75    // we **have to** know the exact position and length of the highlight.76    auto ToCharRange = [this, &Loc](const CharSourceRange &SourceRange) {77      if (SourceRange.isCharRange())78        return SourceRange;79      assert(SourceRange.isTokenRange());80      const SourceLocation End = Lexer::getLocForEndOfToken(81          SourceRange.getEnd(), 0, Loc.getManager(), LangOpts);82      return CharSourceRange::getCharRange(SourceRange.getBegin(), End);83    };84 85    // We are only interested in valid ranges.86    auto ValidRanges =87        llvm::make_filter_range(Ranges, [](const CharSourceRange &R) {88          return R.getAsRange().isValid();89        });90 91    if (Level == DiagnosticsEngine::Note) {92      Error.Notes.push_back(TidyMessage);93      for (const CharSourceRange &SourceRange : ValidRanges)94        Error.Notes.back().Ranges.emplace_back(Loc.getManager(),95                                               ToCharRange(SourceRange));96      return;97    }98    assert(Error.Message.Message.empty() && "Overwriting a diagnostic message");99    Error.Message = TidyMessage;100    for (const CharSourceRange &SourceRange : ValidRanges)101      Error.Message.Ranges.emplace_back(Loc.getManager(),102                                        ToCharRange(SourceRange));103  }104 105  void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,106                         DiagnosticsEngine::Level Level,107                         ArrayRef<CharSourceRange> Ranges) override {}108 109  void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,110                       SmallVectorImpl<CharSourceRange> &Ranges,111                       ArrayRef<FixItHint> Hints) override {112    assert(Loc.isValid());113    tooling::DiagnosticMessage *DiagWithFix =114        Level == DiagnosticsEngine::Note ? &Error.Notes.back() : &Error.Message;115 116    for (const auto &FixIt : Hints) {117      const CharSourceRange Range = FixIt.RemoveRange;118      assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&119             "Invalid range in the fix-it hint.");120      assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&121             "Only file locations supported in fix-it hints.");122 123      const tooling::Replacement Replacement(Loc.getManager(), Range,124                                             FixIt.CodeToInsert);125      llvm::Error Err =126          DiagWithFix->Fix[Replacement.getFilePath()].add(Replacement);127      // FIXME: better error handling (at least, don't let other replacements be128      // applied).129      if (Err) {130        llvm::errs() << "Fix conflicts with existing fix! "131                     << llvm::toString(std::move(Err)) << "\n";132        assert(false && "Fix conflicts with existing fix!");133      }134    }135  }136 137  void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override {}138 139  void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,140                          StringRef ModuleName) override {}141 142  void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,143                                  StringRef ModuleName) override {}144 145  void endDiagnostic(DiagOrStoredDiag D,146                     DiagnosticsEngine::Level Level) override {147    assert(!Error.Message.Message.empty() && "Message has not been set");148  }149 150private:151  ClangTidyError &Error;152};153} // end anonymous namespace154 155ClangTidyError::ClangTidyError(StringRef CheckName,156                               ClangTidyError::Level DiagLevel,157                               StringRef BuildDirectory, bool IsWarningAsError)158    : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),159      IsWarningAsError(IsWarningAsError) {}160 161ClangTidyContext::ClangTidyContext(162    std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,163    bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing,164    bool ExperimentalCustomChecks)165    : OptionsProvider(std::move(OptionsProvider)),166      AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers),167      EnableModuleHeadersParsing(EnableModuleHeadersParsing),168      ExperimentalCustomChecks(ExperimentalCustomChecks) {169  // Before the first translation unit we can get errors related to command-line170  // parsing, use dummy string for the file name in this case.171  setCurrentFile("dummy");172}173 174ClangTidyContext::~ClangTidyContext() = default;175 176DiagnosticBuilder ClangTidyContext::diag(177    StringRef CheckName, SourceLocation Loc, StringRef Description,178    DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {179  assert(Loc.isValid());180  const unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(181      Level, (Description + " [" + CheckName + "]").str());182  CheckNamesByDiagnosticID.try_emplace(ID, CheckName);183  return DiagEngine->Report(Loc, ID);184}185 186DiagnosticBuilder ClangTidyContext::diag(187    StringRef CheckName, StringRef Description,188    DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {189  const unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(190      Level, (Description + " [" + CheckName + "]").str());191  CheckNamesByDiagnosticID.try_emplace(ID, CheckName);192  return DiagEngine->Report(ID);193}194 195DiagnosticBuilder ClangTidyContext::diag(const tooling::Diagnostic &Error) {196  SourceManager &SM = DiagEngine->getSourceManager();197  FileManager &FM = SM.getFileManager();198  const FileEntryRef File =199      llvm::cantFail(FM.getFileRef(Error.Message.FilePath));200  const FileID ID = SM.getOrCreateFileID(File, SrcMgr::C_User);201  const SourceLocation FileStartLoc = SM.getLocForStartOfFile(ID);202  const SourceLocation Loc = FileStartLoc.getLocWithOffset(203      static_cast<SourceLocation::IntTy>(Error.Message.FileOffset));204  return diag(Error.DiagnosticName, Loc, Error.Message.Message,205              static_cast<DiagnosticIDs::Level>(Error.DiagLevel));206}207 208DiagnosticBuilder ClangTidyContext::configurationDiag(209    StringRef Message,210    DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {211  return diag("clang-tidy-config", Message, Level);212}213 214bool ClangTidyContext::shouldSuppressDiagnostic(215    DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,216    SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,217    bool EnableNoLintBlocks) {218  const std::string CheckName = getCheckName(Info.getID());219  return NoLintHandler.shouldSuppress(DiagLevel, Info, CheckName, NoLintErrors,220                                      AllowIO, EnableNoLintBlocks);221}222 223void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {224  DiagEngine->setSourceManager(SourceMgr);225}226 227static bool parseFileExtensions(llvm::ArrayRef<std::string> AllFileExtensions,228                                FileExtensionsSet &FileExtensions) {229  FileExtensions.clear();230  for (const StringRef Suffix : AllFileExtensions) {231    StringRef Extension = Suffix.trim();232    if (!llvm::all_of(Extension, isAlphanumeric))233      return false;234    FileExtensions.insert(Extension);235  }236  return true;237}238 239void ClangTidyContext::setCurrentFile(StringRef File) {240  CurrentFile = std::string(File);241  CurrentOptions = getOptionsForFile(CurrentFile);242  CheckFilter = std::make_unique<CachedGlobList>(*getOptions().Checks);243  WarningAsErrorFilter =244      std::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors);245  if (!parseFileExtensions(*getOptions().HeaderFileExtensions,246                           HeaderFileExtensions))247    this->configurationDiag("Invalid header file extensions");248  if (!parseFileExtensions(*getOptions().ImplementationFileExtensions,249                           ImplementationFileExtensions))250    this->configurationDiag("Invalid implementation file extensions");251}252 253void ClangTidyContext::setASTContext(ASTContext *Context) {254  DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);255  LangOpts = Context->getLangOpts();256}257 258const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const {259  return OptionsProvider->getGlobalOptions();260}261 262const ClangTidyOptions &ClangTidyContext::getOptions() const {263  return CurrentOptions;264}265 266ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const {267  // Merge options on top of getDefaults() as a safeguard against options with268  // unset values.269  return ClangTidyOptions::getDefaults().merge(270      OptionsProvider->getOptions(File), 0);271}272 273void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; }274 275void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix) {276  ProfilePrefix = std::string(Prefix);277}278 279std::optional<ClangTidyProfiling::StorageParams>280ClangTidyContext::getProfileStorageParams() const {281  if (ProfilePrefix.empty())282    return std::nullopt;283 284  return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile);285}286 287bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const {288  assert(CheckFilter != nullptr);289  return CheckFilter->contains(CheckName);290}291 292bool ClangTidyContext::treatAsError(StringRef CheckName) const {293  assert(WarningAsErrorFilter != nullptr);294  return WarningAsErrorFilter->contains(CheckName);295}296 297std::string ClangTidyContext::getCheckName(unsigned DiagnosticID) const {298  const std::string ClangWarningOption = std::string(299      DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID));300  if (!ClangWarningOption.empty())301    return "clang-diagnostic-" + ClangWarningOption;302  const llvm::DenseMap<unsigned, std::string>::const_iterator I =303      CheckNamesByDiagnosticID.find(DiagnosticID);304  if (I != CheckNamesByDiagnosticID.end())305    return I->second;306  return "";307}308 309ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(310    ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine,311    bool RemoveIncompatibleErrors, bool GetFixesFromNotes,312    bool EnableNolintBlocks)313    : Context(Ctx), ExternalDiagEngine(ExternalDiagEngine),314      RemoveIncompatibleErrors(RemoveIncompatibleErrors),315      GetFixesFromNotes(GetFixesFromNotes),316      EnableNolintBlocks(EnableNolintBlocks) {}317 318void ClangTidyDiagnosticConsumer::finalizeLastError() {319  if (!Errors.empty()) {320    const ClangTidyError &Error = Errors.back();321    if (Error.DiagnosticName == "clang-tidy-config") {322      // Never ignore these.323    } else if (!Context.isCheckEnabled(Error.DiagnosticName) &&324               Error.DiagLevel != ClangTidyError::Error) {325      ++Context.Stats.ErrorsIgnoredCheckFilter;326      Errors.pop_back();327    } else if (!LastErrorRelatesToUserCode) {328      ++Context.Stats.ErrorsIgnoredNonUserCode;329      Errors.pop_back();330    } else if (!LastErrorPassesLineFilter) {331      ++Context.Stats.ErrorsIgnoredLineFilter;332      Errors.pop_back();333    } else {334      ++Context.Stats.ErrorsDisplayed;335    }336  }337  LastErrorRelatesToUserCode = false;338  LastErrorPassesLineFilter = false;339}340 341namespace clang::tidy {342 343const llvm::StringMap<tooling::Replacements> *344getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix) {345  if (!Diagnostic.Message.Fix.empty())346    return &Diagnostic.Message.Fix;347  if (!AnyFix)348    return nullptr;349  const llvm::StringMap<tooling::Replacements> *Result = nullptr;350  for (const auto &Note : Diagnostic.Notes) {351    if (!Note.Fix.empty()) {352      if (Result)353        // We have 2 different fixes in notes, bail out.354        return nullptr;355      Result = &Note.Fix;356    }357  }358  return Result;359}360 361} // namespace clang::tidy362 363void ClangTidyDiagnosticConsumer::BeginSourceFile(const LangOptions &LangOpts,364                                                  const Preprocessor *PP) {365  DiagnosticConsumer::BeginSourceFile(LangOpts, PP);366 367  assert(!InSourceFile);368  InSourceFile = true;369}370 371void ClangTidyDiagnosticConsumer::EndSourceFile() {372  assert(InSourceFile);373  InSourceFile = false;374 375  DiagnosticConsumer::EndSourceFile();376}377 378void ClangTidyDiagnosticConsumer::HandleDiagnostic(379    DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {380  // A diagnostic should not be reported outside of a381  // BeginSourceFile()/EndSourceFile() pair if it has a source location.382  assert(InSourceFile || Info.getLocation().isInvalid());383 384  if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)385    return;386 387  SmallVector<tooling::Diagnostic, 1> SuppressionErrors;388  if (Context.shouldSuppressDiagnostic(DiagLevel, Info, SuppressionErrors,389                                       EnableNolintBlocks)) {390    ++Context.Stats.ErrorsIgnoredNOLINT;391    // Ignored a warning, should ignore related notes as well392    LastErrorWasIgnored = true;393    for (const auto &Error : SuppressionErrors)394      Context.diag(Error);395    return;396  }397 398  LastErrorWasIgnored = false;399  // Count warnings/errors.400  DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);401 402  if (DiagLevel == DiagnosticsEngine::Note) {403    assert(!Errors.empty() &&404           "A diagnostic note can only be appended to a message.");405  } else {406    finalizeLastError();407    std::string CheckName = Context.getCheckName(Info.getID());408    if (CheckName.empty()) {409      // This is a compiler diagnostic without a warning option. Assign check410      // name based on its level.411      switch (DiagLevel) {412      case DiagnosticsEngine::Error:413      case DiagnosticsEngine::Fatal:414        CheckName = "clang-diagnostic-error";415        break;416      case DiagnosticsEngine::Warning:417        CheckName = "clang-diagnostic-warning";418        break;419      case DiagnosticsEngine::Remark:420        CheckName = "clang-diagnostic-remark";421        break;422      default:423        CheckName = "clang-diagnostic-unknown";424        break;425      }426    }427 428    ClangTidyError::Level Level = ClangTidyError::Warning;429    if (DiagLevel == DiagnosticsEngine::Error ||430        DiagLevel == DiagnosticsEngine::Fatal) {431      // Force reporting of Clang errors regardless of filters and non-user432      // code.433      Level = ClangTidyError::Error;434      LastErrorRelatesToUserCode = true;435      LastErrorPassesLineFilter = true;436    } else if (DiagLevel == DiagnosticsEngine::Remark) {437      Level = ClangTidyError::Remark;438    }439 440    const bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&441                                  Context.treatAsError(CheckName);442    Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),443                        IsWarningAsError);444  }445 446  if (ExternalDiagEngine) {447    // If there is an external diagnostics engine, like in the448    // ClangTidyPluginAction case, forward the diagnostics to it.449    forwardDiagnostic(Info);450  } else {451    ClangTidyDiagnosticRenderer Converter(452        Context.getLangOpts(), Context.DiagEngine->getDiagnosticOptions(),453        Errors.back());454    SmallString<100> Message;455    Info.FormatDiagnostic(Message);456    FullSourceLoc Loc;457    if (Info.hasSourceManager())458      Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager());459    else if (Context.DiagEngine->hasSourceManager())460      Loc = FullSourceLoc(Info.getLocation(),461                          Context.DiagEngine->getSourceManager());462    Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),463                             Info.getFixItHints());464  }465 466  if (Info.hasSourceManager())467    checkFilters(Info.getLocation(), Info.getSourceManager());468 469  for (const auto &Error : SuppressionErrors)470    Context.diag(Error);471}472 473bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,474                                                   unsigned LineNumber) const {475  if (Context.getGlobalOptions().LineFilter.empty())476    return true;477  for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {478    if (FileName.ends_with(Filter.Name)) {479      if (Filter.LineRanges.empty())480        return true;481      for (const FileFilter::LineRange &Range : Filter.LineRanges) {482        if (Range.first <= LineNumber && LineNumber <= Range.second)483          return true;484      }485      return false;486    }487  }488  return false;489}490 491void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) {492  // Acquire a diagnostic ID also in the external diagnostics engine.493  auto DiagLevelAndFormatString =494      Context.getDiagLevelAndFormatString(Info.getID(), Info.getLocation());495  const unsigned ExternalID =496      ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID(497          DiagLevelAndFormatString.first, DiagLevelAndFormatString.second);498 499  // Forward the details.500  auto Builder = ExternalDiagEngine->Report(Info.getLocation(), ExternalID);501  for (const FixItHint &Hint : Info.getFixItHints())502    Builder << Hint;503  for (auto Range : Info.getRanges())504    Builder << Range;505  for (unsigned Index = 0; Index < Info.getNumArgs(); ++Index) {506    const DiagnosticsEngine::ArgumentKind Kind = Info.getArgKind(Index);507    switch (Kind) {508    case clang::DiagnosticsEngine::ak_std_string:509      Builder << Info.getArgStdStr(Index);510      break;511    case clang::DiagnosticsEngine::ak_c_string:512      Builder << Info.getArgCStr(Index);513      break;514    case clang::DiagnosticsEngine::ak_sint:515      Builder << Info.getArgSInt(Index);516      break;517    case clang::DiagnosticsEngine::ak_uint:518      Builder << Info.getArgUInt(Index);519      break;520    case clang::DiagnosticsEngine::ak_tokenkind:521      Builder << static_cast<tok::TokenKind>(Info.getRawArg(Index));522      break;523    case clang::DiagnosticsEngine::ak_identifierinfo:524      Builder << Info.getArgIdentifier(Index);525      break;526    case clang::DiagnosticsEngine::ak_qual:527      Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(Index));528      break;529    case clang::DiagnosticsEngine::ak_qualtype:530      Builder << QualType::getFromOpaquePtr((void *)Info.getRawArg(Index));531      break;532    case clang::DiagnosticsEngine::ak_declarationname:533      Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(Index));534      break;535    case clang::DiagnosticsEngine::ak_nameddecl:536      Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Index));537      break;538    case clang::DiagnosticsEngine::ak_nestednamespec:539      Builder << NestedNameSpecifier::getFromVoidPointer(540          reinterpret_cast<void *>(Info.getRawArg(Index)));541      break;542    case clang::DiagnosticsEngine::ak_declcontext:543      Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(Index));544      break;545    case clang::DiagnosticsEngine::ak_qualtype_pair:546      assert(false); // This one is not passed around.547      break;548    case clang::DiagnosticsEngine::ak_attr:549      Builder << reinterpret_cast<Attr *>(Info.getRawArg(Index));550      break;551    case clang::DiagnosticsEngine::ak_attr_info:552      Builder << reinterpret_cast<AttributeCommonInfo *>(Info.getRawArg(Index));553      break;554    case clang::DiagnosticsEngine::ak_addrspace:555      Builder << static_cast<LangAS>(Info.getRawArg(Index));556      break;557    case clang::DiagnosticsEngine::ak_expr:558      Builder << reinterpret_cast<const Expr *>(Info.getRawArg(Index));559    }560  }561}562 563void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,564                                               const SourceManager &Sources) {565  // Invalid location may mean a diagnostic in a command line, don't skip these.566  if (!Location.isValid()) {567    LastErrorRelatesToUserCode = true;568    LastErrorPassesLineFilter = true;569    return;570  }571 572  if (!*Context.getOptions().SystemHeaders &&573      (Sources.isInSystemHeader(Location) || Sources.isInSystemMacro(Location)))574    return;575 576  // FIXME: We start with a conservative approach here, but the actual type of577  // location needed depends on the check (in particular, where this check wants578  // to apply fixes).579  const FileID FID = Sources.getDecomposedExpansionLoc(Location).first;580  OptionalFileEntryRef File = Sources.getFileEntryRefForID(FID);581 582  // -DMACRO definitions on the command line have locations in a virtual buffer583  // that doesn't have a FileEntry. Don't skip these as well.584  if (!File) {585    LastErrorRelatesToUserCode = true;586    LastErrorPassesLineFilter = true;587    return;588  }589 590  const StringRef FileName(File->getName());591  LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||592                               Sources.isInMainFile(Location) ||593                               (getHeaderFilter()->match(FileName) &&594                                !getExcludeHeaderFilter()->match(FileName));595 596  const unsigned LineNumber = Sources.getExpansionLineNumber(Location);597  LastErrorPassesLineFilter =598      LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);599}600 601llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {602  if (!HeaderFilter)603    HeaderFilter =604        std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);605  return HeaderFilter.get();606}607 608llvm::Regex *ClangTidyDiagnosticConsumer::getExcludeHeaderFilter() {609  if (!ExcludeHeaderFilter)610    ExcludeHeaderFilter = std::make_unique<llvm::Regex>(611        *Context.getOptions().ExcludeHeaderFilterRegex);612  return ExcludeHeaderFilter.get();613}614 615void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {616  // Each error is modelled as the set of intervals in which it applies617  // replacements. To detect overlapping replacements, we use a sweep line618  // algorithm over these sets of intervals.619  // An event here consists of the opening or closing of an interval. During the620  // process, we maintain a counter with the amount of open intervals. If we621  // find an endpoint of an interval and this counter is different from 0, it622  // means that this interval overlaps with another one, so we set it as623  // inapplicable.624  struct Event {625    // An event can be either the begin or the end of an interval.626    enum EventType {627      ET_Begin = 1,628      ET_Insert = 0,629      ET_End = -1,630    };631 632    Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,633          unsigned ErrorSize)634        : Type(Type), ErrorId(ErrorId) {635      // The events are going to be sorted by their position. In case of draw:636      //637      // * If an interval ends at the same position at which other interval638      //   begins, this is not an overlapping, so we want to remove the ending639      //   interval before adding the starting one: end events have higher640      //   priority than begin events.641      //642      // * If we have several begin points at the same position, we will mark as643      //   inapplicable the ones that we process later, so the first one has to644      //   be the one with the latest end point, because this one will contain645      //   all the other intervals. For the same reason, if we have several end646      //   points in the same position, the last one has to be the one with the647      //   earliest begin point. In both cases, we sort non-increasingly by the648      //   position of the complementary.649      //650      // * In case of two equal intervals, the one whose error is bigger can651      //   potentially contain the other one, so we want to process its begin652      //   points before and its end points later.653      //654      // * Finally, if we have two equal intervals whose errors have the same655      //   size, none of them will be strictly contained inside the other.656      //   Sorting by ErrorId will guarantee that the begin point of the first657      //   one will be processed before, disallowing the second one, and the658      //   end point of the first one will also be processed before,659      //   disallowing the first one.660      switch (Type) {661      case ET_Begin:662        Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);663        break;664      case ET_Insert:665        Priority = std::make_tuple(Begin, Type, -End, ErrorSize, ErrorId);666        break;667      case ET_End:668        Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);669        break;670      }671    }672 673    bool operator<(const Event &Other) const {674      return Priority < Other.Priority;675    }676 677    // Determines if this event is the begin or the end of an interval.678    EventType Type;679    // The index of the error to which the interval that generated this event680    // belongs.681    unsigned ErrorId;682    // The events will be sorted based on this field.683    std::tuple<unsigned, EventType, int, int, unsigned> Priority;684  };685 686  removeDuplicatedDiagnosticsOfAliasCheckers();687 688  // Compute error sizes.689  std::vector<int> Sizes;690  std::vector<691      std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>>692      ErrorFixes;693  for (auto &Error : Errors) {694    if (const auto *Fix = getFixIt(Error, GetFixesFromNotes))695      ErrorFixes.emplace_back(696          &Error, const_cast<llvm::StringMap<tooling::Replacements> *>(Fix));697  }698  for (const auto &ErrorAndFix : ErrorFixes) {699    int Size = 0;700    for (const auto &FileAndReplaces : *ErrorAndFix.second) {701      for (const auto &Replace : FileAndReplaces.second)702        Size += Replace.getLength();703    }704    Sizes.push_back(Size);705  }706 707  // Build events from error intervals.708  llvm::StringMap<std::vector<Event>> FileEvents;709  for (unsigned I = 0; I < ErrorFixes.size(); ++I) {710    for (const auto &FileAndReplace : *ErrorFixes[I].second) {711      for (const auto &Replace : FileAndReplace.second) {712        const unsigned Begin = Replace.getOffset();713        const unsigned End = Begin + Replace.getLength();714        auto &Events = FileEvents[Replace.getFilePath()];715        if (Begin == End) {716          Events.emplace_back(Begin, End, Event::ET_Insert, I, Sizes[I]);717        } else {718          Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);719          Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);720        }721      }722    }723  }724 725  llvm::BitVector Apply(ErrorFixes.size(), true);726  for (auto &FileAndEvents : FileEvents) {727    std::vector<Event> &Events = FileAndEvents.second;728    // Sweep.729    llvm::sort(Events);730    int OpenIntervals = 0;731    for (const auto &Event : Events) {732      switch (Event.Type) {733      case Event::ET_Begin:734        if (OpenIntervals++ != 0)735          Apply[Event.ErrorId] = false;736        break;737      case Event::ET_Insert:738        if (OpenIntervals != 0)739          Apply[Event.ErrorId] = false;740        break;741      case Event::ET_End:742        if (--OpenIntervals != 0)743          Apply[Event.ErrorId] = false;744        break;745      }746    }747    assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");748  }749 750  for (unsigned I = 0; I < ErrorFixes.size(); ++I) {751    if (!Apply[I]) {752      ErrorFixes[I].second->clear();753      ErrorFixes[I].first->Notes.emplace_back(754          "this fix will not be applied because it overlaps with another fix");755    }756  }757}758 759namespace {760struct LessClangTidyError {761  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {762    const tooling::DiagnosticMessage &M1 = LHS.Message;763    const tooling::DiagnosticMessage &M2 = RHS.Message;764 765    return std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName,766                    M1.Message) <767           std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message);768  }769};770struct EqualClangTidyError {771  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {772    const LessClangTidyError Less;773    return !Less(LHS, RHS) && !Less(RHS, LHS);774  }775};776} // end anonymous namespace777 778std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() {779  finalizeLastError();780 781  llvm::stable_sort(Errors, LessClangTidyError());782  Errors.erase(llvm::unique(Errors, EqualClangTidyError()), Errors.end());783  if (RemoveIncompatibleErrors)784    removeIncompatibleErrors();785  return std::move(Errors);786}787 788namespace {789struct LessClangTidyErrorWithoutDiagnosticName {790  bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const {791    const tooling::DiagnosticMessage &M1 = LHS->Message;792    const tooling::DiagnosticMessage &M2 = RHS->Message;793 794    return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <795           std::tie(M2.FilePath, M2.FileOffset, M2.Message);796  }797};798} // end anonymous namespace799 800void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {801  using UniqueErrorSet =802      std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>;803  UniqueErrorSet UniqueErrors;804 805  auto IT = Errors.begin();806  while (IT != Errors.end()) {807    ClangTidyError &Error = *IT;808    const std::pair<UniqueErrorSet::iterator, bool> Inserted =809        UniqueErrors.insert(&Error);810 811    // Unique error, we keep it and move along.812    if (Inserted.second) {813      ++IT;814    } else {815      ClangTidyError &ExistingError = **Inserted.first;816      const llvm::StringMap<tooling::Replacements> &CandidateFix =817          Error.Message.Fix;818      const llvm::StringMap<tooling::Replacements> &ExistingFix =819          (*Inserted.first)->Message.Fix;820 821      if (CandidateFix != ExistingFix) {822        // In case of a conflict, don't suggest any fix-it.823        ExistingError.Message.Fix.clear();824        ExistingError.Notes.emplace_back(825            llvm::formatv("cannot apply fix-it because an alias checker has "826                          "suggested a different fix-it; please remove one of "827                          "the checkers ('{0}', '{1}') or "828                          "ensure they are both configured the same",829                          ExistingError.DiagnosticName, Error.DiagnosticName)830                .str());831      }832 833      if (Error.IsWarningAsError)834        ExistingError.IsWarningAsError = true;835 836      // Since it is the same error, we should take it as alias and remove it.837      ExistingError.EnabledDiagnosticAliases.emplace_back(Error.DiagnosticName);838      IT = Errors.erase(IT);839    }840  }841}842