clang-tools  6.0.0
ClangTidyDiagnosticConsumer.cpp
Go to the documentation of this file.
1 //===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== //
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 ///
10 /// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext
11 /// and ClangTidyError classes.
12 ///
13 /// This tool uses the Clang Tooling infrastructure, see
14 /// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
15 /// for details on setting it up with LLVM source tree.
16 ///
17 //===----------------------------------------------------------------------===//
18 
20 #include "ClangTidyOptions.h"
21 #include "clang/AST/ASTDiagnostic.h"
22 #include "clang/Basic/DiagnosticOptions.h"
23 #include "clang/Frontend/DiagnosticRenderer.h"
24 #include "llvm/ADT/STLExtras.h"
25 #include "llvm/ADT/SmallString.h"
26 #include <tuple>
27 #include <vector>
28 using namespace clang;
29 using namespace tidy;
30 
31 namespace {
32 class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {
33 public:
34  ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,
35  DiagnosticOptions *DiagOpts,
36  ClangTidyError &Error)
37  : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
38 
39 protected:
40  void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc,
41  DiagnosticsEngine::Level Level, StringRef Message,
42  ArrayRef<CharSourceRange> Ranges,
43  DiagOrStoredDiag Info) override {
44  // Remove check name from the message.
45  // FIXME: Remove this once there's a better way to pass check names than
46  // appending the check name to the message in ClangTidyContext::diag and
47  // using getCustomDiagID.
48  std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]";
49  if (Message.endswith(CheckNameInMessage))
50  Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
51 
52  auto TidyMessage =
53  Loc.isValid()
54  ? tooling::DiagnosticMessage(Message, Loc.getManager(), Loc)
55  : tooling::DiagnosticMessage(Message);
56  if (Level == DiagnosticsEngine::Note) {
57  Error.Notes.push_back(TidyMessage);
58  return;
59  }
60  assert(Error.Message.Message.empty() && "Overwriting a diagnostic message");
61  Error.Message = TidyMessage;
62  }
63 
64  void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
65  DiagnosticsEngine::Level Level,
66  ArrayRef<CharSourceRange> Ranges) override {}
67 
68  void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
69  SmallVectorImpl<CharSourceRange> &Ranges,
70  ArrayRef<FixItHint> Hints) override {
71  assert(Loc.isValid());
72  for (const auto &FixIt : Hints) {
73  CharSourceRange Range = FixIt.RemoveRange;
74  assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
75  "Invalid range in the fix-it hint.");
76  assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
77  "Only file locations supported in fix-it hints.");
78 
79  tooling::Replacement Replacement(Loc.getManager(), Range,
80  FixIt.CodeToInsert);
81  llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement);
82  // FIXME: better error handling (at least, don't let other replacements be
83  // applied).
84  if (Err) {
85  llvm::errs() << "Fix conflicts with existing fix! "
86  << llvm::toString(std::move(Err)) << "\n";
87  assert(false && "Fix conflicts with existing fix!");
88  }
89  }
90  }
91 
92  void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override {}
93 
94  void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
95  StringRef ModuleName) override {}
96 
97  void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
98  StringRef ModuleName) override {}
99 
100  void endDiagnostic(DiagOrStoredDiag D,
101  DiagnosticsEngine::Level Level) override {
102  assert(!Error.Message.Message.empty() && "Message has not been set");
103  }
104 
105 private:
106  ClangTidyError &Error;
107 };
108 } // end anonymous namespace
109 
110 ClangTidyError::ClangTidyError(StringRef CheckName,
111  ClangTidyError::Level DiagLevel,
112  StringRef BuildDirectory, bool IsWarningAsError)
113  : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
114  IsWarningAsError(IsWarningAsError) {}
115 
116 // Returns true if GlobList starts with the negative indicator ('-'), removes it
117 // from the GlobList.
118 static bool ConsumeNegativeIndicator(StringRef &GlobList) {
119  GlobList = GlobList.trim(" \r\n");
120  if (GlobList.startswith("-")) {
121  GlobList = GlobList.substr(1);
122  return true;
123  }
124  return false;
125 }
126 // Converts first glob from the comma-separated list of globs to Regex and
127 // removes it and the trailing comma from the GlobList.
128 static llvm::Regex ConsumeGlob(StringRef &GlobList) {
129  StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(','));
130  StringRef Glob = UntrimmedGlob.trim(' ');
131  GlobList = GlobList.substr(UntrimmedGlob.size() + 1);
132  SmallString<128> RegexText("^");
133  StringRef MetaChars("()^$|*+?.[]\\{}");
134  for (char C : Glob) {
135  if (C == '*')
136  RegexText.push_back('.');
137  else if (MetaChars.find(C) != StringRef::npos)
138  RegexText.push_back('\\');
139  RegexText.push_back(C);
140  }
141  RegexText.push_back('$');
142  return llvm::Regex(RegexText);
143 }
144 
145 GlobList::GlobList(StringRef Globs)
146  : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)),
147  NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {}
148 
149 bool GlobList::contains(StringRef S, bool Contains) {
150  if (Regex.match(S))
151  Contains = Positive;
152 
153  if (NextGlob)
154  Contains = NextGlob->contains(S, Contains);
155  return Contains;
156 }
157 
159 public:
160  CachedGlobList(StringRef Globs) : Globs(Globs) {}
161 
162  bool contains(StringRef S) {
163  switch (auto &Result = Cache[S]) {
164  case Yes: return true;
165  case No: return false;
166  case None:
167  Result = Globs.contains(S) ? Yes : No;
168  return Result == Yes;
169  }
170  llvm_unreachable("invalid enum");
171  }
172 
173 private:
174  GlobList Globs;
175  enum Tristate { None, Yes, No };
176  llvm::StringMap<Tristate> Cache;
177 };
178 
180  std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
181  : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
182  Profile(nullptr) {
183  // Before the first translation unit we can get errors related to command-line
184  // parsing, use empty string for the file name in this case.
185  setCurrentFile("");
186 }
187 
189 
190 DiagnosticBuilder ClangTidyContext::diag(
191  StringRef CheckName, SourceLocation Loc, StringRef Description,
192  DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
193  assert(Loc.isValid());
194  unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
195  Level, (Description + " [" + CheckName + "]").str());
196  CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
197  return DiagEngine->Report(Loc, ID);
198 }
199 
200 void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
201  DiagEngine = Engine;
202 }
203 
204 void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
205  DiagEngine->setSourceManager(SourceMgr);
206 }
207 
209  CurrentFile = File;
210  CurrentOptions = getOptionsForFile(CurrentFile);
211  CheckFilter = llvm::make_unique<CachedGlobList>(*getOptions().Checks);
212  WarningAsErrorFilter =
213  llvm::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors);
214 }
215 
216 void ClangTidyContext::setASTContext(ASTContext *Context) {
217  DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
218  LangOpts = Context->getLangOpts();
219 }
220 
222  return OptionsProvider->getGlobalOptions();
223 }
224 
226  return CurrentOptions;
227 }
228 
230  // Merge options on top of getDefaults() as a safeguard against options with
231  // unset values.
233  OptionsProvider->getOptions(File));
234 }
235 
237 
238 bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const {
239  assert(CheckFilter != nullptr);
240  return CheckFilter->contains(CheckName);
241 }
242 
243 bool ClangTidyContext::treatAsError(StringRef CheckName) const {
244  assert(WarningAsErrorFilter != nullptr);
245  return WarningAsErrorFilter->contains(CheckName);
246 }
247 
248 /// \brief Store a \c ClangTidyError.
249 void ClangTidyContext::storeError(const ClangTidyError &Error) {
250  Errors.push_back(Error);
251 }
252 
253 StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const {
254  llvm::DenseMap<unsigned, std::string>::const_iterator I =
255  CheckNamesByDiagnosticID.find(DiagnosticID);
256  if (I != CheckNamesByDiagnosticID.end())
257  return I->second;
258  return "";
259 }
260 
262  ClangTidyContext &Ctx, bool RemoveIncompatibleErrors)
263  : Context(Ctx), RemoveIncompatibleErrors(RemoveIncompatibleErrors),
264  LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
265  LastErrorWasIgnored(false) {
266  IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
267  Diags = llvm::make_unique<DiagnosticsEngine>(
268  IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, this,
269  /*ShouldOwnClient=*/false);
270  Context.setDiagnosticsEngine(Diags.get());
271 }
272 
273 void ClangTidyDiagnosticConsumer::finalizeLastError() {
274  if (!Errors.empty()) {
275  ClangTidyError &Error = Errors.back();
276  if (!Context.isCheckEnabled(Error.DiagnosticName) &&
277  Error.DiagLevel != ClangTidyError::Error) {
278  ++Context.Stats.ErrorsIgnoredCheckFilter;
279  Errors.pop_back();
280  } else if (!LastErrorRelatesToUserCode) {
281  ++Context.Stats.ErrorsIgnoredNonUserCode;
282  Errors.pop_back();
283  } else if (!LastErrorPassesLineFilter) {
284  ++Context.Stats.ErrorsIgnoredLineFilter;
285  Errors.pop_back();
286  } else {
287  ++Context.Stats.ErrorsDisplayed;
288  }
289  }
290  LastErrorRelatesToUserCode = false;
291  LastErrorPassesLineFilter = false;
292 }
293 
294 static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line,
295  unsigned DiagID, const ClangTidyContext &Context) {
296  const size_t NolintIndex = Line.find(NolintDirectiveText);
297  if (NolintIndex == StringRef::npos)
298  return false;
299 
300  size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
301  // Check if the specific checks are specified in brackets.
302  if (BracketIndex < Line.size() && Line[BracketIndex] == '(') {
303  ++BracketIndex;
304  const size_t BracketEndIndex = Line.find(')', BracketIndex);
305  if (BracketEndIndex != StringRef::npos) {
306  StringRef ChecksStr =
307  Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
308  // Allow disabling all the checks with "*".
309  if (ChecksStr != "*") {
310  StringRef CheckName = Context.getCheckName(DiagID);
311  // Allow specifying a few check names, delimited with comma.
312  SmallVector<StringRef, 1> Checks;
313  ChecksStr.split(Checks, ',', -1, false);
314  llvm::transform(Checks, Checks.begin(),
315  [](StringRef S) { return S.trim(); });
316  return llvm::find(Checks, CheckName) != Checks.end();
317  }
318  }
319  }
320  return true;
321 }
322 
323 static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc,
324  unsigned DiagID,
325  const ClangTidyContext &Context) {
326  bool Invalid;
327  const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
328  if (Invalid)
329  return false;
330 
331  // Check if there's a NOLINT on this line.
332  const char *P = CharacterData;
333  while (*P != '\0' && *P != '\r' && *P != '\n')
334  ++P;
335  StringRef RestOfLine(CharacterData, P - CharacterData + 1);
336  if (IsNOLINTFound("NOLINT", RestOfLine, DiagID, Context))
337  return true;
338 
339  // Check if there's a NOLINTNEXTLINE on the previous line.
340  const char *BufBegin =
341  SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
342  if (Invalid || P == BufBegin)
343  return false;
344 
345  // Scan backwards over the current line.
346  P = CharacterData;
347  while (P != BufBegin && *P != '\n')
348  --P;
349 
350  // If we reached the begin of the file there is no line before it.
351  if (P == BufBegin)
352  return false;
353 
354  // Skip over the newline.
355  --P;
356  const char *LineEnd = P;
357 
358  // Now we're on the previous line. Skip to the beginning of it.
359  while (P != BufBegin && *P != '\n')
360  --P;
361 
362  RestOfLine = StringRef(P, LineEnd - P + 1);
363  if (IsNOLINTFound("NOLINTNEXTLINE", RestOfLine, DiagID, Context))
364  return true;
365 
366  return false;
367 }
368 
369 static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc,
370  unsigned DiagID,
371  const ClangTidyContext &Context) {
372  while (true) {
373  if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context))
374  return true;
375  if (!Loc.isMacroID())
376  return false;
377  Loc = SM.getImmediateExpansionRange(Loc).first;
378  }
379  return false;
380 }
381 
383  DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
384  if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
385  return;
386 
387  if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
388  DiagLevel != DiagnosticsEngine::Fatal &&
389  LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(),
390  Info.getLocation(), Info.getID(),
391  Context)) {
392  ++Context.Stats.ErrorsIgnoredNOLINT;
393  // Ignored a warning, should ignore related notes as well
394  LastErrorWasIgnored = true;
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  StringRef WarningOption =
408  Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
409  Info.getID());
410  std::string CheckName = !WarningOption.empty()
411  ? ("clang-diagnostic-" + WarningOption).str()
412  : Context.getCheckName(Info.getID()).str();
413 
414  if (CheckName.empty()) {
415  // This is a compiler diagnostic without a warning option. Assign check
416  // name based on its level.
417  switch (DiagLevel) {
418  case DiagnosticsEngine::Error:
419  case DiagnosticsEngine::Fatal:
420  CheckName = "clang-diagnostic-error";
421  break;
422  case DiagnosticsEngine::Warning:
423  CheckName = "clang-diagnostic-warning";
424  break;
425  default:
426  CheckName = "clang-diagnostic-unknown";
427  break;
428  }
429  }
430 
431  ClangTidyError::Level Level = ClangTidyError::Warning;
432  if (DiagLevel == DiagnosticsEngine::Error ||
433  DiagLevel == DiagnosticsEngine::Fatal) {
434  // Force reporting of Clang errors regardless of filters and non-user
435  // code.
436  Level = ClangTidyError::Error;
437  LastErrorRelatesToUserCode = true;
438  LastErrorPassesLineFilter = true;
439  }
440  bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
441  Context.treatAsError(CheckName);
442  Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),
443  IsWarningAsError);
444  }
445 
446  ClangTidyDiagnosticRenderer Converter(
447  Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
448  Errors.back());
449  SmallString<100> Message;
450  Info.FormatDiagnostic(Message);
451  FullSourceLoc Loc =
452  (Info.getLocation().isInvalid())
453  ? FullSourceLoc()
454  : FullSourceLoc(Info.getLocation(), Info.getSourceManager());
455  Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
456  Info.getFixItHints());
457 
458  checkFilters(Info.getLocation());
459 }
460 
461 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
462  unsigned LineNumber) const {
463  if (Context.getGlobalOptions().LineFilter.empty())
464  return true;
465  for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
466  if (FileName.endswith(Filter.Name)) {
467  if (Filter.LineRanges.empty())
468  return true;
469  for (const FileFilter::LineRange &Range : Filter.LineRanges) {
470  if (Range.first <= LineNumber && LineNumber <= Range.second)
471  return true;
472  }
473  return false;
474  }
475  }
476  return false;
477 }
478 
479 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) {
480  // Invalid location may mean a diagnostic in a command line, don't skip these.
481  if (!Location.isValid()) {
482  LastErrorRelatesToUserCode = true;
483  LastErrorPassesLineFilter = true;
484  return;
485  }
486 
487  const SourceManager &Sources = Diags->getSourceManager();
488  if (!*Context.getOptions().SystemHeaders &&
489  Sources.isInSystemHeader(Location))
490  return;
491 
492  // FIXME: We start with a conservative approach here, but the actual type of
493  // location needed depends on the check (in particular, where this check wants
494  // to apply fixes).
495  FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
496  const FileEntry *File = Sources.getFileEntryForID(FID);
497 
498  // -DMACRO definitions on the command line have locations in a virtual buffer
499  // that doesn't have a FileEntry. Don't skip these as well.
500  if (!File) {
501  LastErrorRelatesToUserCode = true;
502  LastErrorPassesLineFilter = true;
503  return;
504  }
505 
506  StringRef FileName(File->getName());
507  LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
508  Sources.isInMainFile(Location) ||
509  getHeaderFilter()->match(FileName);
510 
511  unsigned LineNumber = Sources.getExpansionLineNumber(Location);
512  LastErrorPassesLineFilter =
513  LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
514 }
515 
516 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
517  if (!HeaderFilter)
518  HeaderFilter =
519  llvm::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
520  return HeaderFilter.get();
521 }
522 
523 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
524  SmallVectorImpl<ClangTidyError> &Errors) const {
525  // Each error is modelled as the set of intervals in which it applies
526  // replacements. To detect overlapping replacements, we use a sweep line
527  // algorithm over these sets of intervals.
528  // An event here consists of the opening or closing of an interval. During the
529  // process, we maintain a counter with the amount of open intervals. If we
530  // find an endpoint of an interval and this counter is different from 0, it
531  // means that this interval overlaps with another one, so we set it as
532  // inapplicable.
533  struct Event {
534  // An event can be either the begin or the end of an interval.
535  enum EventType {
536  ET_Begin = 1,
537  ET_End = -1,
538  };
539 
540  Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,
541  unsigned ErrorSize)
542  : Type(Type), ErrorId(ErrorId) {
543  // The events are going to be sorted by their position. In case of draw:
544  //
545  // * If an interval ends at the same position at which other interval
546  // begins, this is not an overlapping, so we want to remove the ending
547  // interval before adding the starting one: end events have higher
548  // priority than begin events.
549  //
550  // * If we have several begin points at the same position, we will mark as
551  // inapplicable the ones that we process later, so the first one has to
552  // be the one with the latest end point, because this one will contain
553  // all the other intervals. For the same reason, if we have several end
554  // points in the same position, the last one has to be the one with the
555  // earliest begin point. In both cases, we sort non-increasingly by the
556  // position of the complementary.
557  //
558  // * In case of two equal intervals, the one whose error is bigger can
559  // potentially contain the other one, so we want to process its begin
560  // points before and its end points later.
561  //
562  // * Finally, if we have two equal intervals whose errors have the same
563  // size, none of them will be strictly contained inside the other.
564  // Sorting by ErrorId will guarantee that the begin point of the first
565  // one will be processed before, disallowing the second one, and the
566  // end point of the first one will also be processed before,
567  // disallowing the first one.
568  if (Type == ET_Begin)
569  Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
570  else
571  Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
572  }
573 
574  bool operator<(const Event &Other) const {
575  return Priority < Other.Priority;
576  }
577 
578  // Determines if this event is the begin or the end of an interval.
579  EventType Type;
580  // The index of the error to which the interval that generated this event
581  // belongs.
582  unsigned ErrorId;
583  // The events will be sorted based on this field.
584  std::tuple<unsigned, EventType, int, int, unsigned> Priority;
585  };
586 
587  // Compute error sizes.
588  std::vector<int> Sizes;
589  for (const auto &Error : Errors) {
590  int Size = 0;
591  for (const auto &FileAndReplaces : Error.Fix) {
592  for (const auto &Replace : FileAndReplaces.second)
593  Size += Replace.getLength();
594  }
595  Sizes.push_back(Size);
596  }
597 
598  // Build events from error intervals.
599  std::map<std::string, std::vector<Event>> FileEvents;
600  for (unsigned I = 0; I < Errors.size(); ++I) {
601  for (const auto &FileAndReplace : Errors[I].Fix) {
602  for (const auto &Replace : FileAndReplace.second) {
603  unsigned Begin = Replace.getOffset();
604  unsigned End = Begin + Replace.getLength();
605  const std::string &FilePath = Replace.getFilePath();
606  // FIXME: Handle empty intervals, such as those from insertions.
607  if (Begin == End)
608  continue;
609  auto &Events = FileEvents[FilePath];
610  Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
611  Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
612  }
613  }
614  }
615 
616  std::vector<bool> Apply(Errors.size(), true);
617  for (auto &FileAndEvents : FileEvents) {
618  std::vector<Event> &Events = FileAndEvents.second;
619  // Sweep.
620  std::sort(Events.begin(), Events.end());
621  int OpenIntervals = 0;
622  for (const auto &Event : Events) {
623  if (Event.Type == Event::ET_End)
624  --OpenIntervals;
625  // This has to be checked after removing the interval from the count if it
626  // is an end event, or before adding it if it is a begin event.
627  if (OpenIntervals != 0)
628  Apply[Event.ErrorId] = false;
629  if (Event.Type == Event::ET_Begin)
630  ++OpenIntervals;
631  }
632  assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");
633  }
634 
635  for (unsigned I = 0; I < Errors.size(); ++I) {
636  if (!Apply[I]) {
637  Errors[I].Fix.clear();
638  Errors[I].Notes.emplace_back(
639  "this fix will not be applied because it overlaps with another fix");
640  }
641  }
642 }
643 
644 namespace {
645 struct LessClangTidyError {
646  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
647  const tooling::DiagnosticMessage &M1 = LHS.Message;
648  const tooling::DiagnosticMessage &M2 = RHS.Message;
649 
650  return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
651  std::tie(M2.FilePath, M2.FileOffset, M2.Message);
652  }
653 };
654 struct EqualClangTidyError {
655  bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
656  LessClangTidyError Less;
657  return !Less(LHS, RHS) && !Less(RHS, LHS);
658  }
659 };
660 } // end anonymous namespace
661 
662 // Flushes the internal diagnostics buffer to the ClangTidyContext.
664  finalizeLastError();
665 
666  std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
667  Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
668  Errors.end());
669 
670  if (RemoveIncompatibleErrors)
671  removeIncompatibleErrors(Errors);
672 
673  for (const ClangTidyError &Error : Errors)
674  Context.storeError(Error);
675  Errors.clear();
676 }
llvm::Optional< std::string > Checks
Checks filter.
SourceLocation Loc
&#39;#&#39; location in the include directive
ClangTidyOptions mergeWith(const ClangTidyOptions &Other) const
Creates a new ClangTidyOptions instance combined from all fields of this instance overridden by the f...
Read-only set of strings represented as a list of positive and negative globs.
GlobList(StringRef Globs)
GlobList is a comma-separated list of globs (only &#39;*&#39; metacharacter is supported) with optional &#39;-&#39; p...
const ClangTidyGlobalOptions & getGlobalOptions() const
Returns global options.
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check is enabled for the CurrentFile.
HeaderHandle File
void finish() override
Flushes the internal diagnostics buffer to the ClangTidyContext.
bool contains(StringRef S)
Returns true if the pattern matches S.
llvm::Optional< std::string > HeaderFilterRegex
Output warnings from headers matching this filter.
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
Contains options for clang-tidy.
static bool ConsumeNegativeIndicator(StringRef &GlobList)
llvm::Optional< bool > SystemHeaders
Output warnings from system headers matching HeaderFilterRegex.
std::pair< unsigned, unsigned > LineRange
LineRange is a pair<start, end> (inclusive).
void setCurrentFile(StringRef File)
Should be called when starting to process new translation unit.
static llvm::Regex ConsumeGlob(StringRef &GlobList)
DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc, StringRef Message, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Report any errors detected using this method.
void setCheckProfileData(ProfileData *Profile)
Set the output struct for profile data.
ClangTidyContext(std::unique_ptr< ClangTidyOptionsProvider > OptionsProvider)
Initializes ClangTidyContext instance.
const LangOptions & getLangOpts() const
Gets the language options from the AST context.
ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory, bool IsWarningAsError)
StringRef getCheckName(unsigned DiagnosticID) const
Returns the name of the clang-tidy check which produced this diagnostic ID.
ClangTidyOptions getOptionsForFile(StringRef File) const
Returns options for File.
const ClangTidyOptions & getOptions() const
Returns options for CurrentFile.
void setASTContext(ASTContext *Context)
Sets ASTContext for the current translation unit.
std::vector< FileFilter > LineFilter
Output warnings from certain line ranges of certain files only.
llvm::Optional< std::string > WarningsAsErrors
WarningsAsErrors filter.
void setSourceManager(SourceManager *SourceMgr)
Sets the SourceManager of the used DiagnosticsEngine.
FunctionInfo Info
Contains a list of line ranges in a single file.
static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line, unsigned DiagID, const ClangTidyContext &Context)
CharSourceRange Range
SourceRange for the file name.
A detected error complete with information to display diagnostic and automatic fix.
static cl::opt< std::string > Checks("checks", cl::desc(R"( Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' prefix add checks with matching names to the set, globs with the '-' prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of the 'Checks' option in .clang-tidy file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory))
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)
static ClangTidyOptions getDefaults()
These options are used for all settings that haven&#39;t been overridden by the OptionsProvider.
static cl::opt< bool > Fix("fix", cl::desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override
bool treatAsError(StringRef CheckName) const
Returns true if the check should be upgraded to error for the CurrentFile.
ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, bool RemoveIncompatibleErrors=true)
Container for clang-tidy profiling data.
const std::string & getCurrentBuildDirectory()
Returns build directory of the current translation unit.
static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)