1 //===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== //
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
7 //===----------------------------------------------------------------------===//
9 /// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext
10 /// and ClangTidyError classes.
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.
16 //===----------------------------------------------------------------------===//
18 #include "ClangTidyDiagnosticConsumer.h"
19 #include "ClangTidyOptions.h"
21 #include "NoLintDirectiveHandler.h"
22 #include "clang/AST/ASTContext.h"
23 #include "clang/AST/ASTDiagnostic.h"
24 #include "clang/AST/Attr.h"
25 #include "clang/Basic/CharInfo.h"
26 #include "clang/Basic/Diagnostic.h"
27 #include "clang/Basic/DiagnosticOptions.h"
28 #include "clang/Basic/FileManager.h"
29 #include "clang/Basic/SourceManager.h"
30 #include "clang/Frontend/DiagnosticRenderer.h"
31 #include "clang/Lex/Lexer.h"
32 #include "clang/Tooling/Core/Diagnostic.h"
33 #include "clang/Tooling/Core/Replacement.h"
34 #include "llvm/ADT/BitVector.h"
35 #include "llvm/ADT/STLExtras.h"
36 #include "llvm/ADT/SmallString.h"
37 #include "llvm/ADT/StringMap.h"
38 #include "llvm/Support/FormatVariadic.h"
39 #include "llvm/Support/Regex.h"
44 using namespace clang
;
48 class ClangTidyDiagnosticRenderer
: public DiagnosticRenderer
{
50 ClangTidyDiagnosticRenderer(const LangOptions
&LangOpts
,
51 DiagnosticOptions
*DiagOpts
,
52 ClangTidyError
&Error
)
53 : DiagnosticRenderer(LangOpts
, DiagOpts
), Error(Error
) {}
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 if (Message
.endswith(CheckNameInMessage
))
66 Message
= Message
.substr(0, Message
.size() - CheckNameInMessage
.size());
70 ? tooling::DiagnosticMessage(Message
, Loc
.getManager(), Loc
)
71 : tooling::DiagnosticMessage(Message
);
73 // Make sure that if a TokenRange is received from the check it is unfurled
74 // into a real CharRange for the diagnostic printer later.
75 // Whatever we store here gets decoupled from the current SourceManager, so
76 // we **have to** know the exact position and length of the highlight.
77 auto ToCharRange
= [this, &Loc
](const CharSourceRange
&SourceRange
) {
78 if (SourceRange
.isCharRange())
80 assert(SourceRange
.isTokenRange());
81 SourceLocation End
= Lexer::getLocForEndOfToken(
82 SourceRange
.getEnd(), 0, Loc
.getManager(), LangOpts
);
83 return CharSourceRange::getCharRange(SourceRange
.getBegin(), End
);
86 // We are only interested in valid ranges.
88 llvm::make_filter_range(Ranges
, [](const CharSourceRange
&R
) {
89 return R
.getAsRange().isValid();
92 if (Level
== DiagnosticsEngine::Note
) {
93 Error
.Notes
.push_back(TidyMessage
);
94 for (const CharSourceRange
&SourceRange
: ValidRanges
)
95 Error
.Notes
.back().Ranges
.emplace_back(Loc
.getManager(),
96 ToCharRange(SourceRange
));
99 assert(Error
.Message
.Message
.empty() && "Overwriting a diagnostic message");
100 Error
.Message
= TidyMessage
;
101 for (const CharSourceRange
&SourceRange
: ValidRanges
)
102 Error
.Message
.Ranges
.emplace_back(Loc
.getManager(),
103 ToCharRange(SourceRange
));
106 void emitDiagnosticLoc(FullSourceLoc Loc
, PresumedLoc PLoc
,
107 DiagnosticsEngine::Level Level
,
108 ArrayRef
<CharSourceRange
> Ranges
) override
{}
110 void emitCodeContext(FullSourceLoc Loc
, DiagnosticsEngine::Level Level
,
111 SmallVectorImpl
<CharSourceRange
> &Ranges
,
112 ArrayRef
<FixItHint
> Hints
) override
{
113 assert(Loc
.isValid());
114 tooling::DiagnosticMessage
*DiagWithFix
=
115 Level
== DiagnosticsEngine::Note
? &Error
.Notes
.back() : &Error
.Message
;
117 for (const auto &FixIt
: Hints
) {
118 CharSourceRange Range
= FixIt
.RemoveRange
;
119 assert(Range
.getBegin().isValid() && Range
.getEnd().isValid() &&
120 "Invalid range in the fix-it hint.");
121 assert(Range
.getBegin().isFileID() && Range
.getEnd().isFileID() &&
122 "Only file locations supported in fix-it hints.");
124 tooling::Replacement
Replacement(Loc
.getManager(), Range
,
127 DiagWithFix
->Fix
[Replacement
.getFilePath()].add(Replacement
);
128 // FIXME: better error handling (at least, don't let other replacements be
131 llvm::errs() << "Fix conflicts with existing fix! "
132 << llvm::toString(std::move(Err
)) << "\n";
133 assert(false && "Fix conflicts with existing fix!");
138 void emitIncludeLocation(FullSourceLoc Loc
, PresumedLoc PLoc
) override
{}
140 void emitImportLocation(FullSourceLoc Loc
, PresumedLoc PLoc
,
141 StringRef ModuleName
) override
{}
143 void emitBuildingModuleLocation(FullSourceLoc Loc
, PresumedLoc PLoc
,
144 StringRef ModuleName
) override
{}
146 void endDiagnostic(DiagOrStoredDiag D
,
147 DiagnosticsEngine::Level Level
) override
{
148 assert(!Error
.Message
.Message
.empty() && "Message has not been set");
152 ClangTidyError
&Error
;
154 } // end anonymous namespace
156 ClangTidyError::ClangTidyError(StringRef CheckName
,
157 ClangTidyError::Level DiagLevel
,
158 StringRef BuildDirectory
, bool IsWarningAsError
)
159 : tooling::Diagnostic(CheckName
, DiagLevel
, BuildDirectory
),
160 IsWarningAsError(IsWarningAsError
) {}
162 ClangTidyContext::ClangTidyContext(
163 std::unique_ptr
<ClangTidyOptionsProvider
> OptionsProvider
,
164 bool AllowEnablingAnalyzerAlphaCheckers
)
165 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider
)),
167 AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers
),
168 SelfContainedDiags(false) {
169 // Before the first translation unit we can get errors related to command-line
170 // parsing, use empty string for the file name in this case.
174 ClangTidyContext::~ClangTidyContext() = default;
176 DiagnosticBuilder
ClangTidyContext::diag(
177 StringRef CheckName
, SourceLocation Loc
, StringRef Description
,
178 DiagnosticIDs::Level Level
/* = DiagnosticIDs::Warning*/) {
179 assert(Loc
.isValid());
180 unsigned ID
= DiagEngine
->getDiagnosticIDs()->getCustomDiagID(
181 Level
, (Description
+ " [" + CheckName
+ "]").str());
182 CheckNamesByDiagnosticID
.try_emplace(ID
, CheckName
);
183 return DiagEngine
->Report(Loc
, ID
);
186 DiagnosticBuilder
ClangTidyContext::diag(
187 StringRef CheckName
, StringRef Description
,
188 DiagnosticIDs::Level Level
/* = DiagnosticIDs::Warning*/) {
189 unsigned ID
= DiagEngine
->getDiagnosticIDs()->getCustomDiagID(
190 Level
, (Description
+ " [" + CheckName
+ "]").str());
191 CheckNamesByDiagnosticID
.try_emplace(ID
, CheckName
);
192 return DiagEngine
->Report(ID
);
195 DiagnosticBuilder
ClangTidyContext::diag(const tooling::Diagnostic
&Error
) {
196 SourceManager
&SM
= DiagEngine
->getSourceManager();
197 llvm::ErrorOr
<const FileEntry
*> File
=
198 SM
.getFileManager().getFile(Error
.Message
.FilePath
);
199 FileID ID
= SM
.getOrCreateFileID(*File
, SrcMgr::C_User
);
200 SourceLocation FileStartLoc
= SM
.getLocForStartOfFile(ID
);
201 SourceLocation Loc
= FileStartLoc
.getLocWithOffset(
202 static_cast<SourceLocation::IntTy
>(Error
.Message
.FileOffset
));
203 return diag(Error
.DiagnosticName
, Loc
, Error
.Message
.Message
,
204 static_cast<DiagnosticIDs::Level
>(Error
.DiagLevel
));
207 DiagnosticBuilder
ClangTidyContext::configurationDiag(
209 DiagnosticIDs::Level Level
/* = DiagnosticIDs::Warning*/) {
210 return diag("clang-tidy-config", Message
, Level
);
213 bool ClangTidyContext::shouldSuppressDiagnostic(
214 DiagnosticsEngine::Level DiagLevel
, const Diagnostic
&Info
,
215 SmallVectorImpl
<tooling::Diagnostic
> &NoLintErrors
, bool AllowIO
,
216 bool EnableNoLintBlocks
) {
217 std::string CheckName
= getCheckName(Info
.getID());
218 return NoLintHandler
.shouldSuppress(DiagLevel
, Info
, CheckName
, NoLintErrors
,
219 AllowIO
, EnableNoLintBlocks
);
222 void ClangTidyContext::setSourceManager(SourceManager
*SourceMgr
) {
223 DiagEngine
->setSourceManager(SourceMgr
);
226 static bool parseFileExtensions(llvm::ArrayRef
<std::string
> AllFileExtensions
,
227 FileExtensionsSet
&FileExtensions
) {
228 FileExtensions
.clear();
229 for (StringRef Suffix
: AllFileExtensions
) {
230 StringRef Extension
= Suffix
.trim();
231 if (!llvm::all_of(Extension
, isAlphanumeric
))
233 FileExtensions
.insert(Extension
);
238 void ClangTidyContext::setCurrentFile(StringRef File
) {
239 CurrentFile
= std::string(File
);
240 CurrentOptions
= getOptionsForFile(CurrentFile
);
241 CheckFilter
= std::make_unique
<CachedGlobList
>(*getOptions().Checks
);
242 WarningAsErrorFilter
=
243 std::make_unique
<CachedGlobList
>(*getOptions().WarningsAsErrors
);
244 if (!parseFileExtensions(*getOptions().HeaderFileExtensions
,
245 HeaderFileExtensions
))
246 this->configurationDiag("Invalid header file extensions");
247 if (!parseFileExtensions(*getOptions().ImplementationFileExtensions
,
248 ImplementationFileExtensions
))
249 this->configurationDiag("Invalid implementation file extensions");
252 void ClangTidyContext::setASTContext(ASTContext
*Context
) {
253 DiagEngine
->SetArgToStringFn(&FormatASTNodeDiagnosticArgument
, Context
);
254 LangOpts
= Context
->getLangOpts();
257 const ClangTidyGlobalOptions
&ClangTidyContext::getGlobalOptions() const {
258 return OptionsProvider
->getGlobalOptions();
261 const ClangTidyOptions
&ClangTidyContext::getOptions() const {
262 return CurrentOptions
;
265 ClangTidyOptions
ClangTidyContext::getOptionsForFile(StringRef File
) const {
266 // Merge options on top of getDefaults() as a safeguard against options with
268 return ClangTidyOptions::getDefaults().merge(
269 OptionsProvider
->getOptions(File
), 0);
272 void ClangTidyContext::setEnableProfiling(bool P
) { Profile
= P
; }
274 void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix
) {
275 ProfilePrefix
= std::string(Prefix
);
278 std::optional
<ClangTidyProfiling::StorageParams
>
279 ClangTidyContext::getProfileStorageParams() const {
280 if (ProfilePrefix
.empty())
283 return ClangTidyProfiling::StorageParams(ProfilePrefix
, CurrentFile
);
286 bool ClangTidyContext::isCheckEnabled(StringRef CheckName
) const {
287 assert(CheckFilter
!= nullptr);
288 return CheckFilter
->contains(CheckName
);
291 bool ClangTidyContext::treatAsError(StringRef CheckName
) const {
292 assert(WarningAsErrorFilter
!= nullptr);
293 return WarningAsErrorFilter
->contains(CheckName
);
296 std::string
ClangTidyContext::getCheckName(unsigned DiagnosticID
) const {
297 std::string ClangWarningOption
= std::string(
298 DiagEngine
->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID
));
299 if (!ClangWarningOption
.empty())
300 return "clang-diagnostic-" + ClangWarningOption
;
301 llvm::DenseMap
<unsigned, std::string
>::const_iterator I
=
302 CheckNamesByDiagnosticID
.find(DiagnosticID
);
303 if (I
!= CheckNamesByDiagnosticID
.end())
308 ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(
309 ClangTidyContext
&Ctx
, DiagnosticsEngine
*ExternalDiagEngine
,
310 bool RemoveIncompatibleErrors
, bool GetFixesFromNotes
,
311 bool EnableNolintBlocks
)
312 : Context(Ctx
), ExternalDiagEngine(ExternalDiagEngine
),
313 RemoveIncompatibleErrors(RemoveIncompatibleErrors
),
314 GetFixesFromNotes(GetFixesFromNotes
),
315 EnableNolintBlocks(EnableNolintBlocks
), LastErrorRelatesToUserCode(false),
316 LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) {}
318 void ClangTidyDiagnosticConsumer::finalizeLastError() {
319 if (!Errors
.empty()) {
320 ClangTidyError
&Error
= Errors
.back();
321 if (Error
.DiagnosticName
== "clang-tidy-config") {
322 // Never ignore these.
323 } else if (!Context
.isCheckEnabled(Error
.DiagnosticName
) &&
324 Error
.DiagLevel
!= ClangTidyError::Error
) {
325 ++Context
.Stats
.ErrorsIgnoredCheckFilter
;
327 } else if (!LastErrorRelatesToUserCode
) {
328 ++Context
.Stats
.ErrorsIgnoredNonUserCode
;
330 } else if (!LastErrorPassesLineFilter
) {
331 ++Context
.Stats
.ErrorsIgnoredLineFilter
;
334 ++Context
.Stats
.ErrorsDisplayed
;
337 LastErrorRelatesToUserCode
= false;
338 LastErrorPassesLineFilter
= false;
341 namespace clang::tidy
{
343 const llvm::StringMap
<tooling::Replacements
> *
344 getFixIt(const tooling::Diagnostic
&Diagnostic
, bool GetFixFromNotes
) {
345 if (!Diagnostic
.Message
.Fix
.empty())
346 return &Diagnostic
.Message
.Fix
;
347 if (!GetFixFromNotes
)
349 const llvm::StringMap
<tooling::Replacements
> *Result
= nullptr;
350 for (const auto &Note
: Diagnostic
.Notes
) {
351 if (!Note
.Fix
.empty()) {
353 // We have 2 different fixes in notes, bail out.
361 } // namespace clang::tidy
363 void ClangTidyDiagnosticConsumer::HandleDiagnostic(
364 DiagnosticsEngine::Level DiagLevel
, const Diagnostic
&Info
) {
365 if (LastErrorWasIgnored
&& DiagLevel
== DiagnosticsEngine::Note
)
368 SmallVector
<tooling::Diagnostic
, 1> SuppressionErrors
;
369 if (Context
.shouldSuppressDiagnostic(DiagLevel
, Info
, SuppressionErrors
,
370 EnableNolintBlocks
)) {
371 ++Context
.Stats
.ErrorsIgnoredNOLINT
;
372 // Ignored a warning, should ignore related notes as well
373 LastErrorWasIgnored
= true;
374 Context
.DiagEngine
->Clear();
375 for (const auto &Error
: SuppressionErrors
)
380 LastErrorWasIgnored
= false;
381 // Count warnings/errors.
382 DiagnosticConsumer::HandleDiagnostic(DiagLevel
, Info
);
384 if (DiagLevel
== DiagnosticsEngine::Note
) {
385 assert(!Errors
.empty() &&
386 "A diagnostic note can only be appended to a message.");
389 std::string CheckName
= Context
.getCheckName(Info
.getID());
390 if (CheckName
.empty()) {
391 // This is a compiler diagnostic without a warning option. Assign check
392 // name based on its level.
394 case DiagnosticsEngine::Error
:
395 case DiagnosticsEngine::Fatal
:
396 CheckName
= "clang-diagnostic-error";
398 case DiagnosticsEngine::Warning
:
399 CheckName
= "clang-diagnostic-warning";
401 case DiagnosticsEngine::Remark
:
402 CheckName
= "clang-diagnostic-remark";
405 CheckName
= "clang-diagnostic-unknown";
410 ClangTidyError::Level Level
= ClangTidyError::Warning
;
411 if (DiagLevel
== DiagnosticsEngine::Error
||
412 DiagLevel
== DiagnosticsEngine::Fatal
) {
413 // Force reporting of Clang errors regardless of filters and non-user
415 Level
= ClangTidyError::Error
;
416 LastErrorRelatesToUserCode
= true;
417 LastErrorPassesLineFilter
= true;
418 } else if (DiagLevel
== DiagnosticsEngine::Remark
) {
419 Level
= ClangTidyError::Remark
;
422 bool IsWarningAsError
= DiagLevel
== DiagnosticsEngine::Warning
&&
423 Context
.treatAsError(CheckName
);
424 if (IsWarningAsError
)
425 Level
= ClangTidyError::Error
;
426 Errors
.emplace_back(CheckName
, Level
, Context
.getCurrentBuildDirectory(),
430 if (ExternalDiagEngine
) {
431 // If there is an external diagnostics engine, like in the
432 // ClangTidyPluginAction case, forward the diagnostics to it.
433 forwardDiagnostic(Info
);
435 ClangTidyDiagnosticRenderer
Converter(
436 Context
.getLangOpts(), &Context
.DiagEngine
->getDiagnosticOptions(),
438 SmallString
<100> Message
;
439 Info
.FormatDiagnostic(Message
);
441 if (Info
.getLocation().isValid() && Info
.hasSourceManager())
442 Loc
= FullSourceLoc(Info
.getLocation(), Info
.getSourceManager());
443 Converter
.emitDiagnostic(Loc
, DiagLevel
, Message
, Info
.getRanges(),
444 Info
.getFixItHints());
447 if (Info
.hasSourceManager())
448 checkFilters(Info
.getLocation(), Info
.getSourceManager());
450 Context
.DiagEngine
->Clear();
451 for (const auto &Error
: SuppressionErrors
)
455 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName
,
456 unsigned LineNumber
) const {
457 if (Context
.getGlobalOptions().LineFilter
.empty())
459 for (const FileFilter
&Filter
: Context
.getGlobalOptions().LineFilter
) {
460 if (FileName
.endswith(Filter
.Name
)) {
461 if (Filter
.LineRanges
.empty())
463 for (const FileFilter::LineRange
&Range
: Filter
.LineRanges
) {
464 if (Range
.first
<= LineNumber
&& LineNumber
<= Range
.second
)
473 void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic
&Info
) {
474 // Acquire a diagnostic ID also in the external diagnostics engine.
475 auto DiagLevelAndFormatString
=
476 Context
.getDiagLevelAndFormatString(Info
.getID(), Info
.getLocation());
477 unsigned ExternalID
= ExternalDiagEngine
->getDiagnosticIDs()->getCustomDiagID(
478 DiagLevelAndFormatString
.first
, DiagLevelAndFormatString
.second
);
480 // Forward the details.
481 auto Builder
= ExternalDiagEngine
->Report(Info
.getLocation(), ExternalID
);
482 for (auto Hint
: Info
.getFixItHints())
484 for (auto Range
: Info
.getRanges())
486 for (unsigned Index
= 0; Index
< Info
.getNumArgs(); ++Index
) {
487 DiagnosticsEngine::ArgumentKind Kind
= Info
.getArgKind(Index
);
489 case clang::DiagnosticsEngine::ak_std_string
:
490 Builder
<< Info
.getArgStdStr(Index
);
492 case clang::DiagnosticsEngine::ak_c_string
:
493 Builder
<< Info
.getArgCStr(Index
);
495 case clang::DiagnosticsEngine::ak_sint
:
496 Builder
<< Info
.getArgSInt(Index
);
498 case clang::DiagnosticsEngine::ak_uint
:
499 Builder
<< Info
.getArgUInt(Index
);
501 case clang::DiagnosticsEngine::ak_tokenkind
:
502 Builder
<< static_cast<tok::TokenKind
>(Info
.getRawArg(Index
));
504 case clang::DiagnosticsEngine::ak_identifierinfo
:
505 Builder
<< Info
.getArgIdentifier(Index
);
507 case clang::DiagnosticsEngine::ak_qual
:
508 Builder
<< Qualifiers::fromOpaqueValue(Info
.getRawArg(Index
));
510 case clang::DiagnosticsEngine::ak_qualtype
:
511 Builder
<< QualType::getFromOpaquePtr((void *)Info
.getRawArg(Index
));
513 case clang::DiagnosticsEngine::ak_declarationname
:
514 Builder
<< DeclarationName::getFromOpaqueInteger(Info
.getRawArg(Index
));
516 case clang::DiagnosticsEngine::ak_nameddecl
:
517 Builder
<< reinterpret_cast<const NamedDecl
*>(Info
.getRawArg(Index
));
519 case clang::DiagnosticsEngine::ak_nestednamespec
:
520 Builder
<< reinterpret_cast<NestedNameSpecifier
*>(Info
.getRawArg(Index
));
522 case clang::DiagnosticsEngine::ak_declcontext
:
523 Builder
<< reinterpret_cast<DeclContext
*>(Info
.getRawArg(Index
));
525 case clang::DiagnosticsEngine::ak_qualtype_pair
:
526 assert(false); // This one is not passed around.
528 case clang::DiagnosticsEngine::ak_attr
:
529 Builder
<< reinterpret_cast<Attr
*>(Info
.getRawArg(Index
));
531 case clang::DiagnosticsEngine::ak_addrspace
:
532 Builder
<< static_cast<LangAS
>(Info
.getRawArg(Index
));
538 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location
,
539 const SourceManager
&Sources
) {
540 // Invalid location may mean a diagnostic in a command line, don't skip these.
541 if (!Location
.isValid()) {
542 LastErrorRelatesToUserCode
= true;
543 LastErrorPassesLineFilter
= true;
547 if (!*Context
.getOptions().SystemHeaders
&&
548 (Sources
.isInSystemHeader(Location
) || Sources
.isInSystemMacro(Location
)))
551 // FIXME: We start with a conservative approach here, but the actual type of
552 // location needed depends on the check (in particular, where this check wants
554 FileID FID
= Sources
.getDecomposedExpansionLoc(Location
).first
;
555 const FileEntry
*File
= Sources
.getFileEntryForID(FID
);
557 // -DMACRO definitions on the command line have locations in a virtual buffer
558 // that doesn't have a FileEntry. Don't skip these as well.
560 LastErrorRelatesToUserCode
= true;
561 LastErrorPassesLineFilter
= true;
565 StringRef
FileName(File
->getName());
566 LastErrorRelatesToUserCode
= LastErrorRelatesToUserCode
||
567 Sources
.isInMainFile(Location
) ||
568 getHeaderFilter()->match(FileName
);
570 unsigned LineNumber
= Sources
.getExpansionLineNumber(Location
);
571 LastErrorPassesLineFilter
=
572 LastErrorPassesLineFilter
|| passesLineFilter(FileName
, LineNumber
);
575 llvm::Regex
*ClangTidyDiagnosticConsumer::getHeaderFilter() {
578 std::make_unique
<llvm::Regex
>(*Context
.getOptions().HeaderFilterRegex
);
579 return HeaderFilter
.get();
582 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
583 // Each error is modelled as the set of intervals in which it applies
584 // replacements. To detect overlapping replacements, we use a sweep line
585 // algorithm over these sets of intervals.
586 // An event here consists of the opening or closing of an interval. During the
587 // process, we maintain a counter with the amount of open intervals. If we
588 // find an endpoint of an interval and this counter is different from 0, it
589 // means that this interval overlaps with another one, so we set it as
592 // An event can be either the begin or the end of an interval.
599 Event(unsigned Begin
, unsigned End
, EventType Type
, unsigned ErrorId
,
601 : Type(Type
), ErrorId(ErrorId
) {
602 // The events are going to be sorted by their position. In case of draw:
604 // * If an interval ends at the same position at which other interval
605 // begins, this is not an overlapping, so we want to remove the ending
606 // interval before adding the starting one: end events have higher
607 // priority than begin events.
609 // * If we have several begin points at the same position, we will mark as
610 // inapplicable the ones that we process later, so the first one has to
611 // be the one with the latest end point, because this one will contain
612 // all the other intervals. For the same reason, if we have several end
613 // points in the same position, the last one has to be the one with the
614 // earliest begin point. In both cases, we sort non-increasingly by the
615 // position of the complementary.
617 // * In case of two equal intervals, the one whose error is bigger can
618 // potentially contain the other one, so we want to process its begin
619 // points before and its end points later.
621 // * Finally, if we have two equal intervals whose errors have the same
622 // size, none of them will be strictly contained inside the other.
623 // Sorting by ErrorId will guarantee that the begin point of the first
624 // one will be processed before, disallowing the second one, and the
625 // end point of the first one will also be processed before,
626 // disallowing the first one.
629 Priority
= std::make_tuple(Begin
, Type
, -End
, -ErrorSize
, ErrorId
);
632 Priority
= std::make_tuple(Begin
, Type
, -End
, ErrorSize
, ErrorId
);
635 Priority
= std::make_tuple(End
, Type
, -Begin
, ErrorSize
, ErrorId
);
640 bool operator<(const Event
&Other
) const {
641 return Priority
< Other
.Priority
;
644 // Determines if this event is the begin or the end of an interval.
646 // The index of the error to which the interval that generated this event
649 // The events will be sorted based on this field.
650 std::tuple
<unsigned, EventType
, int, int, unsigned> Priority
;
653 removeDuplicatedDiagnosticsOfAliasCheckers();
655 // Compute error sizes.
656 std::vector
<int> Sizes
;
658 std::pair
<ClangTidyError
*, llvm::StringMap
<tooling::Replacements
> *>>
660 for (auto &Error
: Errors
) {
661 if (const auto *Fix
= getFixIt(Error
, GetFixesFromNotes
))
662 ErrorFixes
.emplace_back(
663 &Error
, const_cast<llvm::StringMap
<tooling::Replacements
> *>(Fix
));
665 for (const auto &ErrorAndFix
: ErrorFixes
) {
667 for (const auto &FileAndReplaces
: *ErrorAndFix
.second
) {
668 for (const auto &Replace
: FileAndReplaces
.second
)
669 Size
+= Replace
.getLength();
671 Sizes
.push_back(Size
);
674 // Build events from error intervals.
675 llvm::StringMap
<std::vector
<Event
>> FileEvents
;
676 for (unsigned I
= 0; I
< ErrorFixes
.size(); ++I
) {
677 for (const auto &FileAndReplace
: *ErrorFixes
[I
].second
) {
678 for (const auto &Replace
: FileAndReplace
.second
) {
679 unsigned Begin
= Replace
.getOffset();
680 unsigned End
= Begin
+ Replace
.getLength();
681 auto &Events
= FileEvents
[Replace
.getFilePath()];
683 Events
.emplace_back(Begin
, End
, Event::ET_Insert
, I
, Sizes
[I
]);
685 Events
.emplace_back(Begin
, End
, Event::ET_Begin
, I
, Sizes
[I
]);
686 Events
.emplace_back(Begin
, End
, Event::ET_End
, I
, Sizes
[I
]);
692 llvm::BitVector
Apply(ErrorFixes
.size(), true);
693 for (auto &FileAndEvents
: FileEvents
) {
694 std::vector
<Event
> &Events
= FileAndEvents
.second
;
697 int OpenIntervals
= 0;
698 for (const auto &Event
: Events
) {
699 switch (Event
.Type
) {
700 case Event::ET_Begin
:
701 if (OpenIntervals
++ != 0)
702 Apply
[Event
.ErrorId
] = false;
704 case Event::ET_Insert
:
705 if (OpenIntervals
!= 0)
706 Apply
[Event
.ErrorId
] = false;
709 if (--OpenIntervals
!= 0)
710 Apply
[Event
.ErrorId
] = false;
714 assert(OpenIntervals
== 0 && "Amount of begin/end points doesn't match");
717 for (unsigned I
= 0; I
< ErrorFixes
.size(); ++I
) {
719 ErrorFixes
[I
].second
->clear();
720 ErrorFixes
[I
].first
->Notes
.emplace_back(
721 "this fix will not be applied because it overlaps with another fix");
727 struct LessClangTidyError
{
728 bool operator()(const ClangTidyError
&LHS
, const ClangTidyError
&RHS
) const {
729 const tooling::DiagnosticMessage
&M1
= LHS
.Message
;
730 const tooling::DiagnosticMessage
&M2
= RHS
.Message
;
732 return std::tie(M1
.FilePath
, M1
.FileOffset
, LHS
.DiagnosticName
,
734 std::tie(M2
.FilePath
, M2
.FileOffset
, RHS
.DiagnosticName
, M2
.Message
);
737 struct EqualClangTidyError
{
738 bool operator()(const ClangTidyError
&LHS
, const ClangTidyError
&RHS
) const {
739 LessClangTidyError Less
;
740 return !Less(LHS
, RHS
) && !Less(RHS
, LHS
);
743 } // end anonymous namespace
745 std::vector
<ClangTidyError
> ClangTidyDiagnosticConsumer::take() {
748 llvm::stable_sort(Errors
, LessClangTidyError());
749 Errors
.erase(std::unique(Errors
.begin(), Errors
.end(), EqualClangTidyError()),
751 if (RemoveIncompatibleErrors
)
752 removeIncompatibleErrors();
753 return std::move(Errors
);
757 struct LessClangTidyErrorWithoutDiagnosticName
{
758 bool operator()(const ClangTidyError
*LHS
, const ClangTidyError
*RHS
) const {
759 const tooling::DiagnosticMessage
&M1
= LHS
->Message
;
760 const tooling::DiagnosticMessage
&M2
= RHS
->Message
;
762 return std::tie(M1
.FilePath
, M1
.FileOffset
, M1
.Message
) <
763 std::tie(M2
.FilePath
, M2
.FileOffset
, M2
.Message
);
766 } // end anonymous namespace
768 void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {
769 using UniqueErrorSet
=
770 std::set
<ClangTidyError
*, LessClangTidyErrorWithoutDiagnosticName
>;
771 UniqueErrorSet UniqueErrors
;
773 auto IT
= Errors
.begin();
774 while (IT
!= Errors
.end()) {
775 ClangTidyError
&Error
= *IT
;
776 std::pair
<UniqueErrorSet::iterator
, bool> Inserted
=
777 UniqueErrors
.insert(&Error
);
779 // Unique error, we keep it and move along.
780 if (Inserted
.second
) {
783 ClangTidyError
&ExistingError
= **Inserted
.first
;
784 const llvm::StringMap
<tooling::Replacements
> &CandidateFix
=
786 const llvm::StringMap
<tooling::Replacements
> &ExistingFix
=
787 (*Inserted
.first
)->Message
.Fix
;
789 if (CandidateFix
!= ExistingFix
) {
791 // In case of a conflict, don't suggest any fix-it.
792 ExistingError
.Message
.Fix
.clear();
793 ExistingError
.Notes
.emplace_back(
794 llvm::formatv("cannot apply fix-it because an alias checker has "
795 "suggested a different fix-it; please remove one of "
796 "the checkers ('{0}', '{1}') or "
797 "ensure they are both configured the same",
798 ExistingError
.DiagnosticName
, Error
.DiagnosticName
)
802 if (Error
.IsWarningAsError
)
803 ExistingError
.IsWarningAsError
= true;
805 // Since it is the same error, we should take it as alias and remove it.
806 ExistingError
.EnabledDiagnosticAliases
.emplace_back(Error
.DiagnosticName
);
807 IT
= Errors
.erase(IT
);