-//===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== //\r
-//\r
-// The LLVM Compiler Infrastructure\r
-//\r
-// This file is distributed under the University of Illinois Open Source\r
-// License. See LICENSE.TXT for details.\r
-//\r
-//===----------------------------------------------------------------------===//\r
-///\r
-/// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext\r
-/// and ClangTidyError classes.\r
-///\r
-/// This tool uses the Clang Tooling infrastructure, see\r
-/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html\r
-/// for details on setting it up with LLVM source tree.\r
-///\r
-//===----------------------------------------------------------------------===//\r
-\r
-#include "ClangTidyDiagnosticConsumer.h"\r
-#include "ClangTidyOptions.h"\r
-#include "clang/AST/ASTDiagnostic.h"\r
-#include "clang/Basic/DiagnosticOptions.h"\r
-#include "clang/Frontend/DiagnosticRenderer.h"\r
-#include "llvm/ADT/SmallString.h"\r
-#include <tuple>\r
-#include <vector>\r
-using namespace clang;\r
-using namespace tidy;\r
-\r
-namespace {\r
-class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {\r
-public:\r
- ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,\r
- DiagnosticOptions *DiagOpts,\r
- ClangTidyError &Error)\r
- : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}\r
-\r
-protected:\r
- void emitDiagnosticMessage(SourceLocation Loc, PresumedLoc PLoc,\r
- DiagnosticsEngine::Level Level, StringRef Message,\r
- ArrayRef<CharSourceRange> Ranges,\r
- const SourceManager *SM,\r
- DiagOrStoredDiag Info) override {\r
- // Remove check name from the message.\r
- // FIXME: Remove this once there's a better way to pass check names than\r
- // appending the check name to the message in ClangTidyContext::diag and\r
- // using getCustomDiagID.\r
- std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]";\r
- if (Message.endswith(CheckNameInMessage))\r
- Message = Message.substr(0, Message.size() - CheckNameInMessage.size());\r
-\r
- auto TidyMessage = Loc.isValid()\r
- ? tooling::DiagnosticMessage(Message, *SM, Loc)\r
- : tooling::DiagnosticMessage(Message);\r
- if (Level == DiagnosticsEngine::Note) {\r
- Error.Notes.push_back(TidyMessage);\r
- return;\r
- }\r
- assert(Error.Message.Message.empty() && "Overwriting a diagnostic message");\r
- Error.Message = TidyMessage;\r
- }\r
-\r
- void emitDiagnosticLoc(SourceLocation Loc, PresumedLoc PLoc,\r
- DiagnosticsEngine::Level Level,\r
- ArrayRef<CharSourceRange> Ranges,\r
- const SourceManager &SM) override {}\r
-\r
- void emitCodeContext(SourceLocation Loc, DiagnosticsEngine::Level Level,\r
- SmallVectorImpl<CharSourceRange> &Ranges,\r
- ArrayRef<FixItHint> Hints,\r
- const SourceManager &SM) override {\r
- assert(Loc.isValid());\r
- for (const auto &FixIt : Hints) {\r
- CharSourceRange Range = FixIt.RemoveRange;\r
- assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&\r
- "Invalid range in the fix-it hint.");\r
- assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&\r
- "Only file locations supported in fix-it hints.");\r
-\r
- tooling::Replacement Replacement(SM, Range, FixIt.CodeToInsert);\r
- llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement);\r
- // FIXME: better error handling (at least, don't let other replacements be\r
- // applied).\r
- if (Err) {\r
- llvm::errs() << "Fix conflicts with existing fix! "\r
- << llvm::toString(std::move(Err)) << "\n";\r
- assert(false && "Fix conflicts with existing fix!");\r
- }\r
- }\r
- }\r
-\r
- void emitIncludeLocation(SourceLocation Loc, PresumedLoc PLoc,\r
- const SourceManager &SM) override {}\r
-\r
- void emitImportLocation(SourceLocation Loc, PresumedLoc PLoc,\r
- StringRef ModuleName,\r
- const SourceManager &SM) override {}\r
-\r
- void emitBuildingModuleLocation(SourceLocation Loc, PresumedLoc PLoc,\r
- StringRef ModuleName,\r
- const SourceManager &SM) override {}\r
-\r
- void endDiagnostic(DiagOrStoredDiag D,\r
- DiagnosticsEngine::Level Level) override {\r
- assert(!Error.Message.Message.empty() && "Message has not been set");\r
- }\r
-\r
-private:\r
- ClangTidyError &Error;\r
-};\r
-} // end anonymous namespace\r
-\r
-ClangTidyError::ClangTidyError(StringRef CheckName,\r
- ClangTidyError::Level DiagLevel,\r
- StringRef BuildDirectory, bool IsWarningAsError)\r
- : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),\r
- IsWarningAsError(IsWarningAsError) {}\r
-\r
-// Returns true if GlobList starts with the negative indicator ('-'), removes it\r
-// from the GlobList.\r
-static bool ConsumeNegativeIndicator(StringRef &GlobList) {\r
- GlobList = GlobList.trim(' ');\r
- if (GlobList.startswith("-")) {\r
- GlobList = GlobList.substr(1);\r
- return true;\r
- }\r
- return false;\r
-}\r
-// Converts first glob from the comma-separated list of globs to Regex and\r
-// removes it and the trailing comma from the GlobList.\r
-static llvm::Regex ConsumeGlob(StringRef &GlobList) {\r
- StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(','));\r
- StringRef Glob = UntrimmedGlob.trim(' ');\r
- GlobList = GlobList.substr(UntrimmedGlob.size() + 1);\r
- SmallString<128> RegexText("^");\r
- StringRef MetaChars("()^$|*+?.[]\\{}");\r
- for (char C : Glob) {\r
- if (C == '*')\r
- RegexText.push_back('.');\r
- else if (MetaChars.find(C) != StringRef::npos)\r
- RegexText.push_back('\\');\r
- RegexText.push_back(C);\r
- }\r
- RegexText.push_back('$');\r
- return llvm::Regex(RegexText);\r
-}\r
-\r
-GlobList::GlobList(StringRef Globs)\r
- : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)),\r
- NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {}\r
-\r
-bool GlobList::contains(StringRef S, bool Contains) {\r
- if (Regex.match(S))\r
- Contains = Positive;\r
-\r
- if (NextGlob)\r
- Contains = NextGlob->contains(S, Contains);\r
- return Contains;\r
-}\r
-\r
-ClangTidyContext::ClangTidyContext(\r
- std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)\r
- : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),\r
- Profile(nullptr) {\r
- // Before the first translation unit we can get errors related to command-line\r
- // parsing, use empty string for the file name in this case.\r
- setCurrentFile("");\r
-}\r
-\r
-DiagnosticBuilder ClangTidyContext::diag(\r
- StringRef CheckName, SourceLocation Loc, StringRef Description,\r
- DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {\r
- assert(Loc.isValid());\r
- unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(\r
- Level, (Description + " [" + CheckName + "]").str());\r
- CheckNamesByDiagnosticID.try_emplace(ID, CheckName);\r
- return DiagEngine->Report(Loc, ID);\r
-}\r
-\r
-void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {\r
- DiagEngine = Engine;\r
-}\r
-\r
-void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {\r
- DiagEngine->setSourceManager(SourceMgr);\r
-}\r
-\r
-void ClangTidyContext::setCurrentFile(StringRef File) {\r
- CurrentFile = File;\r
- CurrentOptions = getOptionsForFile(CurrentFile);\r
- CheckFilter.reset(new GlobList(*getOptions().Checks));\r
- WarningAsErrorFilter.reset(new GlobList(*getOptions().WarningsAsErrors));\r
-}\r
-\r
-void ClangTidyContext::setASTContext(ASTContext *Context) {\r
- DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);\r
- LangOpts = Context->getLangOpts();\r
-}\r
-\r
-const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const {\r
- return OptionsProvider->getGlobalOptions();\r
-}\r
-\r
-const ClangTidyOptions &ClangTidyContext::getOptions() const {\r
- return CurrentOptions;\r
-}\r
-\r
-ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const {\r
- // Merge options on top of getDefaults() as a safeguard against options with\r
- // unset values.\r
- return ClangTidyOptions::getDefaults().mergeWith(\r
- OptionsProvider->getOptions(File));\r
-}\r
-\r
-void ClangTidyContext::setCheckProfileData(ProfileData *P) { Profile = P; }\r
-\r
-GlobList &ClangTidyContext::getChecksFilter() {\r
- assert(CheckFilter != nullptr);\r
- return *CheckFilter;\r
-}\r
-\r
-GlobList &ClangTidyContext::getWarningAsErrorFilter() {\r
- assert(WarningAsErrorFilter != nullptr);\r
- return *WarningAsErrorFilter;\r
-}\r
-\r
-/// \brief Store a \c ClangTidyError.\r
-void ClangTidyContext::storeError(const ClangTidyError &Error) {\r
- Errors.push_back(Error);\r
-}\r
-\r
-StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const {\r
- llvm::DenseMap<unsigned, std::string>::const_iterator I =\r
- CheckNamesByDiagnosticID.find(DiagnosticID);\r
- if (I != CheckNamesByDiagnosticID.end())\r
- return I->second;\r
- return "";\r
-}\r
-\r
-ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx)\r
- : Context(Ctx), LastErrorRelatesToUserCode(false),\r
- LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) {\r
- IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();\r
- Diags.reset(new DiagnosticsEngine(\r
- IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, this,\r
- /*ShouldOwnClient=*/false));\r
- Context.setDiagnosticsEngine(Diags.get());\r
-}\r
-\r
-void ClangTidyDiagnosticConsumer::finalizeLastError() {\r
- if (!Errors.empty()) {\r
- ClangTidyError &Error = Errors.back();\r
- if (!Context.getChecksFilter().contains(Error.DiagnosticName) &&\r
- Error.DiagLevel != ClangTidyError::Error) {\r
- ++Context.Stats.ErrorsIgnoredCheckFilter;\r
- Errors.pop_back();\r
- } else if (!LastErrorRelatesToUserCode) {\r
- ++Context.Stats.ErrorsIgnoredNonUserCode;\r
- Errors.pop_back();\r
- } else if (!LastErrorPassesLineFilter) {\r
- ++Context.Stats.ErrorsIgnoredLineFilter;\r
- Errors.pop_back();\r
- } else {\r
- ++Context.Stats.ErrorsDisplayed;\r
- }\r
- }\r
- LastErrorRelatesToUserCode = false;\r
- LastErrorPassesLineFilter = false;\r
-}\r
-\r
-static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc) {\r
- bool Invalid;\r
- const char *CharacterData = SM.getCharacterData(Loc, &Invalid);\r
- if (Invalid)\r
- return false;\r
-\r
- // Check if there's a NOLINT on this line.\r
- const char *P = CharacterData;\r
- while (*P != '\0' && *P != '\r' && *P != '\n')\r
- ++P;\r
- StringRef RestOfLine(CharacterData, P - CharacterData + 1);\r
- // FIXME: Handle /\bNOLINT\b(\([^)]*\))?/ as cpplint.py does.\r
- if (RestOfLine.find("NOLINT") != StringRef::npos)\r
- return true;\r
-\r
- // Check if there's a NOLINTNEXTLINE on the previous line.\r
- const char *BufBegin =\r
- SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);\r
- if (Invalid || P == BufBegin)\r
- return false;\r
-\r
- // Scan backwards over the current line.\r
- P = CharacterData;\r
- while (P != BufBegin && *P != '\n')\r
- --P;\r
-\r
- // If we reached the begin of the file there is no line before it.\r
- if (P == BufBegin)\r
- return false;\r
-\r
- // Skip over the newline.\r
- --P;\r
- const char *LineEnd = P;\r
-\r
- // Now we're on the previous line. Skip to the beginning of it.\r
- while (P != BufBegin && *P != '\n')\r
- --P;\r
-\r
- RestOfLine = StringRef(P, LineEnd - P + 1);\r
- if (RestOfLine.find("NOLINTNEXTLINE") != StringRef::npos)\r
- return true;\r
-\r
- return false;\r
-}\r
-\r
-static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM,\r
- SourceLocation Loc) {\r
- while (true) {\r
- if (LineIsMarkedWithNOLINT(SM, Loc))\r
- return true;\r
- if (!Loc.isMacroID())\r
- return false;\r
- Loc = SM.getImmediateExpansionRange(Loc).first;\r
- }\r
- return false;\r
-}\r
-\r
-void ClangTidyDiagnosticConsumer::HandleDiagnostic(\r
- DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {\r
- if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)\r
- return;\r
-\r
- if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&\r
- DiagLevel != DiagnosticsEngine::Fatal &&\r
- LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(),\r
- Info.getLocation())) {\r
- ++Context.Stats.ErrorsIgnoredNOLINT;\r
- // Ignored a warning, should ignore related notes as well\r
- LastErrorWasIgnored = true;\r
- return;\r
- }\r
-\r
- LastErrorWasIgnored = false;\r
- // Count warnings/errors.\r
- DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);\r
-\r
- if (DiagLevel == DiagnosticsEngine::Note) {\r
- assert(!Errors.empty() &&\r
- "A diagnostic note can only be appended to a message.");\r
- } else {\r
- finalizeLastError();\r
- StringRef WarningOption =\r
- Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(\r
- Info.getID());\r
- std::string CheckName = !WarningOption.empty()\r
- ? ("clang-diagnostic-" + WarningOption).str()\r
- : Context.getCheckName(Info.getID()).str();\r
-\r
- if (CheckName.empty()) {\r
- // This is a compiler diagnostic without a warning option. Assign check\r
- // name based on its level.\r
- switch (DiagLevel) {\r
- case DiagnosticsEngine::Error:\r
- case DiagnosticsEngine::Fatal:\r
- CheckName = "clang-diagnostic-error";\r
- break;\r
- case DiagnosticsEngine::Warning:\r
- CheckName = "clang-diagnostic-warning";\r
- break;\r
- default:\r
- CheckName = "clang-diagnostic-unknown";\r
- break;\r
- }\r
- }\r
-\r
- ClangTidyError::Level Level = ClangTidyError::Warning;\r
- if (DiagLevel == DiagnosticsEngine::Error ||\r
- DiagLevel == DiagnosticsEngine::Fatal) {\r
- // Force reporting of Clang errors regardless of filters and non-user\r
- // code.\r
- Level = ClangTidyError::Error;\r
- LastErrorRelatesToUserCode = true;\r
- LastErrorPassesLineFilter = true;\r
- }\r
- bool IsWarningAsError =\r
- DiagLevel == DiagnosticsEngine::Warning &&\r
- Context.getWarningAsErrorFilter().contains(CheckName);\r
- Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),\r
- IsWarningAsError);\r
- }\r
-\r
- ClangTidyDiagnosticRenderer Converter(\r
- Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),\r
- Errors.back());\r
- SmallString<100> Message;\r
- Info.FormatDiagnostic(Message);\r
- SourceManager *Sources = nullptr;\r
- if (Info.hasSourceManager())\r
- Sources = &Info.getSourceManager();\r
- Converter.emitDiagnostic(Info.getLocation(), DiagLevel, Message,\r
- Info.getRanges(), Info.getFixItHints(), Sources);\r
-\r
- checkFilters(Info.getLocation());\r
-}\r
-\r
-bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,\r
- unsigned LineNumber) const {\r
- if (Context.getGlobalOptions().LineFilter.empty())\r
- return true;\r
- for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {\r
- if (FileName.endswith(Filter.Name)) {\r
- if (Filter.LineRanges.empty())\r
- return true;\r
- for (const FileFilter::LineRange &Range : Filter.LineRanges) {\r
- if (Range.first <= LineNumber && LineNumber <= Range.second)\r
- return true;\r
- }\r
- return false;\r
- }\r
- }\r
- return false;\r
-}\r
-\r
-void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) {\r
- // Invalid location may mean a diagnostic in a command line, don't skip these.\r
- if (!Location.isValid()) {\r
- LastErrorRelatesToUserCode = true;\r
- LastErrorPassesLineFilter = true;\r
- return;\r
- }\r
-\r
- const SourceManager &Sources = Diags->getSourceManager();\r
- if (!*Context.getOptions().SystemHeaders &&\r
- Sources.isInSystemHeader(Location))\r
- return;\r
-\r
- // FIXME: We start with a conservative approach here, but the actual type of\r
- // location needed depends on the check (in particular, where this check wants\r
- // to apply fixes).\r
- FileID FID = Sources.getDecomposedExpansionLoc(Location).first;\r
- const FileEntry *File = Sources.getFileEntryForID(FID);\r
-\r
- // -DMACRO definitions on the command line have locations in a virtual buffer\r
- // that doesn't have a FileEntry. Don't skip these as well.\r
- if (!File) {\r
- LastErrorRelatesToUserCode = true;\r
- LastErrorPassesLineFilter = true;\r
- return;\r
- }\r
-\r
- StringRef FileName(File->getName());\r
- LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||\r
- Sources.isInMainFile(Location) ||\r
- getHeaderFilter()->match(FileName);\r
-\r
- unsigned LineNumber = Sources.getExpansionLineNumber(Location);\r
- LastErrorPassesLineFilter =\r
- LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);\r
-}\r
-\r
-llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {\r
- if (!HeaderFilter)\r
- HeaderFilter.reset(\r
- new llvm::Regex(*Context.getOptions().HeaderFilterRegex));\r
- return HeaderFilter.get();\r
-}\r
-\r
-void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(\r
- SmallVectorImpl<ClangTidyError> &Errors) const {\r
- // Each error is modelled as the set of intervals in which it applies\r
- // replacements. To detect overlapping replacements, we use a sweep line\r
- // algorithm over these sets of intervals.\r
- // An event here consists of the opening or closing of an interval. During the\r
- // process, we maintain a counter with the amount of open intervals. If we\r
- // find an endpoint of an interval and this counter is different from 0, it\r
- // means that this interval overlaps with another one, so we set it as\r
- // inapplicable.\r
- struct Event {\r
- // An event can be either the begin or the end of an interval.\r
- enum EventType {\r
- ET_Begin = 1,\r
- ET_End = -1,\r
- };\r
-\r
- Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,\r
- unsigned ErrorSize)\r
- : Type(Type), ErrorId(ErrorId) {\r
- // The events are going to be sorted by their position. In case of draw:\r
- //\r
- // * If an interval ends at the same position at which other interval\r
- // begins, this is not an overlapping, so we want to remove the ending\r
- // interval before adding the starting one: end events have higher\r
- // priority than begin events.\r
- //\r
- // * If we have several begin points at the same position, we will mark as\r
- // inapplicable the ones that we process later, so the first one has to\r
- // be the one with the latest end point, because this one will contain\r
- // all the other intervals. For the same reason, if we have several end\r
- // points in the same position, the last one has to be the one with the\r
- // earliest begin point. In both cases, we sort non-increasingly by the\r
- // position of the complementary.\r
- //\r
- // * In case of two equal intervals, the one whose error is bigger can\r
- // potentially contain the other one, so we want to process its begin\r
- // points before and its end points later.\r
- //\r
- // * Finally, if we have two equal intervals whose errors have the same\r
- // size, none of them will be strictly contained inside the other.\r
- // Sorting by ErrorId will guarantee that the begin point of the first\r
- // one will be processed before, disallowing the second one, and the\r
- // end point of the first one will also be processed before,\r
- // disallowing the first one.\r
- if (Type == ET_Begin)\r
- Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);\r
- else\r
- Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);\r
- }\r
-\r
- bool operator<(const Event &Other) const {\r
- return Priority < Other.Priority;\r
- }\r
-\r
- // Determines if this event is the begin or the end of an interval.\r
- EventType Type;\r
- // The index of the error to which the interval that generated this event\r
- // belongs.\r
- unsigned ErrorId;\r
- // The events will be sorted based on this field.\r
- std::tuple<unsigned, EventType, int, int, unsigned> Priority;\r
- };\r
-\r
- // Compute error sizes.\r
- std::vector<int> Sizes;\r
- for (const auto &Error : Errors) {\r
- int Size = 0;\r
- for (const auto &FileAndReplaces : Error.Fix) {\r
- for (const auto &Replace : FileAndReplaces.second)\r
- Size += Replace.getLength();\r
- }\r
- Sizes.push_back(Size);\r
- }\r
-\r
- // Build events from error intervals.\r
- std::map<std::string, std::vector<Event>> FileEvents;\r
- for (unsigned I = 0; I < Errors.size(); ++I) {\r
- for (const auto &FileAndReplace : Errors[I].Fix) {\r
- for (const auto &Replace : FileAndReplace.second) {\r
- unsigned Begin = Replace.getOffset();\r
- unsigned End = Begin + Replace.getLength();\r
- const std::string &FilePath = Replace.getFilePath();\r
- // FIXME: Handle empty intervals, such as those from insertions.\r
- if (Begin == End)\r
- continue;\r
- auto &Events = FileEvents[FilePath];\r
- Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);\r
- Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);\r
- }\r
- }\r
- }\r
-\r
- std::vector<bool> Apply(Errors.size(), true);\r
- for (auto &FileAndEvents : FileEvents) {\r
- std::vector<Event> &Events = FileAndEvents.second;\r
- // Sweep.\r
- std::sort(Events.begin(), Events.end());\r
- int OpenIntervals = 0;\r
- for (const auto &Event : Events) {\r
- if (Event.Type == Event::ET_End)\r
- --OpenIntervals;\r
- // This has to be checked after removing the interval from the count if it\r
- // is an end event, or before adding it if it is a begin event.\r
- if (OpenIntervals != 0)\r
- Apply[Event.ErrorId] = false;\r
- if (Event.Type == Event::ET_Begin)\r
- ++OpenIntervals;\r
- }\r
- assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");\r
- }\r
-\r
- for (unsigned I = 0; I < Errors.size(); ++I) {\r
- if (!Apply[I]) {\r
- Errors[I].Fix.clear();\r
- Errors[I].Notes.emplace_back(\r
- "this fix will not be applied because it overlaps with another fix");\r
- }\r
- }\r
-}\r
-\r
-namespace {\r
-struct LessClangTidyError {\r
- bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {\r
- const tooling::DiagnosticMessage &M1 = LHS.Message;\r
- const tooling::DiagnosticMessage &M2 = RHS.Message;\r
-\r
- return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <\r
- std::tie(M2.FilePath, M2.FileOffset, M2.Message);\r
- }\r
-};\r
-struct EqualClangTidyError {\r
- bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {\r
- LessClangTidyError Less;\r
- return !Less(LHS, RHS) && !Less(RHS, LHS);\r
- }\r
-};\r
-} // end anonymous namespace\r
-\r
-// Flushes the internal diagnostics buffer to the ClangTidyContext.\r
-void ClangTidyDiagnosticConsumer::finish() {\r
- finalizeLastError();\r
-\r
- std::sort(Errors.begin(), Errors.end(), LessClangTidyError());\r
- Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),\r
- Errors.end());\r
- removeIncompatibleErrors(Errors);\r
-\r
- for (const ClangTidyError &Error : Errors)\r
- Context.storeError(Error);\r
- Errors.clear();\r
-}\r
+//===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== //
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext
+/// and ClangTidyError classes.
+///
+/// This tool uses the Clang Tooling infrastructure, see
+/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
+/// for details on setting it up with LLVM source tree.
+///
+//===----------------------------------------------------------------------===//
+
+#include "ClangTidyDiagnosticConsumer.h"
+#include "ClangTidyOptions.h"
+#include "clang/AST/ASTDiagnostic.h"
+#include "clang/Basic/DiagnosticOptions.h"
+#include "clang/Frontend/DiagnosticRenderer.h"
+#include "llvm/ADT/SmallString.h"
+#include <tuple>
+#include <vector>
+using namespace clang;
+using namespace tidy;
+
+namespace {
+class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {
+public:
+ ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,
+ DiagnosticOptions *DiagOpts,
+ ClangTidyError &Error)
+ : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
+
+protected:
+ void emitDiagnosticMessage(SourceLocation Loc, PresumedLoc PLoc,
+ DiagnosticsEngine::Level Level, StringRef Message,
+ ArrayRef<CharSourceRange> Ranges,
+ const SourceManager *SM,
+ DiagOrStoredDiag Info) override {
+ // Remove check name from the message.
+ // FIXME: Remove this once there's a better way to pass check names than
+ // appending the check name to the message in ClangTidyContext::diag and
+ // using getCustomDiagID.
+ std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]";
+ if (Message.endswith(CheckNameInMessage))
+ Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
+
+ auto TidyMessage = Loc.isValid()
+ ? tooling::DiagnosticMessage(Message, *SM, Loc)
+ : tooling::DiagnosticMessage(Message);
+ if (Level == DiagnosticsEngine::Note) {
+ Error.Notes.push_back(TidyMessage);
+ return;
+ }
+ assert(Error.Message.Message.empty() && "Overwriting a diagnostic message");
+ Error.Message = TidyMessage;
+ }
+
+ void emitDiagnosticLoc(SourceLocation Loc, PresumedLoc PLoc,
+ DiagnosticsEngine::Level Level,
+ ArrayRef<CharSourceRange> Ranges,
+ const SourceManager &SM) override {}
+
+ void emitCodeContext(SourceLocation Loc, DiagnosticsEngine::Level Level,
+ SmallVectorImpl<CharSourceRange> &Ranges,
+ ArrayRef<FixItHint> Hints,
+ const SourceManager &SM) override {
+ assert(Loc.isValid());
+ for (const auto &FixIt : Hints) {
+ CharSourceRange Range = FixIt.RemoveRange;
+ assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
+ "Invalid range in the fix-it hint.");
+ assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
+ "Only file locations supported in fix-it hints.");
+
+ tooling::Replacement Replacement(SM, Range, FixIt.CodeToInsert);
+ llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement);
+ // FIXME: better error handling (at least, don't let other replacements be
+ // applied).
+ if (Err) {
+ llvm::errs() << "Fix conflicts with existing fix! "
+ << llvm::toString(std::move(Err)) << "\n";
+ assert(false && "Fix conflicts with existing fix!");
+ }
+ }
+ }
+
+ void emitIncludeLocation(SourceLocation Loc, PresumedLoc PLoc,
+ const SourceManager &SM) override {}
+
+ void emitImportLocation(SourceLocation Loc, PresumedLoc PLoc,
+ StringRef ModuleName,
+ const SourceManager &SM) override {}
+
+ void emitBuildingModuleLocation(SourceLocation Loc, PresumedLoc PLoc,
+ StringRef ModuleName,
+ const SourceManager &SM) override {}
+
+ void endDiagnostic(DiagOrStoredDiag D,
+ DiagnosticsEngine::Level Level) override {
+ assert(!Error.Message.Message.empty() && "Message has not been set");
+ }
+
+private:
+ ClangTidyError &Error;
+};
+} // end anonymous namespace
+
+ClangTidyError::ClangTidyError(StringRef CheckName,
+ ClangTidyError::Level DiagLevel,
+ StringRef BuildDirectory, bool IsWarningAsError)
+ : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
+ IsWarningAsError(IsWarningAsError) {}
+
+// Returns true if GlobList starts with the negative indicator ('-'), removes it
+// from the GlobList.
+static bool ConsumeNegativeIndicator(StringRef &GlobList) {
+ GlobList = GlobList.trim(' ');
+ if (GlobList.startswith("-")) {
+ GlobList = GlobList.substr(1);
+ return true;
+ }
+ return false;
+}
+// Converts first glob from the comma-separated list of globs to Regex and
+// removes it and the trailing comma from the GlobList.
+static llvm::Regex ConsumeGlob(StringRef &GlobList) {
+ StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(','));
+ StringRef Glob = UntrimmedGlob.trim(' ');
+ GlobList = GlobList.substr(UntrimmedGlob.size() + 1);
+ SmallString<128> RegexText("^");
+ StringRef MetaChars("()^$|*+?.[]\\{}");
+ for (char C : Glob) {
+ if (C == '*')
+ RegexText.push_back('.');
+ else if (MetaChars.find(C) != StringRef::npos)
+ RegexText.push_back('\\');
+ RegexText.push_back(C);
+ }
+ RegexText.push_back('$');
+ return llvm::Regex(RegexText);
+}
+
+GlobList::GlobList(StringRef Globs)
+ : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)),
+ NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {}
+
+bool GlobList::contains(StringRef S, bool Contains) {
+ if (Regex.match(S))
+ Contains = Positive;
+
+ if (NextGlob)
+ Contains = NextGlob->contains(S, Contains);
+ return Contains;
+}
+
+ClangTidyContext::ClangTidyContext(
+ std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
+ : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
+ Profile(nullptr) {
+ // Before the first translation unit we can get errors related to command-line
+ // parsing, use empty string for the file name in this case.
+ setCurrentFile("");
+}
+
+DiagnosticBuilder ClangTidyContext::diag(
+ StringRef CheckName, SourceLocation Loc, StringRef Description,
+ DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
+ assert(Loc.isValid());
+ unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
+ Level, (Description + " [" + CheckName + "]").str());
+ CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
+ return DiagEngine->Report(Loc, ID);
+}
+
+void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
+ DiagEngine = Engine;
+}
+
+void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
+ DiagEngine->setSourceManager(SourceMgr);
+}
+
+void ClangTidyContext::setCurrentFile(StringRef File) {
+ CurrentFile = File;
+ CurrentOptions = getOptionsForFile(CurrentFile);
+ CheckFilter.reset(new GlobList(*getOptions().Checks));
+ WarningAsErrorFilter.reset(new GlobList(*getOptions().WarningsAsErrors));
+}
+
+void ClangTidyContext::setASTContext(ASTContext *Context) {
+ DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
+ LangOpts = Context->getLangOpts();
+}
+
+const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const {
+ return OptionsProvider->getGlobalOptions();
+}
+
+const ClangTidyOptions &ClangTidyContext::getOptions() const {
+ return CurrentOptions;
+}
+
+ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const {
+ // Merge options on top of getDefaults() as a safeguard against options with
+ // unset values.
+ return ClangTidyOptions::getDefaults().mergeWith(
+ OptionsProvider->getOptions(File));
+}
+
+void ClangTidyContext::setCheckProfileData(ProfileData *P) { Profile = P; }
+
+GlobList &ClangTidyContext::getChecksFilter() {
+ assert(CheckFilter != nullptr);
+ return *CheckFilter;
+}
+
+GlobList &ClangTidyContext::getWarningAsErrorFilter() {
+ assert(WarningAsErrorFilter != nullptr);
+ return *WarningAsErrorFilter;
+}
+
+/// \brief Store a \c ClangTidyError.
+void ClangTidyContext::storeError(const ClangTidyError &Error) {
+ Errors.push_back(Error);
+}
+
+StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const {
+ llvm::DenseMap<unsigned, std::string>::const_iterator I =
+ CheckNamesByDiagnosticID.find(DiagnosticID);
+ if (I != CheckNamesByDiagnosticID.end())
+ return I->second;
+ return "";
+}
+
+ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx)
+ : Context(Ctx), LastErrorRelatesToUserCode(false),
+ LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) {
+ IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
+ Diags.reset(new DiagnosticsEngine(
+ IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, this,
+ /*ShouldOwnClient=*/false));
+ Context.setDiagnosticsEngine(Diags.get());
+}
+
+void ClangTidyDiagnosticConsumer::finalizeLastError() {
+ if (!Errors.empty()) {
+ ClangTidyError &Error = Errors.back();
+ if (!Context.getChecksFilter().contains(Error.DiagnosticName) &&
+ Error.DiagLevel != ClangTidyError::Error) {
+ ++Context.Stats.ErrorsIgnoredCheckFilter;
+ Errors.pop_back();
+ } else if (!LastErrorRelatesToUserCode) {
+ ++Context.Stats.ErrorsIgnoredNonUserCode;
+ Errors.pop_back();
+ } else if (!LastErrorPassesLineFilter) {
+ ++Context.Stats.ErrorsIgnoredLineFilter;
+ Errors.pop_back();
+ } else {
+ ++Context.Stats.ErrorsDisplayed;
+ }
+ }
+ LastErrorRelatesToUserCode = false;
+ LastErrorPassesLineFilter = false;
+}
+
+static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc) {
+ bool Invalid;
+ const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
+ if (Invalid)
+ return false;
+
+ // Check if there's a NOLINT on this line.
+ const char *P = CharacterData;
+ while (*P != '\0' && *P != '\r' && *P != '\n')
+ ++P;
+ StringRef RestOfLine(CharacterData, P - CharacterData + 1);
+ // FIXME: Handle /\bNOLINT\b(\([^)]*\))?/ as cpplint.py does.
+ if (RestOfLine.find("NOLINT") != StringRef::npos)
+ return true;
+
+ // Check if there's a NOLINTNEXTLINE on the previous line.
+ const char *BufBegin =
+ SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
+ if (Invalid || P == BufBegin)
+ return false;
+
+ // Scan backwards over the current line.
+ P = CharacterData;
+ while (P != BufBegin && *P != '\n')
+ --P;
+
+ // If we reached the begin of the file there is no line before it.
+ if (P == BufBegin)
+ return false;
+
+ // Skip over the newline.
+ --P;
+ const char *LineEnd = P;
+
+ // Now we're on the previous line. Skip to the beginning of it.
+ while (P != BufBegin && *P != '\n')
+ --P;
+
+ RestOfLine = StringRef(P, LineEnd - P + 1);
+ if (RestOfLine.find("NOLINTNEXTLINE") != StringRef::npos)
+ return true;
+
+ return false;
+}
+
+static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM,
+ SourceLocation Loc) {
+ while (true) {
+ if (LineIsMarkedWithNOLINT(SM, Loc))
+ return true;
+ if (!Loc.isMacroID())
+ return false;
+ Loc = SM.getImmediateExpansionRange(Loc).first;
+ }
+ return false;
+}
+
+void ClangTidyDiagnosticConsumer::HandleDiagnostic(
+ DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
+ if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
+ return;
+
+ if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
+ DiagLevel != DiagnosticsEngine::Fatal &&
+ LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(),
+ Info.getLocation())) {
+ ++Context.Stats.ErrorsIgnoredNOLINT;
+ // Ignored a warning, should ignore related notes as well
+ LastErrorWasIgnored = true;
+ return;
+ }
+
+ LastErrorWasIgnored = false;
+ // Count warnings/errors.
+ DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
+
+ if (DiagLevel == DiagnosticsEngine::Note) {
+ assert(!Errors.empty() &&
+ "A diagnostic note can only be appended to a message.");
+ } else {
+ finalizeLastError();
+ StringRef WarningOption =
+ Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
+ Info.getID());
+ std::string CheckName = !WarningOption.empty()
+ ? ("clang-diagnostic-" + WarningOption).str()
+ : Context.getCheckName(Info.getID()).str();
+
+ if (CheckName.empty()) {
+ // This is a compiler diagnostic without a warning option. Assign check
+ // name based on its level.
+ switch (DiagLevel) {
+ case DiagnosticsEngine::Error:
+ case DiagnosticsEngine::Fatal:
+ CheckName = "clang-diagnostic-error";
+ break;
+ case DiagnosticsEngine::Warning:
+ CheckName = "clang-diagnostic-warning";
+ break;
+ default:
+ CheckName = "clang-diagnostic-unknown";
+ break;
+ }
+ }
+
+ ClangTidyError::Level Level = ClangTidyError::Warning;
+ if (DiagLevel == DiagnosticsEngine::Error ||
+ DiagLevel == DiagnosticsEngine::Fatal) {
+ // Force reporting of Clang errors regardless of filters and non-user
+ // code.
+ Level = ClangTidyError::Error;
+ LastErrorRelatesToUserCode = true;
+ LastErrorPassesLineFilter = true;
+ }
+ bool IsWarningAsError =
+ DiagLevel == DiagnosticsEngine::Warning &&
+ Context.getWarningAsErrorFilter().contains(CheckName);
+ Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),
+ IsWarningAsError);
+ }
+
+ ClangTidyDiagnosticRenderer Converter(
+ Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
+ Errors.back());
+ SmallString<100> Message;
+ Info.FormatDiagnostic(Message);
+ SourceManager *Sources = nullptr;
+ if (Info.hasSourceManager())
+ Sources = &Info.getSourceManager();
+ Converter.emitDiagnostic(Info.getLocation(), DiagLevel, Message,
+ Info.getRanges(), Info.getFixItHints(), Sources);
+
+ checkFilters(Info.getLocation());
+}
+
+bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
+ unsigned LineNumber) const {
+ if (Context.getGlobalOptions().LineFilter.empty())
+ return true;
+ for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
+ if (FileName.endswith(Filter.Name)) {
+ if (Filter.LineRanges.empty())
+ return true;
+ for (const FileFilter::LineRange &Range : Filter.LineRanges) {
+ if (Range.first <= LineNumber && LineNumber <= Range.second)
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+}
+
+void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) {
+ // Invalid location may mean a diagnostic in a command line, don't skip these.
+ if (!Location.isValid()) {
+ LastErrorRelatesToUserCode = true;
+ LastErrorPassesLineFilter = true;
+ return;
+ }
+
+ const SourceManager &Sources = Diags->getSourceManager();
+ if (!*Context.getOptions().SystemHeaders &&
+ Sources.isInSystemHeader(Location))
+ return;
+
+ // FIXME: We start with a conservative approach here, but the actual type of
+ // location needed depends on the check (in particular, where this check wants
+ // to apply fixes).
+ FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
+ const FileEntry *File = Sources.getFileEntryForID(FID);
+
+ // -DMACRO definitions on the command line have locations in a virtual buffer
+ // that doesn't have a FileEntry. Don't skip these as well.
+ if (!File) {
+ LastErrorRelatesToUserCode = true;
+ LastErrorPassesLineFilter = true;
+ return;
+ }
+
+ StringRef FileName(File->getName());
+ LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
+ Sources.isInMainFile(Location) ||
+ getHeaderFilter()->match(FileName);
+
+ unsigned LineNumber = Sources.getExpansionLineNumber(Location);
+ LastErrorPassesLineFilter =
+ LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
+}
+
+llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
+ if (!HeaderFilter)
+ HeaderFilter.reset(
+ new llvm::Regex(*Context.getOptions().HeaderFilterRegex));
+ return HeaderFilter.get();
+}
+
+void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
+ SmallVectorImpl<ClangTidyError> &Errors) const {
+ // Each error is modelled as the set of intervals in which it applies
+ // replacements. To detect overlapping replacements, we use a sweep line
+ // algorithm over these sets of intervals.
+ // An event here consists of the opening or closing of an interval. During the
+ // process, we maintain a counter with the amount of open intervals. If we
+ // find an endpoint of an interval and this counter is different from 0, it
+ // means that this interval overlaps with another one, so we set it as
+ // inapplicable.
+ struct Event {
+ // An event can be either the begin or the end of an interval.
+ enum EventType {
+ ET_Begin = 1,
+ ET_End = -1,
+ };
+
+ Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,
+ unsigned ErrorSize)
+ : Type(Type), ErrorId(ErrorId) {
+ // The events are going to be sorted by their position. In case of draw:
+ //
+ // * If an interval ends at the same position at which other interval
+ // begins, this is not an overlapping, so we want to remove the ending
+ // interval before adding the starting one: end events have higher
+ // priority than begin events.
+ //
+ // * If we have several begin points at the same position, we will mark as
+ // inapplicable the ones that we process later, so the first one has to
+ // be the one with the latest end point, because this one will contain
+ // all the other intervals. For the same reason, if we have several end
+ // points in the same position, the last one has to be the one with the
+ // earliest begin point. In both cases, we sort non-increasingly by the
+ // position of the complementary.
+ //
+ // * In case of two equal intervals, the one whose error is bigger can
+ // potentially contain the other one, so we want to process its begin
+ // points before and its end points later.
+ //
+ // * Finally, if we have two equal intervals whose errors have the same
+ // size, none of them will be strictly contained inside the other.
+ // Sorting by ErrorId will guarantee that the begin point of the first
+ // one will be processed before, disallowing the second one, and the
+ // end point of the first one will also be processed before,
+ // disallowing the first one.
+ if (Type == ET_Begin)
+ Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
+ else
+ Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
+ }
+
+ bool operator<(const Event &Other) const {
+ return Priority < Other.Priority;
+ }
+
+ // Determines if this event is the begin or the end of an interval.
+ EventType Type;
+ // The index of the error to which the interval that generated this event
+ // belongs.
+ unsigned ErrorId;
+ // The events will be sorted based on this field.
+ std::tuple<unsigned, EventType, int, int, unsigned> Priority;
+ };
+
+ // Compute error sizes.
+ std::vector<int> Sizes;
+ for (const auto &Error : Errors) {
+ int Size = 0;
+ for (const auto &FileAndReplaces : Error.Fix) {
+ for (const auto &Replace : FileAndReplaces.second)
+ Size += Replace.getLength();
+ }
+ Sizes.push_back(Size);
+ }
+
+ // Build events from error intervals.
+ std::map<std::string, std::vector<Event>> FileEvents;
+ for (unsigned I = 0; I < Errors.size(); ++I) {
+ for (const auto &FileAndReplace : Errors[I].Fix) {
+ for (const auto &Replace : FileAndReplace.second) {
+ unsigned Begin = Replace.getOffset();
+ unsigned End = Begin + Replace.getLength();
+ const std::string &FilePath = Replace.getFilePath();
+ // FIXME: Handle empty intervals, such as those from insertions.
+ if (Begin == End)
+ continue;
+ auto &Events = FileEvents[FilePath];
+ Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
+ Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
+ }
+ }
+ }
+
+ std::vector<bool> Apply(Errors.size(), true);
+ for (auto &FileAndEvents : FileEvents) {
+ std::vector<Event> &Events = FileAndEvents.second;
+ // Sweep.
+ std::sort(Events.begin(), Events.end());
+ int OpenIntervals = 0;
+ for (const auto &Event : Events) {
+ if (Event.Type == Event::ET_End)
+ --OpenIntervals;
+ // This has to be checked after removing the interval from the count if it
+ // is an end event, or before adding it if it is a begin event.
+ if (OpenIntervals != 0)
+ Apply[Event.ErrorId] = false;
+ if (Event.Type == Event::ET_Begin)
+ ++OpenIntervals;
+ }
+ assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");
+ }
+
+ for (unsigned I = 0; I < Errors.size(); ++I) {
+ if (!Apply[I]) {
+ Errors[I].Fix.clear();
+ Errors[I].Notes.emplace_back(
+ "this fix will not be applied because it overlaps with another fix");
+ }
+ }
+}
+
+namespace {
+struct LessClangTidyError {
+ bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
+ const tooling::DiagnosticMessage &M1 = LHS.Message;
+ const tooling::DiagnosticMessage &M2 = RHS.Message;
+
+ return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
+ std::tie(M2.FilePath, M2.FileOffset, M2.Message);
+ }
+};
+struct EqualClangTidyError {
+ bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
+ LessClangTidyError Less;
+ return !Less(LHS, RHS) && !Less(RHS, LHS);
+ }
+};
+} // end anonymous namespace
+
+// Flushes the internal diagnostics buffer to the ClangTidyContext.
+void ClangTidyDiagnosticConsumer::finish() {
+ finalizeLastError();
+
+ std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
+ Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
+ Errors.end());
+ removeIncompatibleErrors(Errors);
+
+ for (const ClangTidyError &Error : Errors)
+ Context.storeError(Error);
+ Errors.clear();
+}
-//===--- ForwardingReferenceOverloadCheck.cpp - clang-tidy-----------------===//\r
-//\r
-// The LLVM Compiler Infrastructure\r
-//\r
-// This file is distributed under the University of Illinois Open Source\r
-// License. See LICENSE.TXT for details.\r
-//\r
-//===----------------------------------------------------------------------===//\r
-\r
-#include "ForwardingReferenceOverloadCheck.h"\r
-#include "clang/AST/ASTContext.h"\r
-#include "clang/ASTMatchers/ASTMatchFinder.h"\r
-#include <algorithm>\r
-\r
-using namespace clang::ast_matchers;\r
-\r
-namespace clang {\r
-namespace tidy {\r
-namespace misc {\r
-\r
-namespace {\r
-// Check if the given type is related to std::enable_if.\r
-AST_MATCHER(QualType, isEnableIf) {\r
- auto CheckTemplate = [](const TemplateSpecializationType *Spec) {\r
- if (!Spec || !Spec->getTemplateName().getAsTemplateDecl()) {\r
- return false;\r
- }\r
- const NamedDecl *TypeDecl =\r
- Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl();\r
- return TypeDecl->isInStdNamespace() &&\r
- (TypeDecl->getName().equals("enable_if") ||\r
- TypeDecl->getName().equals("enable_if_t"));\r
- };\r
- const Type *BaseType = Node.getTypePtr();\r
- // Case: pointer or reference to enable_if.\r
- while (BaseType->isPointerType() || BaseType->isReferenceType()) {\r
- BaseType = BaseType->getPointeeType().getTypePtr();\r
- }\r
- // Case: type parameter dependent (enable_if<is_integral<T>>).\r
- if (const auto *Dependent = BaseType->getAs<DependentNameType>()) {\r
- BaseType = Dependent->getQualifier()->getAsType();\r
- }\r
- if (CheckTemplate(BaseType->getAs<TemplateSpecializationType>())) {\r
- return true; // Case: enable_if_t< >.\r
- } else if (const auto *Elaborated = BaseType->getAs<ElaboratedType>()) {\r
- if (const auto *Qualifier = Elaborated->getQualifier()->getAsType()) {\r
- if (CheckTemplate(Qualifier->getAs<TemplateSpecializationType>())) {\r
- return true; // Case: enable_if< >::type.\r
- }\r
- }\r
- }\r
- return false;\r
-}\r
-AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument,\r
- clang::ast_matchers::internal::Matcher<QualType>, TypeMatcher) {\r
- return Node.hasDefaultArgument() &&\r
- TypeMatcher.matches(Node.getDefaultArgument(), Finder, Builder);\r
-}\r
-} // namespace\r
-\r
-void ForwardingReferenceOverloadCheck::registerMatchers(MatchFinder *Finder) {\r
- // Forwarding references require C++11 or later.\r
- if (!getLangOpts().CPlusPlus11)\r
- return;\r
-\r
- auto ForwardingRefParm =\r
- parmVarDecl(\r
- hasType(qualType(rValueReferenceType(),\r
- references(templateTypeParmType(hasDeclaration(\r
- templateTypeParmDecl().bind("type-parm-decl")))),\r
- unless(references(isConstQualified())))))\r
- .bind("parm-var");\r
-\r
- DeclarationMatcher findOverload =\r
- cxxConstructorDecl(\r
- hasParameter(0, ForwardingRefParm),\r
- unless(hasAnyParameter(\r
- // No warning: enable_if as constructor parameter.\r
- parmVarDecl(hasType(isEnableIf())))),\r
- unless(hasParent(functionTemplateDecl(has(templateTypeParmDecl(\r
- // No warning: enable_if as type parameter.\r
- hasDefaultArgument(isEnableIf())))))))\r
- .bind("ctor");\r
- Finder->addMatcher(findOverload, this);\r
-}\r
-\r
-void ForwardingReferenceOverloadCheck::check(\r
- const MatchFinder::MatchResult &Result) {\r
- const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var");\r
- const auto *TypeParmDecl =\r
- Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl");\r
-\r
- // Get the FunctionDecl and FunctionTemplateDecl containing the function\r
- // parameter.\r
- const auto *FuncForParam = dyn_cast<FunctionDecl>(ParmVar->getDeclContext());\r
- if (!FuncForParam)\r
- return;\r
- const FunctionTemplateDecl *FuncTemplate =\r
- FuncForParam->getDescribedFunctionTemplate();\r
- if (!FuncTemplate)\r
- return;\r
-\r
- // Check that the template type parameter belongs to the same function\r
- // template as the function parameter of that type. (This implies that type\r
- // deduction will happen on the type.)\r
- const TemplateParameterList *Params = FuncTemplate->getTemplateParameters();\r
- if (std::find(Params->begin(), Params->end(), TypeParmDecl) == Params->end())\r
- return;\r
-\r
- // Every parameter after the first must have a default value.\r
- const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");\r
- for (auto Iter = Ctor->param_begin() + 1; Iter != Ctor->param_end(); ++Iter) {\r
- if (!(*Iter)->hasDefaultArg())\r
- return;\r
- }\r
- bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false,\r
- DisabledMove = false;\r
- for (const auto *OtherCtor : Ctor->getParent()->ctors()) {\r
- if (OtherCtor->isCopyOrMoveConstructor()) {\r
- if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private)\r
- (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true;\r
- else\r
- (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true;\r
- }\r
- }\r
- bool Copy = (!EnabledMove && !DisabledMove && !DisabledCopy) || EnabledCopy;\r
- bool Move = !DisabledMove || EnabledMove;\r
- if (!Copy && !Move)\r
- return;\r
- diag(Ctor->getLocation(),\r
- "constructor accepting a forwarding reference can "\r
- "hide the %select{copy|move|copy and move}0 constructor%s1")\r
- << (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move;\r
- for (const auto *OtherCtor : Ctor->getParent()->ctors()) {\r
- if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() &&\r
- OtherCtor->getAccess() != AS_private) {\r
- diag(OtherCtor->getLocation(),\r
- "%select{copy|move}0 constructor declared here", DiagnosticIDs::Note)\r
- << OtherCtor->isMoveConstructor();\r
- }\r
- }\r
-}\r
-\r
-} // namespace misc\r
-} // namespace tidy\r
-} // namespace clang\r
+//===--- ForwardingReferenceOverloadCheck.cpp - clang-tidy-----------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ForwardingReferenceOverloadCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include <algorithm>
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace misc {
+
+namespace {
+// Check if the given type is related to std::enable_if.
+AST_MATCHER(QualType, isEnableIf) {
+ auto CheckTemplate = [](const TemplateSpecializationType *Spec) {
+ if (!Spec || !Spec->getTemplateName().getAsTemplateDecl()) {
+ return false;
+ }
+ const NamedDecl *TypeDecl =
+ Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl();
+ return TypeDecl->isInStdNamespace() &&
+ (TypeDecl->getName().equals("enable_if") ||
+ TypeDecl->getName().equals("enable_if_t"));
+ };
+ const Type *BaseType = Node.getTypePtr();
+ // Case: pointer or reference to enable_if.
+ while (BaseType->isPointerType() || BaseType->isReferenceType()) {
+ BaseType = BaseType->getPointeeType().getTypePtr();
+ }
+ // Case: type parameter dependent (enable_if<is_integral<T>>).
+ if (const auto *Dependent = BaseType->getAs<DependentNameType>()) {
+ BaseType = Dependent->getQualifier()->getAsType();
+ }
+ if (CheckTemplate(BaseType->getAs<TemplateSpecializationType>())) {
+ return true; // Case: enable_if_t< >.
+ } else if (const auto *Elaborated = BaseType->getAs<ElaboratedType>()) {
+ if (const auto *Qualifier = Elaborated->getQualifier()->getAsType()) {
+ if (CheckTemplate(Qualifier->getAs<TemplateSpecializationType>())) {
+ return true; // Case: enable_if< >::type.
+ }
+ }
+ }
+ return false;
+}
+AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument,
+ clang::ast_matchers::internal::Matcher<QualType>, TypeMatcher) {
+ return Node.hasDefaultArgument() &&
+ TypeMatcher.matches(Node.getDefaultArgument(), Finder, Builder);
+}
+} // namespace
+
+void ForwardingReferenceOverloadCheck::registerMatchers(MatchFinder *Finder) {
+ // Forwarding references require C++11 or later.
+ if (!getLangOpts().CPlusPlus11)
+ return;
+
+ auto ForwardingRefParm =
+ parmVarDecl(
+ hasType(qualType(rValueReferenceType(),
+ references(templateTypeParmType(hasDeclaration(
+ templateTypeParmDecl().bind("type-parm-decl")))),
+ unless(references(isConstQualified())))))
+ .bind("parm-var");
+
+ DeclarationMatcher findOverload =
+ cxxConstructorDecl(
+ hasParameter(0, ForwardingRefParm),
+ unless(hasAnyParameter(
+ // No warning: enable_if as constructor parameter.
+ parmVarDecl(hasType(isEnableIf())))),
+ unless(hasParent(functionTemplateDecl(has(templateTypeParmDecl(
+ // No warning: enable_if as type parameter.
+ hasDefaultArgument(isEnableIf())))))))
+ .bind("ctor");
+ Finder->addMatcher(findOverload, this);
+}
+
+void ForwardingReferenceOverloadCheck::check(
+ const MatchFinder::MatchResult &Result) {
+ const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var");
+ const auto *TypeParmDecl =
+ Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl");
+
+ // Get the FunctionDecl and FunctionTemplateDecl containing the function
+ // parameter.
+ const auto *FuncForParam = dyn_cast<FunctionDecl>(ParmVar->getDeclContext());
+ if (!FuncForParam)
+ return;
+ const FunctionTemplateDecl *FuncTemplate =
+ FuncForParam->getDescribedFunctionTemplate();
+ if (!FuncTemplate)
+ return;
+
+ // Check that the template type parameter belongs to the same function
+ // template as the function parameter of that type. (This implies that type
+ // deduction will happen on the type.)
+ const TemplateParameterList *Params = FuncTemplate->getTemplateParameters();
+ if (std::find(Params->begin(), Params->end(), TypeParmDecl) == Params->end())
+ return;
+
+ // Every parameter after the first must have a default value.
+ const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
+ for (auto Iter = Ctor->param_begin() + 1; Iter != Ctor->param_end(); ++Iter) {
+ if (!(*Iter)->hasDefaultArg())
+ return;
+ }
+ bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false,
+ DisabledMove = false;
+ for (const auto *OtherCtor : Ctor->getParent()->ctors()) {
+ if (OtherCtor->isCopyOrMoveConstructor()) {
+ if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private)
+ (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true;
+ else
+ (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true;
+ }
+ }
+ bool Copy = (!EnabledMove && !DisabledMove && !DisabledCopy) || EnabledCopy;
+ bool Move = !DisabledMove || EnabledMove;
+ if (!Copy && !Move)
+ return;
+ diag(Ctor->getLocation(),
+ "constructor accepting a forwarding reference can "
+ "hide the %select{copy|move|copy and move}0 constructor%s1")
+ << (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move;
+ for (const auto *OtherCtor : Ctor->getParent()->ctors()) {
+ if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() &&
+ OtherCtor->getAccess() != AS_private) {
+ diag(OtherCtor->getLocation(),
+ "%select{copy|move}0 constructor declared here", DiagnosticIDs::Note)
+ << OtherCtor->isMoveConstructor();
+ }
+ }
+}
+
+} // namespace misc
+} // namespace tidy
+} // namespace clang