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 Message
.consume_back(CheckNameInMessage
);
69 ? tooling::DiagnosticMessage(Message
, Loc
.getManager(), Loc
)
70 : tooling::DiagnosticMessage(Message
);
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())
79 assert(SourceRange
.isTokenRange());
80 SourceLocation End
= Lexer::getLocForEndOfToken(
81 SourceRange
.getEnd(), 0, Loc
.getManager(), LangOpts
);
82 return CharSourceRange::getCharRange(SourceRange
.getBegin(), End
);
85 // We are only interested in valid ranges.
87 llvm::make_filter_range(Ranges
, [](const CharSourceRange
&R
) {
88 return R
.getAsRange().isValid();
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
));
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
));
105 void emitDiagnosticLoc(FullSourceLoc Loc
, PresumedLoc PLoc
,
106 DiagnosticsEngine::Level Level
,
107 ArrayRef
<CharSourceRange
> Ranges
) override
{}
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
;
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.");
123 tooling::Replacement
Replacement(Loc
.getManager(), Range
,
126 DiagWithFix
->Fix
[Replacement
.getFilePath()].add(Replacement
);
127 // FIXME: better error handling (at least, don't let other replacements be
130 llvm::errs() << "Fix conflicts with existing fix! "
131 << llvm::toString(std::move(Err
)) << "\n";
132 assert(false && "Fix conflicts with existing fix!");
137 void emitIncludeLocation(FullSourceLoc Loc
, PresumedLoc PLoc
) override
{}
139 void emitImportLocation(FullSourceLoc Loc
, PresumedLoc PLoc
,
140 StringRef ModuleName
) override
{}
142 void emitBuildingModuleLocation(FullSourceLoc Loc
, PresumedLoc PLoc
,
143 StringRef ModuleName
) override
{}
145 void endDiagnostic(DiagOrStoredDiag D
,
146 DiagnosticsEngine::Level Level
) override
{
147 assert(!Error
.Message
.Message
.empty() && "Message has not been set");
151 ClangTidyError
&Error
;
153 } // end anonymous namespace
155 ClangTidyError::ClangTidyError(StringRef CheckName
,
156 ClangTidyError::Level DiagLevel
,
157 StringRef BuildDirectory
, bool IsWarningAsError
)
158 : tooling::Diagnostic(CheckName
, DiagLevel
, BuildDirectory
),
159 IsWarningAsError(IsWarningAsError
) {}
161 ClangTidyContext::ClangTidyContext(
162 std::unique_ptr
<ClangTidyOptionsProvider
> OptionsProvider
,
163 bool AllowEnablingAnalyzerAlphaCheckers
, bool EnableModuleHeadersParsing
)
164 : OptionsProvider(std::move(OptionsProvider
)),
166 AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers
),
167 EnableModuleHeadersParsing(EnableModuleHeadersParsing
) {
168 // Before the first translation unit we can get errors related to command-line
169 // parsing, use empty string for the file name in this case.
173 ClangTidyContext::~ClangTidyContext() = default;
175 DiagnosticBuilder
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
);
185 DiagnosticBuilder
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
);
194 DiagnosticBuilder
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
));
206 DiagnosticBuilder
ClangTidyContext::configurationDiag(
208 DiagnosticIDs::Level Level
/* = DiagnosticIDs::Warning*/) {
209 return diag("clang-tidy-config", Message
, Level
);
212 bool ClangTidyContext::shouldSuppressDiagnostic(
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
);
221 void ClangTidyContext::setSourceManager(SourceManager
*SourceMgr
) {
222 DiagEngine
->setSourceManager(SourceMgr
);
225 static 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
))
232 FileExtensions
.insert(Extension
);
237 void ClangTidyContext::setCurrentFile(StringRef File
) {
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");
251 void ClangTidyContext::setASTContext(ASTContext
*Context
) {
252 DiagEngine
->SetArgToStringFn(&FormatASTNodeDiagnosticArgument
, Context
);
253 LangOpts
= Context
->getLangOpts();
256 const ClangTidyGlobalOptions
&ClangTidyContext::getGlobalOptions() const {
257 return OptionsProvider
->getGlobalOptions();
260 const ClangTidyOptions
&ClangTidyContext::getOptions() const {
261 return CurrentOptions
;
264 ClangTidyOptions
ClangTidyContext::getOptionsForFile(StringRef File
) const {
265 // Merge options on top of getDefaults() as a safeguard against options with
267 return ClangTidyOptions::getDefaults().merge(
268 OptionsProvider
->getOptions(File
), 0);
271 void ClangTidyContext::setEnableProfiling(bool P
) { Profile
= P
; }
273 void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix
) {
274 ProfilePrefix
= std::string(Prefix
);
277 std::optional
<ClangTidyProfiling::StorageParams
>
278 ClangTidyContext::getProfileStorageParams() const {
279 if (ProfilePrefix
.empty())
282 return ClangTidyProfiling::StorageParams(ProfilePrefix
, CurrentFile
);
285 bool ClangTidyContext::isCheckEnabled(StringRef CheckName
) const {
286 assert(CheckFilter
!= nullptr);
287 return CheckFilter
->contains(CheckName
);
290 bool ClangTidyContext::treatAsError(StringRef CheckName
) const {
291 assert(WarningAsErrorFilter
!= nullptr);
292 return WarningAsErrorFilter
->contains(CheckName
);
295 std::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())
307 ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(
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
) {
316 if (Context
.getOptions().HeaderFilterRegex
&&
317 !Context
.getOptions().HeaderFilterRegex
->empty())
319 std::make_unique
<llvm::Regex
>(*Context
.getOptions().HeaderFilterRegex
);
321 if (Context
.getOptions().ExcludeHeaderFilterRegex
&&
322 !Context
.getOptions().ExcludeHeaderFilterRegex
->empty())
323 ExcludeHeaderFilter
= std::make_unique
<llvm::Regex
>(
324 *Context
.getOptions().ExcludeHeaderFilterRegex
);
327 void ClangTidyDiagnosticConsumer::finalizeLastError() {
328 if (!Errors
.empty()) {
329 ClangTidyError
&Error
= Errors
.back();
330 if (Error
.DiagnosticName
== "clang-tidy-config") {
331 // Never ignore these.
332 } else if (!Context
.isCheckEnabled(Error
.DiagnosticName
) &&
333 Error
.DiagLevel
!= ClangTidyError::Error
) {
334 ++Context
.Stats
.ErrorsIgnoredCheckFilter
;
336 } else if (!LastErrorRelatesToUserCode
) {
337 ++Context
.Stats
.ErrorsIgnoredNonUserCode
;
339 } else if (!LastErrorPassesLineFilter
) {
340 ++Context
.Stats
.ErrorsIgnoredLineFilter
;
343 ++Context
.Stats
.ErrorsDisplayed
;
346 LastErrorRelatesToUserCode
= false;
347 LastErrorPassesLineFilter
= false;
350 namespace clang::tidy
{
352 const llvm::StringMap
<tooling::Replacements
> *
353 getFixIt(const tooling::Diagnostic
&Diagnostic
, bool AnyFix
) {
354 if (!Diagnostic
.Message
.Fix
.empty())
355 return &Diagnostic
.Message
.Fix
;
358 const llvm::StringMap
<tooling::Replacements
> *Result
= nullptr;
359 for (const auto &Note
: Diagnostic
.Notes
) {
360 if (!Note
.Fix
.empty()) {
362 // We have 2 different fixes in notes, bail out.
370 } // namespace clang::tidy
372 void ClangTidyDiagnosticConsumer::HandleDiagnostic(
373 DiagnosticsEngine::Level DiagLevel
, const Diagnostic
&Info
) {
374 if (LastErrorWasIgnored
&& DiagLevel
== DiagnosticsEngine::Note
)
377 SmallVector
<tooling::Diagnostic
, 1> SuppressionErrors
;
378 if (Context
.shouldSuppressDiagnostic(DiagLevel
, Info
, SuppressionErrors
,
379 EnableNolintBlocks
)) {
380 ++Context
.Stats
.ErrorsIgnoredNOLINT
;
381 // Ignored a warning, should ignore related notes as well
382 LastErrorWasIgnored
= true;
383 for (const auto &Error
: SuppressionErrors
)
388 LastErrorWasIgnored
= false;
389 // Count warnings/errors.
390 DiagnosticConsumer::HandleDiagnostic(DiagLevel
, Info
);
392 if (DiagLevel
== DiagnosticsEngine::Note
) {
393 assert(!Errors
.empty() &&
394 "A diagnostic note can only be appended to a message.");
397 std::string CheckName
= Context
.getCheckName(Info
.getID());
398 if (CheckName
.empty()) {
399 // This is a compiler diagnostic without a warning option. Assign check
400 // name based on its level.
402 case DiagnosticsEngine::Error
:
403 case DiagnosticsEngine::Fatal
:
404 CheckName
= "clang-diagnostic-error";
406 case DiagnosticsEngine::Warning
:
407 CheckName
= "clang-diagnostic-warning";
409 case DiagnosticsEngine::Remark
:
410 CheckName
= "clang-diagnostic-remark";
413 CheckName
= "clang-diagnostic-unknown";
418 ClangTidyError::Level Level
= ClangTidyError::Warning
;
419 if (DiagLevel
== DiagnosticsEngine::Error
||
420 DiagLevel
== DiagnosticsEngine::Fatal
) {
421 // Force reporting of Clang errors regardless of filters and non-user
423 Level
= ClangTidyError::Error
;
424 LastErrorRelatesToUserCode
= true;
425 LastErrorPassesLineFilter
= true;
426 } else if (DiagLevel
== DiagnosticsEngine::Remark
) {
427 Level
= ClangTidyError::Remark
;
430 bool IsWarningAsError
= DiagLevel
== DiagnosticsEngine::Warning
&&
431 Context
.treatAsError(CheckName
);
432 Errors
.emplace_back(CheckName
, Level
, Context
.getCurrentBuildDirectory(),
436 if (ExternalDiagEngine
) {
437 // If there is an external diagnostics engine, like in the
438 // ClangTidyPluginAction case, forward the diagnostics to it.
439 forwardDiagnostic(Info
);
441 ClangTidyDiagnosticRenderer
Converter(
442 Context
.getLangOpts(), &Context
.DiagEngine
->getDiagnosticOptions(),
444 SmallString
<100> Message
;
445 Info
.FormatDiagnostic(Message
);
447 if (Info
.hasSourceManager())
448 Loc
= FullSourceLoc(Info
.getLocation(), Info
.getSourceManager());
449 else if (Context
.DiagEngine
->hasSourceManager())
450 Loc
= FullSourceLoc(Info
.getLocation(),
451 Context
.DiagEngine
->getSourceManager());
452 Converter
.emitDiagnostic(Loc
, DiagLevel
, Message
, Info
.getRanges(),
453 Info
.getFixItHints());
456 if (Info
.hasSourceManager())
457 checkFilters(Info
.getLocation(), Info
.getSourceManager());
459 for (const auto &Error
: SuppressionErrors
)
463 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName
,
464 unsigned LineNumber
) const {
465 if (Context
.getGlobalOptions().LineFilter
.empty())
467 for (const FileFilter
&Filter
: Context
.getGlobalOptions().LineFilter
) {
468 if (FileName
.ends_with(Filter
.Name
)) {
469 if (Filter
.LineRanges
.empty())
471 for (const FileFilter::LineRange
&Range
: Filter
.LineRanges
) {
472 if (Range
.first
<= LineNumber
&& LineNumber
<= Range
.second
)
481 void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic
&Info
) {
482 // Acquire a diagnostic ID also in the external diagnostics engine.
483 auto DiagLevelAndFormatString
=
484 Context
.getDiagLevelAndFormatString(Info
.getID(), Info
.getLocation());
485 unsigned ExternalID
= ExternalDiagEngine
->getDiagnosticIDs()->getCustomDiagID(
486 DiagLevelAndFormatString
.first
, DiagLevelAndFormatString
.second
);
488 // Forward the details.
489 auto Builder
= ExternalDiagEngine
->Report(Info
.getLocation(), ExternalID
);
490 for (const FixItHint
&Hint
: Info
.getFixItHints())
492 for (auto Range
: Info
.getRanges())
494 for (unsigned Index
= 0; Index
< Info
.getNumArgs(); ++Index
) {
495 DiagnosticsEngine::ArgumentKind Kind
= Info
.getArgKind(Index
);
497 case clang::DiagnosticsEngine::ak_std_string
:
498 Builder
<< Info
.getArgStdStr(Index
);
500 case clang::DiagnosticsEngine::ak_c_string
:
501 Builder
<< Info
.getArgCStr(Index
);
503 case clang::DiagnosticsEngine::ak_sint
:
504 Builder
<< Info
.getArgSInt(Index
);
506 case clang::DiagnosticsEngine::ak_uint
:
507 Builder
<< Info
.getArgUInt(Index
);
509 case clang::DiagnosticsEngine::ak_tokenkind
:
510 Builder
<< static_cast<tok::TokenKind
>(Info
.getRawArg(Index
));
512 case clang::DiagnosticsEngine::ak_identifierinfo
:
513 Builder
<< Info
.getArgIdentifier(Index
);
515 case clang::DiagnosticsEngine::ak_qual
:
516 Builder
<< Qualifiers::fromOpaqueValue(Info
.getRawArg(Index
));
518 case clang::DiagnosticsEngine::ak_qualtype
:
519 Builder
<< QualType::getFromOpaquePtr((void *)Info
.getRawArg(Index
));
521 case clang::DiagnosticsEngine::ak_declarationname
:
522 Builder
<< DeclarationName::getFromOpaqueInteger(Info
.getRawArg(Index
));
524 case clang::DiagnosticsEngine::ak_nameddecl
:
525 Builder
<< reinterpret_cast<const NamedDecl
*>(Info
.getRawArg(Index
));
527 case clang::DiagnosticsEngine::ak_nestednamespec
:
528 Builder
<< reinterpret_cast<NestedNameSpecifier
*>(Info
.getRawArg(Index
));
530 case clang::DiagnosticsEngine::ak_declcontext
:
531 Builder
<< reinterpret_cast<DeclContext
*>(Info
.getRawArg(Index
));
533 case clang::DiagnosticsEngine::ak_qualtype_pair
:
534 assert(false); // This one is not passed around.
536 case clang::DiagnosticsEngine::ak_attr
:
537 Builder
<< reinterpret_cast<Attr
*>(Info
.getRawArg(Index
));
539 case clang::DiagnosticsEngine::ak_addrspace
:
540 Builder
<< static_cast<LangAS
>(Info
.getRawArg(Index
));
546 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location
,
547 const SourceManager
&Sources
) {
548 // Invalid location may mean a diagnostic in a command line, don't skip these.
549 if (!Location
.isValid()) {
550 LastErrorRelatesToUserCode
= true;
551 LastErrorPassesLineFilter
= true;
555 if (!*Context
.getOptions().SystemHeaders
&&
556 (Sources
.isInSystemHeader(Location
) || Sources
.isInSystemMacro(Location
)))
559 // FIXME: We start with a conservative approach here, but the actual type of
560 // location needed depends on the check (in particular, where this check wants
562 FileID FID
= Sources
.getDecomposedExpansionLoc(Location
).first
;
563 OptionalFileEntryRef File
= Sources
.getFileEntryRefForID(FID
);
565 // -DMACRO definitions on the command line have locations in a virtual buffer
566 // that doesn't have a FileEntry. Don't skip these as well.
568 LastErrorRelatesToUserCode
= true;
569 LastErrorPassesLineFilter
= true;
573 StringRef
FileName(File
->getName());
574 LastErrorRelatesToUserCode
=
575 LastErrorRelatesToUserCode
|| Sources
.isInMainFile(Location
) ||
577 (HeaderFilter
->match(FileName
) &&
578 !(ExcludeHeaderFilter
&& ExcludeHeaderFilter
->match(FileName
))));
580 unsigned LineNumber
= Sources
.getExpansionLineNumber(Location
);
581 LastErrorPassesLineFilter
=
582 LastErrorPassesLineFilter
|| passesLineFilter(FileName
, LineNumber
);
585 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
586 // Each error is modelled as the set of intervals in which it applies
587 // replacements. To detect overlapping replacements, we use a sweep line
588 // algorithm over these sets of intervals.
589 // An event here consists of the opening or closing of an interval. During the
590 // process, we maintain a counter with the amount of open intervals. If we
591 // find an endpoint of an interval and this counter is different from 0, it
592 // means that this interval overlaps with another one, so we set it as
595 // An event can be either the begin or the end of an interval.
602 Event(unsigned Begin
, unsigned End
, EventType Type
, unsigned ErrorId
,
604 : Type(Type
), ErrorId(ErrorId
) {
605 // The events are going to be sorted by their position. In case of draw:
607 // * If an interval ends at the same position at which other interval
608 // begins, this is not an overlapping, so we want to remove the ending
609 // interval before adding the starting one: end events have higher
610 // priority than begin events.
612 // * If we have several begin points at the same position, we will mark as
613 // inapplicable the ones that we process later, so the first one has to
614 // be the one with the latest end point, because this one will contain
615 // all the other intervals. For the same reason, if we have several end
616 // points in the same position, the last one has to be the one with the
617 // earliest begin point. In both cases, we sort non-increasingly by the
618 // position of the complementary.
620 // * In case of two equal intervals, the one whose error is bigger can
621 // potentially contain the other one, so we want to process its begin
622 // points before and its end points later.
624 // * Finally, if we have two equal intervals whose errors have the same
625 // size, none of them will be strictly contained inside the other.
626 // Sorting by ErrorId will guarantee that the begin point of the first
627 // one will be processed before, disallowing the second one, and the
628 // end point of the first one will also be processed before,
629 // disallowing the first one.
632 Priority
= std::make_tuple(Begin
, Type
, -End
, -ErrorSize
, ErrorId
);
635 Priority
= std::make_tuple(Begin
, Type
, -End
, ErrorSize
, ErrorId
);
638 Priority
= std::make_tuple(End
, Type
, -Begin
, ErrorSize
, ErrorId
);
643 bool operator<(const Event
&Other
) const {
644 return Priority
< Other
.Priority
;
647 // Determines if this event is the begin or the end of an interval.
649 // The index of the error to which the interval that generated this event
652 // The events will be sorted based on this field.
653 std::tuple
<unsigned, EventType
, int, int, unsigned> Priority
;
656 removeDuplicatedDiagnosticsOfAliasCheckers();
658 // Compute error sizes.
659 std::vector
<int> Sizes
;
661 std::pair
<ClangTidyError
*, llvm::StringMap
<tooling::Replacements
> *>>
663 for (auto &Error
: Errors
) {
664 if (const auto *Fix
= getFixIt(Error
, GetFixesFromNotes
))
665 ErrorFixes
.emplace_back(
666 &Error
, const_cast<llvm::StringMap
<tooling::Replacements
> *>(Fix
));
668 for (const auto &ErrorAndFix
: ErrorFixes
) {
670 for (const auto &FileAndReplaces
: *ErrorAndFix
.second
) {
671 for (const auto &Replace
: FileAndReplaces
.second
)
672 Size
+= Replace
.getLength();
674 Sizes
.push_back(Size
);
677 // Build events from error intervals.
678 llvm::StringMap
<std::vector
<Event
>> FileEvents
;
679 for (unsigned I
= 0; I
< ErrorFixes
.size(); ++I
) {
680 for (const auto &FileAndReplace
: *ErrorFixes
[I
].second
) {
681 for (const auto &Replace
: FileAndReplace
.second
) {
682 unsigned Begin
= Replace
.getOffset();
683 unsigned End
= Begin
+ Replace
.getLength();
684 auto &Events
= FileEvents
[Replace
.getFilePath()];
686 Events
.emplace_back(Begin
, End
, Event::ET_Insert
, I
, Sizes
[I
]);
688 Events
.emplace_back(Begin
, End
, Event::ET_Begin
, I
, Sizes
[I
]);
689 Events
.emplace_back(Begin
, End
, Event::ET_End
, I
, Sizes
[I
]);
695 llvm::BitVector
Apply(ErrorFixes
.size(), true);
696 for (auto &FileAndEvents
: FileEvents
) {
697 std::vector
<Event
> &Events
= FileAndEvents
.second
;
700 int OpenIntervals
= 0;
701 for (const auto &Event
: Events
) {
702 switch (Event
.Type
) {
703 case Event::ET_Begin
:
704 if (OpenIntervals
++ != 0)
705 Apply
[Event
.ErrorId
] = false;
707 case Event::ET_Insert
:
708 if (OpenIntervals
!= 0)
709 Apply
[Event
.ErrorId
] = false;
712 if (--OpenIntervals
!= 0)
713 Apply
[Event
.ErrorId
] = false;
717 assert(OpenIntervals
== 0 && "Amount of begin/end points doesn't match");
720 for (unsigned I
= 0; I
< ErrorFixes
.size(); ++I
) {
722 ErrorFixes
[I
].second
->clear();
723 ErrorFixes
[I
].first
->Notes
.emplace_back(
724 "this fix will not be applied because it overlaps with another fix");
730 struct LessClangTidyError
{
731 bool operator()(const ClangTidyError
&LHS
, const ClangTidyError
&RHS
) const {
732 const tooling::DiagnosticMessage
&M1
= LHS
.Message
;
733 const tooling::DiagnosticMessage
&M2
= RHS
.Message
;
735 return std::tie(M1
.FilePath
, M1
.FileOffset
, LHS
.DiagnosticName
,
737 std::tie(M2
.FilePath
, M2
.FileOffset
, RHS
.DiagnosticName
, M2
.Message
);
740 struct EqualClangTidyError
{
741 bool operator()(const ClangTidyError
&LHS
, const ClangTidyError
&RHS
) const {
742 LessClangTidyError Less
;
743 return !Less(LHS
, RHS
) && !Less(RHS
, LHS
);
746 } // end anonymous namespace
748 std::vector
<ClangTidyError
> ClangTidyDiagnosticConsumer::take() {
751 llvm::stable_sort(Errors
, LessClangTidyError());
752 Errors
.erase(std::unique(Errors
.begin(), Errors
.end(), EqualClangTidyError()),
754 if (RemoveIncompatibleErrors
)
755 removeIncompatibleErrors();
756 return std::move(Errors
);
760 struct LessClangTidyErrorWithoutDiagnosticName
{
761 bool operator()(const ClangTidyError
*LHS
, const ClangTidyError
*RHS
) const {
762 const tooling::DiagnosticMessage
&M1
= LHS
->Message
;
763 const tooling::DiagnosticMessage
&M2
= RHS
->Message
;
765 return std::tie(M1
.FilePath
, M1
.FileOffset
, M1
.Message
) <
766 std::tie(M2
.FilePath
, M2
.FileOffset
, M2
.Message
);
769 } // end anonymous namespace
771 void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {
772 using UniqueErrorSet
=
773 std::set
<ClangTidyError
*, LessClangTidyErrorWithoutDiagnosticName
>;
774 UniqueErrorSet UniqueErrors
;
776 auto IT
= Errors
.begin();
777 while (IT
!= Errors
.end()) {
778 ClangTidyError
&Error
= *IT
;
779 std::pair
<UniqueErrorSet::iterator
, bool> Inserted
=
780 UniqueErrors
.insert(&Error
);
782 // Unique error, we keep it and move along.
783 if (Inserted
.second
) {
786 ClangTidyError
&ExistingError
= **Inserted
.first
;
787 const llvm::StringMap
<tooling::Replacements
> &CandidateFix
=
789 const llvm::StringMap
<tooling::Replacements
> &ExistingFix
=
790 (*Inserted
.first
)->Message
.Fix
;
792 if (CandidateFix
!= ExistingFix
) {
794 // In case of a conflict, don't suggest any fix-it.
795 ExistingError
.Message
.Fix
.clear();
796 ExistingError
.Notes
.emplace_back(
797 llvm::formatv("cannot apply fix-it because an alias checker has "
798 "suggested a different fix-it; please remove one of "
799 "the checkers ('{0}', '{1}') or "
800 "ensure they are both configured the same",
801 ExistingError
.DiagnosticName
, Error
.DiagnosticName
)
805 if (Error
.IsWarningAsError
)
806 ExistingError
.IsWarningAsError
= true;
808 // Since it is the same error, we should take it as alias and remove it.
809 ExistingError
.EnabledDiagnosticAliases
.emplace_back(Error
.DiagnosticName
);
810 IT
= Errors
.erase(IT
);