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