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