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