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