• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== //
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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 ///
9 ///  \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext
10 ///  and ClangTidyError classes.
11 ///
12 ///  This tool uses the Clang Tooling infrastructure, see
13 ///    http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
14 ///  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 "clang/AST/ASTContext.h"
22 #include "clang/AST/ASTDiagnostic.h"
23 #include "clang/AST/Attr.h"
24 #include "clang/Basic/Diagnostic.h"
25 #include "clang/Basic/DiagnosticOptions.h"
26 #include "clang/Basic/FileManager.h"
27 #include "clang/Basic/SourceManager.h"
28 #include "clang/Frontend/DiagnosticRenderer.h"
29 #include "clang/Tooling/Core/Diagnostic.h"
30 #include "clang/Tooling/Core/Replacement.h"
31 #include "llvm/ADT/STLExtras.h"
32 #include "llvm/ADT/SmallString.h"
33 #include "llvm/ADT/StringMap.h"
34 #include "llvm/Support/ErrorHandling.h"
35 #include "llvm/Support/FormatVariadic.h"
36 #include "llvm/Support/Regex.h"
37 #include <tuple>
38 #include <vector>
39 using namespace clang;
40 using namespace tidy;
41 
42 namespace {
43 class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {
44 public:
ClangTidyDiagnosticRenderer(const LangOptions & LangOpts,DiagnosticOptions * DiagOpts,ClangTidyError & Error)45   ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,
46                               DiagnosticOptions *DiagOpts,
47                               ClangTidyError &Error)
48       : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
49 
50 protected:
emitDiagnosticMessage(FullSourceLoc Loc,PresumedLoc PLoc,DiagnosticsEngine::Level Level,StringRef Message,ArrayRef<CharSourceRange> Ranges,DiagOrStoredDiag Info)51   void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc,
52                              DiagnosticsEngine::Level Level, StringRef Message,
53                              ArrayRef<CharSourceRange> Ranges,
54                              DiagOrStoredDiag Info) override {
55     // Remove check name from the message.
56     // FIXME: Remove this once there's a better way to pass check names than
57     // appending the check name to the message in ClangTidyContext::diag and
58     // using getCustomDiagID.
59     std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]";
60     if (Message.endswith(CheckNameInMessage))
61       Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
62 
63     auto TidyMessage =
64         Loc.isValid()
65             ? tooling::DiagnosticMessage(Message, Loc.getManager(), Loc)
66             : tooling::DiagnosticMessage(Message);
67     if (Level == DiagnosticsEngine::Note) {
68       Error.Notes.push_back(TidyMessage);
69       return;
70     }
71     assert(Error.Message.Message.empty() && "Overwriting a diagnostic message");
72     Error.Message = TidyMessage;
73     for (const CharSourceRange &SourceRange : Ranges) {
74       Error.Ranges.emplace_back(Loc.getManager(), SourceRange);
75     }
76   }
77 
emitDiagnosticLoc(FullSourceLoc Loc,PresumedLoc PLoc,DiagnosticsEngine::Level Level,ArrayRef<CharSourceRange> Ranges)78   void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
79                          DiagnosticsEngine::Level Level,
80                          ArrayRef<CharSourceRange> Ranges) override {}
81 
emitCodeContext(FullSourceLoc Loc,DiagnosticsEngine::Level Level,SmallVectorImpl<CharSourceRange> & Ranges,ArrayRef<FixItHint> Hints)82   void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
83                        SmallVectorImpl<CharSourceRange> &Ranges,
84                        ArrayRef<FixItHint> Hints) override {
85     assert(Loc.isValid());
86     tooling::DiagnosticMessage *DiagWithFix =
87         Level == DiagnosticsEngine::Note ? &Error.Notes.back() : &Error.Message;
88 
89     for (const auto &FixIt : Hints) {
90       CharSourceRange Range = FixIt.RemoveRange;
91       assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
92              "Invalid range in the fix-it hint.");
93       assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
94              "Only file locations supported in fix-it hints.");
95 
96       tooling::Replacement Replacement(Loc.getManager(), Range,
97                                        FixIt.CodeToInsert);
98       llvm::Error Err =
99           DiagWithFix->Fix[Replacement.getFilePath()].add(Replacement);
100       // FIXME: better error handling (at least, don't let other replacements be
101       // applied).
102       if (Err) {
103         llvm::errs() << "Fix conflicts with existing fix! "
104                      << llvm::toString(std::move(Err)) << "\n";
105         assert(false && "Fix conflicts with existing fix!");
106       }
107     }
108   }
109 
emitIncludeLocation(FullSourceLoc Loc,PresumedLoc PLoc)110   void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override {}
111 
emitImportLocation(FullSourceLoc Loc,PresumedLoc PLoc,StringRef ModuleName)112   void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
113                           StringRef ModuleName) override {}
114 
emitBuildingModuleLocation(FullSourceLoc Loc,PresumedLoc PLoc,StringRef ModuleName)115   void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
116                                   StringRef ModuleName) override {}
117 
endDiagnostic(DiagOrStoredDiag D,DiagnosticsEngine::Level Level)118   void endDiagnostic(DiagOrStoredDiag D,
119                      DiagnosticsEngine::Level Level) override {
120     assert(!Error.Message.Message.empty() && "Message has not been set");
121   }
122 
123 private:
124   ClangTidyError &Error;
125 };
126 } // end anonymous namespace
127 
ClangTidyError(StringRef CheckName,ClangTidyError::Level DiagLevel,StringRef BuildDirectory,bool IsWarningAsError)128 ClangTidyError::ClangTidyError(StringRef CheckName,
129                                ClangTidyError::Level DiagLevel,
130                                StringRef BuildDirectory, bool IsWarningAsError)
131     : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
132       IsWarningAsError(IsWarningAsError) {}
133 
134 class ClangTidyContext::CachedGlobList {
135 public:
CachedGlobList(StringRef Globs)136   CachedGlobList(StringRef Globs) : Globs(Globs) {}
137 
contains(StringRef S)138   bool contains(StringRef S) {
139     switch (auto &Result = Cache[S]) {
140     case Yes:
141       return true;
142     case No:
143       return false;
144     case None:
145       Result = Globs.contains(S) ? Yes : No;
146       return Result == Yes;
147     }
148     llvm_unreachable("invalid enum");
149   }
150 
151 private:
152   GlobList Globs;
153   enum Tristate { None, Yes, No };
154   llvm::StringMap<Tristate> Cache;
155 };
156 
ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,bool AllowEnablingAnalyzerAlphaCheckers)157 ClangTidyContext::ClangTidyContext(
158     std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
159     bool AllowEnablingAnalyzerAlphaCheckers)
160     : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
161       Profile(false),
162       AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers) {
163   // Before the first translation unit we can get errors related to command-line
164   // parsing, use empty string for the file name in this case.
165   setCurrentFile("");
166 }
167 
168 ClangTidyContext::~ClangTidyContext() = default;
169 
diag(StringRef CheckName,SourceLocation Loc,StringRef Description,DiagnosticIDs::Level Level)170 DiagnosticBuilder ClangTidyContext::diag(
171     StringRef CheckName, SourceLocation Loc, StringRef Description,
172     DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
173   assert(Loc.isValid());
174   unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
175       Level, (Description + " [" + CheckName + "]").str());
176   CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
177   return DiagEngine->Report(Loc, ID);
178 }
179 
diag(StringRef CheckName,StringRef Description,DiagnosticIDs::Level Level)180 DiagnosticBuilder ClangTidyContext::diag(
181     StringRef CheckName, StringRef Description,
182     DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
183   unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
184       Level, (Description + " [" + CheckName + "]").str());
185   CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
186   return DiagEngine->Report(ID);
187 }
188 
configurationDiag(StringRef Message,DiagnosticIDs::Level Level)189 DiagnosticBuilder ClangTidyContext::configurationDiag(
190     StringRef Message,
191     DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
192   return diag("clang-tidy-config", Message, Level);
193 }
194 
setSourceManager(SourceManager * SourceMgr)195 void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
196   DiagEngine->setSourceManager(SourceMgr);
197 }
198 
setCurrentFile(StringRef File)199 void ClangTidyContext::setCurrentFile(StringRef File) {
200   CurrentFile = std::string(File);
201   CurrentOptions = getOptionsForFile(CurrentFile);
202   CheckFilter = std::make_unique<CachedGlobList>(*getOptions().Checks);
203   WarningAsErrorFilter =
204       std::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors);
205 }
206 
setASTContext(ASTContext * Context)207 void ClangTidyContext::setASTContext(ASTContext *Context) {
208   DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
209   LangOpts = Context->getLangOpts();
210 }
211 
getGlobalOptions() const212 const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const {
213   return OptionsProvider->getGlobalOptions();
214 }
215 
getOptions() const216 const ClangTidyOptions &ClangTidyContext::getOptions() const {
217   return CurrentOptions;
218 }
219 
getOptionsForFile(StringRef File) const220 ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const {
221   // Merge options on top of getDefaults() as a safeguard against options with
222   // unset values.
223   return ClangTidyOptions::getDefaults().merge(
224       OptionsProvider->getOptions(File), 0);
225 }
226 
setEnableProfiling(bool P)227 void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; }
228 
setProfileStoragePrefix(StringRef Prefix)229 void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix) {
230   ProfilePrefix = std::string(Prefix);
231 }
232 
233 llvm::Optional<ClangTidyProfiling::StorageParams>
getProfileStorageParams() const234 ClangTidyContext::getProfileStorageParams() const {
235   if (ProfilePrefix.empty())
236     return llvm::None;
237 
238   return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile);
239 }
240 
isCheckEnabled(StringRef CheckName) const241 bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const {
242   assert(CheckFilter != nullptr);
243   return CheckFilter->contains(CheckName);
244 }
245 
treatAsError(StringRef CheckName) const246 bool ClangTidyContext::treatAsError(StringRef CheckName) const {
247   assert(WarningAsErrorFilter != nullptr);
248   return WarningAsErrorFilter->contains(CheckName);
249 }
250 
getCheckName(unsigned DiagnosticID) const251 std::string ClangTidyContext::getCheckName(unsigned DiagnosticID) const {
252   std::string ClangWarningOption = std::string(
253       DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID));
254   if (!ClangWarningOption.empty())
255     return "clang-diagnostic-" + ClangWarningOption;
256   llvm::DenseMap<unsigned, std::string>::const_iterator I =
257       CheckNamesByDiagnosticID.find(DiagnosticID);
258   if (I != CheckNamesByDiagnosticID.end())
259     return I->second;
260   return "";
261 }
262 
ClangTidyDiagnosticConsumer(ClangTidyContext & Ctx,DiagnosticsEngine * ExternalDiagEngine,bool RemoveIncompatibleErrors)263 ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(
264     ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine,
265     bool RemoveIncompatibleErrors)
266     : Context(Ctx), ExternalDiagEngine(ExternalDiagEngine),
267       RemoveIncompatibleErrors(RemoveIncompatibleErrors),
268       LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
269       LastErrorWasIgnored(false) {}
270 
finalizeLastError()271 void ClangTidyDiagnosticConsumer::finalizeLastError() {
272   if (!Errors.empty()) {
273     ClangTidyError &Error = Errors.back();
274     if (Error.DiagnosticName == "clang-tidy-config") {
275       // Never ignore these.
276     } else 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 
IsNOLINTFound(StringRef NolintDirectiveText,StringRef Line,unsigned DiagID,const ClangTidyContext & Context)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         std::string 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 
getBuffer(const SourceManager & SM,FileID File,bool AllowIO)323 static llvm::Optional<StringRef> getBuffer(const SourceManager &SM, FileID File,
324                                            bool AllowIO) {
325   return AllowIO ? SM.getBufferDataOrNone(File)
326                  : SM.getBufferDataIfLoaded(File);
327 }
328 
LineIsMarkedWithNOLINT(const SourceManager & SM,SourceLocation Loc,unsigned DiagID,const ClangTidyContext & Context,bool AllowIO)329 static bool LineIsMarkedWithNOLINT(const SourceManager &SM, SourceLocation Loc,
330                                    unsigned DiagID,
331                                    const ClangTidyContext &Context,
332                                    bool AllowIO) {
333   FileID File;
334   unsigned Offset;
335   std::tie(File, Offset) = SM.getDecomposedSpellingLoc(Loc);
336   llvm::Optional<StringRef> Buffer = getBuffer(SM, File, AllowIO);
337   if (!Buffer)
338     return false;
339 
340   // Check if there's a NOLINT on this line.
341   StringRef RestOfLine = Buffer->substr(Offset).split('\n').first;
342   if (IsNOLINTFound("NOLINT", RestOfLine, DiagID, Context))
343     return true;
344 
345   // Check if there's a NOLINTNEXTLINE on the previous line.
346   StringRef PrevLine =
347       Buffer->substr(0, Offset).rsplit('\n').first.rsplit('\n').second;
348   return IsNOLINTFound("NOLINTNEXTLINE", PrevLine, DiagID, Context);
349 }
350 
LineIsMarkedWithNOLINTinMacro(const SourceManager & SM,SourceLocation Loc,unsigned DiagID,const ClangTidyContext & Context,bool AllowIO)351 static bool LineIsMarkedWithNOLINTinMacro(const SourceManager &SM,
352                                           SourceLocation Loc, unsigned DiagID,
353                                           const ClangTidyContext &Context,
354                                           bool AllowIO) {
355   while (true) {
356     if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context, AllowIO))
357       return true;
358     if (!Loc.isMacroID())
359       return false;
360     Loc = SM.getImmediateExpansionRange(Loc).getBegin();
361   }
362   return false;
363 }
364 
365 namespace clang {
366 namespace tidy {
367 
shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Info,ClangTidyContext & Context,bool AllowIO)368 bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
369                               const Diagnostic &Info, ClangTidyContext &Context,
370                               bool AllowIO) {
371   return Info.getLocation().isValid() &&
372          DiagLevel != DiagnosticsEngine::Error &&
373          DiagLevel != DiagnosticsEngine::Fatal &&
374          LineIsMarkedWithNOLINTinMacro(Info.getSourceManager(),
375                                        Info.getLocation(), Info.getID(),
376                                        Context, AllowIO);
377 }
378 
379 } // namespace tidy
380 } // namespace clang
381 
HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Info)382 void ClangTidyDiagnosticConsumer::HandleDiagnostic(
383     DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
384   if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
385     return;
386 
387   if (shouldSuppressDiagnostic(DiagLevel, Info, Context)) {
388     ++Context.Stats.ErrorsIgnoredNOLINT;
389     // Ignored a warning, should ignore related notes as well
390     LastErrorWasIgnored = true;
391     return;
392   }
393 
394   LastErrorWasIgnored = false;
395   // Count warnings/errors.
396   DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
397 
398   if (DiagLevel == DiagnosticsEngine::Note) {
399     assert(!Errors.empty() &&
400            "A diagnostic note can only be appended to a message.");
401   } else {
402     finalizeLastError();
403     std::string CheckName = Context.getCheckName(Info.getID());
404     if (CheckName.empty()) {
405       // This is a compiler diagnostic without a warning option. Assign check
406       // name based on its level.
407       switch (DiagLevel) {
408       case DiagnosticsEngine::Error:
409       case DiagnosticsEngine::Fatal:
410         CheckName = "clang-diagnostic-error";
411         break;
412       case DiagnosticsEngine::Warning:
413         CheckName = "clang-diagnostic-warning";
414         break;
415       default:
416         CheckName = "clang-diagnostic-unknown";
417         break;
418       }
419     }
420 
421     ClangTidyError::Level Level = ClangTidyError::Warning;
422     if (DiagLevel == DiagnosticsEngine::Error ||
423         DiagLevel == DiagnosticsEngine::Fatal) {
424       // Force reporting of Clang errors regardless of filters and non-user
425       // code.
426       Level = ClangTidyError::Error;
427       LastErrorRelatesToUserCode = true;
428       LastErrorPassesLineFilter = true;
429     }
430     bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
431                             Context.treatAsError(CheckName);
432     Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),
433                         IsWarningAsError);
434   }
435 
436   if (ExternalDiagEngine) {
437     // If there is an external diagnostics engine, like in the
438     // ClangTidyPluginAction case, forward the diagnostics to it.
439     forwardDiagnostic(Info);
440   } else {
441     ClangTidyDiagnosticRenderer Converter(
442         Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
443         Errors.back());
444     SmallString<100> Message;
445     Info.FormatDiagnostic(Message);
446     FullSourceLoc Loc;
447     if (Info.getLocation().isValid() && Info.hasSourceManager())
448       Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager());
449     Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
450                              Info.getFixItHints());
451   }
452 
453   if (Info.hasSourceManager())
454     checkFilters(Info.getLocation(), Info.getSourceManager());
455 }
456 
passesLineFilter(StringRef FileName,unsigned LineNumber) const457 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
458                                                    unsigned LineNumber) const {
459   if (Context.getGlobalOptions().LineFilter.empty())
460     return true;
461   for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
462     if (FileName.endswith(Filter.Name)) {
463       if (Filter.LineRanges.empty())
464         return true;
465       for (const FileFilter::LineRange &Range : Filter.LineRanges) {
466         if (Range.first <= LineNumber && LineNumber <= Range.second)
467           return true;
468       }
469       return false;
470     }
471   }
472   return false;
473 }
474 
forwardDiagnostic(const Diagnostic & Info)475 void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) {
476   // Acquire a diagnostic ID also in the external diagnostics engine.
477   auto DiagLevelAndFormatString =
478       Context.getDiagLevelAndFormatString(Info.getID(), Info.getLocation());
479   unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID(
480       DiagLevelAndFormatString.first, DiagLevelAndFormatString.second);
481 
482   // Forward the details.
483   auto Builder = ExternalDiagEngine->Report(Info.getLocation(), ExternalID);
484   for (auto Hint : Info.getFixItHints())
485     Builder << Hint;
486   for (auto Range : Info.getRanges())
487     Builder << Range;
488   for (unsigned Index = 0; Index < Info.getNumArgs(); ++Index) {
489     DiagnosticsEngine::ArgumentKind Kind = Info.getArgKind(Index);
490     switch (Kind) {
491     case clang::DiagnosticsEngine::ak_std_string:
492       Builder << Info.getArgStdStr(Index);
493       break;
494     case clang::DiagnosticsEngine::ak_c_string:
495       Builder << Info.getArgCStr(Index);
496       break;
497     case clang::DiagnosticsEngine::ak_sint:
498       Builder << Info.getArgSInt(Index);
499       break;
500     case clang::DiagnosticsEngine::ak_uint:
501       Builder << Info.getArgUInt(Index);
502       break;
503     case clang::DiagnosticsEngine::ak_tokenkind:
504       Builder << static_cast<tok::TokenKind>(Info.getRawArg(Index));
505       break;
506     case clang::DiagnosticsEngine::ak_identifierinfo:
507       Builder << Info.getArgIdentifier(Index);
508       break;
509     case clang::DiagnosticsEngine::ak_qual:
510       Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(Index));
511       break;
512     case clang::DiagnosticsEngine::ak_qualtype:
513       Builder << QualType::getFromOpaquePtr((void *)Info.getRawArg(Index));
514       break;
515     case clang::DiagnosticsEngine::ak_declarationname:
516       Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(Index));
517       break;
518     case clang::DiagnosticsEngine::ak_nameddecl:
519       Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Index));
520       break;
521     case clang::DiagnosticsEngine::ak_nestednamespec:
522       Builder << reinterpret_cast<NestedNameSpecifier *>(Info.getRawArg(Index));
523       break;
524     case clang::DiagnosticsEngine::ak_declcontext:
525       Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(Index));
526       break;
527     case clang::DiagnosticsEngine::ak_qualtype_pair:
528       assert(false); // This one is not passed around.
529       break;
530     case clang::DiagnosticsEngine::ak_attr:
531       Builder << reinterpret_cast<Attr *>(Info.getRawArg(Index));
532       break;
533     case clang::DiagnosticsEngine::ak_addrspace:
534       Builder << static_cast<LangAS>(Info.getRawArg(Index));
535       break;
536     }
537   }
538 }
539 
checkFilters(SourceLocation Location,const SourceManager & Sources)540 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
541                                                const SourceManager &Sources) {
542   // Invalid location may mean a diagnostic in a command line, don't skip these.
543   if (!Location.isValid()) {
544     LastErrorRelatesToUserCode = true;
545     LastErrorPassesLineFilter = true;
546     return;
547   }
548 
549   if (!*Context.getOptions().SystemHeaders &&
550       Sources.isInSystemHeader(Location))
551     return;
552 
553   // FIXME: We start with a conservative approach here, but the actual type of
554   // location needed depends on the check (in particular, where this check wants
555   // to apply fixes).
556   FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
557   const FileEntry *File = Sources.getFileEntryForID(FID);
558 
559   // -DMACRO definitions on the command line have locations in a virtual buffer
560   // that doesn't have a FileEntry. Don't skip these as well.
561   if (!File) {
562     LastErrorRelatesToUserCode = true;
563     LastErrorPassesLineFilter = true;
564     return;
565   }
566 
567   StringRef FileName(File->getName());
568   LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
569                                Sources.isInMainFile(Location) ||
570                                getHeaderFilter()->match(FileName);
571 
572   unsigned LineNumber = Sources.getExpansionLineNumber(Location);
573   LastErrorPassesLineFilter =
574       LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
575 }
576 
getHeaderFilter()577 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
578   if (!HeaderFilter)
579     HeaderFilter =
580         std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
581   return HeaderFilter.get();
582 }
583 
removeIncompatibleErrors()584 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
585   // Each error is modelled as the set of intervals in which it applies
586   // replacements. To detect overlapping replacements, we use a sweep line
587   // algorithm over these sets of intervals.
588   // An event here consists of the opening or closing of an interval. During the
589   // process, we maintain a counter with the amount of open intervals. If we
590   // find an endpoint of an interval and this counter is different from 0, it
591   // means that this interval overlaps with another one, so we set it as
592   // inapplicable.
593   struct Event {
594     // An event can be either the begin or the end of an interval.
595     enum EventType {
596       ET_Begin = 1,
597       ET_Insert = 0,
598       ET_End = -1,
599     };
600 
601     Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,
602           unsigned ErrorSize)
603         : Type(Type), ErrorId(ErrorId) {
604       // The events are going to be sorted by their position. In case of draw:
605       //
606       // * If an interval ends at the same position at which other interval
607       //   begins, this is not an overlapping, so we want to remove the ending
608       //   interval before adding the starting one: end events have higher
609       //   priority than begin events.
610       //
611       // * If we have several begin points at the same position, we will mark as
612       //   inapplicable the ones that we process later, so the first one has to
613       //   be the one with the latest end point, because this one will contain
614       //   all the other intervals. For the same reason, if we have several end
615       //   points in the same position, the last one has to be the one with the
616       //   earliest begin point. In both cases, we sort non-increasingly by the
617       //   position of the complementary.
618       //
619       // * In case of two equal intervals, the one whose error is bigger can
620       //   potentially contain the other one, so we want to process its begin
621       //   points before and its end points later.
622       //
623       // * Finally, if we have two equal intervals whose errors have the same
624       //   size, none of them will be strictly contained inside the other.
625       //   Sorting by ErrorId will guarantee that the begin point of the first
626       //   one will be processed before, disallowing the second one, and the
627       //   end point of the first one will also be processed before,
628       //   disallowing the first one.
629       switch (Type) {
630       case ET_Begin:
631         Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
632         break;
633       case ET_Insert:
634         Priority = std::make_tuple(Begin, Type, -End, ErrorSize, ErrorId);
635         break;
636       case ET_End:
637         Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
638         break;
639       }
640     }
641 
642     bool operator<(const Event &Other) const {
643       return Priority < Other.Priority;
644     }
645 
646     // Determines if this event is the begin or the end of an interval.
647     EventType Type;
648     // The index of the error to which the interval that generated this event
649     // belongs.
650     unsigned ErrorId;
651     // The events will be sorted based on this field.
652     std::tuple<unsigned, EventType, int, int, unsigned> Priority;
653   };
654 
655   removeDuplicatedDiagnosticsOfAliasCheckers();
656 
657   // Compute error sizes.
658   std::vector<int> Sizes;
659   std::vector<
660       std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>>
661       ErrorFixes;
662   for (auto &Error : Errors) {
663     if (const auto *Fix = tooling::selectFirstFix(Error))
664       ErrorFixes.emplace_back(
665           &Error, const_cast<llvm::StringMap<tooling::Replacements> *>(Fix));
666   }
667   for (const auto &ErrorAndFix : ErrorFixes) {
668     int Size = 0;
669     for (const auto &FileAndReplaces : *ErrorAndFix.second) {
670       for (const auto &Replace : FileAndReplaces.second)
671         Size += Replace.getLength();
672     }
673     Sizes.push_back(Size);
674   }
675 
676   // Build events from error intervals.
677   llvm::StringMap<std::vector<Event>> FileEvents;
678   for (unsigned I = 0; I < ErrorFixes.size(); ++I) {
679     for (const auto &FileAndReplace : *ErrorFixes[I].second) {
680       for (const auto &Replace : FileAndReplace.second) {
681         unsigned Begin = Replace.getOffset();
682         unsigned End = Begin + Replace.getLength();
683         auto &Events = FileEvents[Replace.getFilePath()];
684         if (Begin == End) {
685           Events.emplace_back(Begin, End, Event::ET_Insert, I, Sizes[I]);
686         } else {
687           Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
688           Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
689         }
690       }
691     }
692   }
693 
694   std::vector<bool> Apply(ErrorFixes.size(), true);
695   for (auto &FileAndEvents : FileEvents) {
696     std::vector<Event> &Events = FileAndEvents.second;
697     // Sweep.
698     llvm::sort(Events);
699     int OpenIntervals = 0;
700     for (const auto &Event : Events) {
701       switch (Event.Type) {
702       case Event::ET_Begin:
703         if (OpenIntervals++ != 0)
704           Apply[Event.ErrorId] = false;
705         break;
706       case Event::ET_Insert:
707         if (OpenIntervals != 0)
708           Apply[Event.ErrorId] = false;
709         break;
710       case Event::ET_End:
711         if (--OpenIntervals != 0)
712           Apply[Event.ErrorId] = false;
713         break;
714       }
715     }
716     assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");
717   }
718 
719   for (unsigned I = 0; I < ErrorFixes.size(); ++I) {
720     if (!Apply[I]) {
721       ErrorFixes[I].second->clear();
722       ErrorFixes[I].first->Notes.emplace_back(
723           "this fix will not be applied because it overlaps with another fix");
724     }
725   }
726 }
727 
728 namespace {
729 struct LessClangTidyError {
operator ()__anonbe6389250311::LessClangTidyError730   bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
731     const tooling::DiagnosticMessage &M1 = LHS.Message;
732     const tooling::DiagnosticMessage &M2 = RHS.Message;
733 
734     return std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName,
735                     M1.Message) <
736            std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message);
737   }
738 };
739 struct EqualClangTidyError {
operator ()__anonbe6389250311::EqualClangTidyError740   bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
741     LessClangTidyError Less;
742     return !Less(LHS, RHS) && !Less(RHS, LHS);
743   }
744 };
745 } // end anonymous namespace
746 
take()747 std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() {
748   finalizeLastError();
749 
750   llvm::stable_sort(Errors, LessClangTidyError());
751   Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
752                Errors.end());
753   if (RemoveIncompatibleErrors)
754     removeIncompatibleErrors();
755   return std::move(Errors);
756 }
757 
758 namespace {
759 struct LessClangTidyErrorWithoutDiagnosticName {
operator ()__anonbe6389250411::LessClangTidyErrorWithoutDiagnosticName760   bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const {
761     const tooling::DiagnosticMessage &M1 = LHS->Message;
762     const tooling::DiagnosticMessage &M2 = RHS->Message;
763 
764     return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
765            std::tie(M2.FilePath, M2.FileOffset, M2.Message);
766   }
767 };
768 } // end anonymous namespace
769 
removeDuplicatedDiagnosticsOfAliasCheckers()770 void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {
771   using UniqueErrorSet =
772       std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>;
773   UniqueErrorSet UniqueErrors;
774 
775   auto IT = Errors.begin();
776   while (IT != Errors.end()) {
777     ClangTidyError &Error = *IT;
778     std::pair<UniqueErrorSet::iterator, bool> Inserted =
779         UniqueErrors.insert(&Error);
780 
781     // Unique error, we keep it and move along.
782     if (Inserted.second) {
783       ++IT;
784     } else {
785       ClangTidyError &ExistingError = **Inserted.first;
786       const llvm::StringMap<tooling::Replacements> &CandidateFix =
787           Error.Message.Fix;
788       const llvm::StringMap<tooling::Replacements> &ExistingFix =
789           (*Inserted.first)->Message.Fix;
790 
791       if (CandidateFix != ExistingFix) {
792 
793         // In case of a conflict, don't suggest any fix-it.
794         ExistingError.Message.Fix.clear();
795         ExistingError.Notes.emplace_back(
796             llvm::formatv("cannot apply fix-it because an alias checker has "
797                           "suggested a different fix-it; please remove one of "
798                           "the checkers ('{0}', '{1}') or "
799                           "ensure they are both configured the same",
800                           ExistingError.DiagnosticName, Error.DiagnosticName)
801                 .str());
802       }
803 
804       if (Error.IsWarningAsError)
805         ExistingError.IsWarningAsError = true;
806 
807       // Since it is the same error, we should take it as alias and remove it.
808       ExistingError.EnabledDiagnosticAliases.emplace_back(Error.DiagnosticName);
809       IT = Errors.erase(IT);
810     }
811   }
812 }
813