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