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 return llvm::any_of(
482 Filter.LineRanges, [&](const FileFilter::LineRange &Range) {
483 return Range.first <= LineNumber && LineNumber <= Range.second;
484 });
485 }
486 }
487 return false;
488}
489
490void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) {
491 // Acquire a diagnostic ID also in the external diagnostics engine.
492 auto DiagLevelAndFormatString =
493 Context.getDiagLevelAndFormatString(Info.getID(), Info.getLocation());
494 const unsigned ExternalID =
495 ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID(
496 DiagLevelAndFormatString.first, DiagLevelAndFormatString.second);
497
498 // Forward the details.
499 auto Builder = ExternalDiagEngine->Report(Info.getLocation(), ExternalID);
500 for (const FixItHint &Hint : Info.getFixItHints())
501 Builder << Hint;
502 for (auto Range : Info.getRanges())
503 Builder << Range;
504 for (unsigned Index = 0; Index < Info.getNumArgs(); ++Index) {
505 const DiagnosticsEngine::ArgumentKind Kind = Info.getArgKind(Index);
506 switch (Kind) {
507 case clang::DiagnosticsEngine::ak_std_string:
508 Builder << Info.getArgStdStr(Index);
509 break;
510 case clang::DiagnosticsEngine::ak_c_string:
511 Builder << Info.getArgCStr(Index);
512 break;
513 case clang::DiagnosticsEngine::ak_sint:
514 Builder << Info.getArgSInt(Index);
515 break;
516 case clang::DiagnosticsEngine::ak_uint:
517 Builder << Info.getArgUInt(Index);
518 break;
519 case clang::DiagnosticsEngine::ak_tokenkind:
520 Builder << static_cast<tok::TokenKind>(Info.getRawArg(Index));
521 break;
522 case clang::DiagnosticsEngine::ak_identifierinfo:
523 Builder << Info.getArgIdentifier(Index);
524 break;
525 case clang::DiagnosticsEngine::ak_qual:
526 Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(Index));
527 break;
528 case clang::DiagnosticsEngine::ak_qualtype:
529 Builder << QualType::getFromOpaquePtr((void *)Info.getRawArg(Index));
530 break;
531 case clang::DiagnosticsEngine::ak_declarationname:
532 Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(Index));
533 break;
534 case clang::DiagnosticsEngine::ak_nameddecl:
535 Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Index));
536 break;
537 case clang::DiagnosticsEngine::ak_nestednamespec:
538 Builder << NestedNameSpecifier::getFromVoidPointer(
539 reinterpret_cast<void *>(Info.getRawArg(Index)));
540 break;
541 case clang::DiagnosticsEngine::ak_declcontext:
542 Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(Index));
543 break;
544 case clang::DiagnosticsEngine::ak_qualtype_pair:
545 assert(false); // This one is not passed around.
546 break;
547 case clang::DiagnosticsEngine::ak_attr:
548 Builder << reinterpret_cast<Attr *>(Info.getRawArg(Index));
549 break;
550 case clang::DiagnosticsEngine::ak_attr_info:
551 Builder << reinterpret_cast<AttributeCommonInfo *>(Info.getRawArg(Index));
552 break;
553 case clang::DiagnosticsEngine::ak_addrspace:
554 Builder << static_cast<LangAS>(Info.getRawArg(Index));
555 break;
556 case clang::DiagnosticsEngine::ak_expr:
557 Builder << reinterpret_cast<const Expr *>(Info.getRawArg(Index));
558 }
559 }
560}
561
562void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
563 const SourceManager &Sources) {
564 // Invalid location may mean a diagnostic in a command line, don't skip these.
565 if (!Location.isValid()) {
566 LastErrorRelatesToUserCode = true;
567 LastErrorPassesLineFilter = true;
568 return;
569 }
570
571 if (!*Context.getOptions().SystemHeaders &&
572 (Sources.isInSystemHeader(Location) || Sources.isInSystemMacro(Location)))
573 return;
574
575 // FIXME: We start with a conservative approach here, but the actual type of
576 // location needed depends on the check (in particular, where this check wants
577 // to apply fixes).
578 const FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
579 OptionalFileEntryRef File = Sources.getFileEntryRefForID(FID);
580
581 // -DMACRO definitions on the command line have locations in a virtual buffer
582 // that doesn't have a FileEntry. Don't skip these as well.
583 if (!File) {
584 LastErrorRelatesToUserCode = true;
585 LastErrorPassesLineFilter = true;
586 return;
587 }
588
589 const StringRef FileName(File->getName());
590 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
591 Sources.isInMainFile(Location) ||
592 (getHeaderFilter()->match(FileName) &&
593 !getExcludeHeaderFilter()->match(FileName));
594
595 const unsigned LineNumber = Sources.getExpansionLineNumber(Location);
596 LastErrorPassesLineFilter =
597 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
598}
599
600llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
601 if (!HeaderFilter)
602 HeaderFilter =
603 std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
604 return HeaderFilter.get();
605}
606
607llvm::Regex *ClangTidyDiagnosticConsumer::getExcludeHeaderFilter() {
608 if (!ExcludeHeaderFilter)
609 ExcludeHeaderFilter = std::make_unique<llvm::Regex>(
610 *Context.getOptions().ExcludeHeaderFilterRegex);
611 return ExcludeHeaderFilter.get();
612}
613
614void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
615 // Each error is modelled as the set of intervals in which it applies
616 // replacements. To detect overlapping replacements, we use a sweep line
617 // algorithm over these sets of intervals.
618 // An event here consists of the opening or closing of an interval. During the
619 // process, we maintain a counter with the amount of open intervals. If we
620 // find an endpoint of an interval and this counter is different from 0, it
621 // means that this interval overlaps with another one, so we set it as
622 // inapplicable.
623 struct Event {
624 // An event can be either the begin or the end of an interval.
625 enum EventType {
626 ET_Begin = 1,
627 ET_Insert = 0,
628 ET_End = -1,
629 };
630
631 Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,
632 unsigned ErrorSize)
633 : Type(Type), ErrorId(ErrorId) {
634 // The events are going to be sorted by their position. In case of draw:
635 //
636 // * If an interval ends at the same position at which other interval
637 // begins, this is not an overlapping, so we want to remove the ending
638 // interval before adding the starting one: end events have higher
639 // priority than begin events.
640 //
641 // * If we have several begin points at the same position, we will mark as
642 // inapplicable the ones that we process later, so the first one has to
643 // be the one with the latest end point, because this one will contain
644 // all the other intervals. For the same reason, if we have several end
645 // points in the same position, the last one has to be the one with the
646 // earliest begin point. In both cases, we sort non-increasingly by the
647 // position of the complementary.
648 //
649 // * In case of two equal intervals, the one whose error is bigger can
650 // potentially contain the other one, so we want to process its begin
651 // points before and its end points later.
652 //
653 // * Finally, if we have two equal intervals whose errors have the same
654 // size, none of them will be strictly contained inside the other.
655 // Sorting by ErrorId will guarantee that the begin point of the first
656 // one will be processed before, disallowing the second one, and the
657 // end point of the first one will also be processed before,
658 // disallowing the first one.
659 switch (Type) {
660 case ET_Begin:
661 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
662 break;
663 case ET_Insert:
664 Priority = std::make_tuple(Begin, Type, -End, ErrorSize, ErrorId);
665 break;
666 case ET_End:
667 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
668 break;
669 }
670 }
671
672 bool operator<(const Event &Other) const {
673 return Priority < Other.Priority;
674 }
675
676 // Determines if this event is the begin or the end of an interval.
677 EventType Type;
678 // The index of the error to which the interval that generated this event
679 // belongs.
680 unsigned ErrorId;
681 // The events will be sorted based on this field.
682 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
683 };
684
685 removeDuplicatedDiagnosticsOfAliasCheckers();
686
687 // Compute error sizes.
688 std::vector<int> Sizes;
689 std::vector<
690 std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>>
691 ErrorFixes;
692 for (auto &Error : Errors) {
693 if (const auto *Fix = getFixIt(Error, GetFixesFromNotes))
694 ErrorFixes.emplace_back(
695 &Error, const_cast<llvm::StringMap<tooling::Replacements> *>(Fix));
696 }
697 for (const auto &ErrorAndFix : ErrorFixes) {
698 int Size = 0;
699 for (const auto &FileAndReplaces : *ErrorAndFix.second) {
700 for (const auto &Replace : FileAndReplaces.second)
701 Size += Replace.getLength();
702 }
703 Sizes.push_back(Size);
704 }
705
706 // Build events from error intervals.
707 llvm::StringMap<std::vector<Event>> FileEvents;
708 for (unsigned I = 0; I < ErrorFixes.size(); ++I) {
709 for (const auto &FileAndReplace : *ErrorFixes[I].second) {
710 for (const auto &Replace : FileAndReplace.second) {
711 const unsigned Begin = Replace.getOffset();
712 const unsigned End = Begin + Replace.getLength();
713 auto &Events = FileEvents[Replace.getFilePath()];
714 if (Begin == End) {
715 Events.emplace_back(Begin, End, Event::ET_Insert, I, Sizes[I]);
716 } else {
717 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
718 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
719 }
720 }
721 }
722 }
723
724 llvm::BitVector Apply(ErrorFixes.size(), true);
725 for (auto &FileAndEvents : FileEvents) {
726 std::vector<Event> &Events = FileAndEvents.second;
727 // Sweep.
728 llvm::sort(Events);
729 int OpenIntervals = 0;
730 for (const auto &Event : Events) {
731 switch (Event.Type) {
732 case Event::ET_Begin:
733 if (OpenIntervals++ != 0)
734 Apply[Event.ErrorId] = false;
735 break;
736 case Event::ET_Insert:
737 if (OpenIntervals != 0)
738 Apply[Event.ErrorId] = false;
739 break;
740 case Event::ET_End:
741 if (--OpenIntervals != 0)
742 Apply[Event.ErrorId] = false;
743 break;
744 }
745 }
746 assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");
747 }
748
749 for (unsigned I = 0; I < ErrorFixes.size(); ++I) {
750 if (!Apply[I]) {
751 ErrorFixes[I].second->clear();
752 ErrorFixes[I].first->Notes.emplace_back(
753 "this fix will not be applied because it overlaps with another fix");
754 }
755 }
756}
757
758namespace {
759struct LessClangTidyError {
760 bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
761 const tooling::DiagnosticMessage &M1 = LHS.Message;
762 const tooling::DiagnosticMessage &M2 = RHS.Message;
763
764 return std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName,
765 M1.Message) <
766 std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message);
767 }
768};
769struct EqualClangTidyError {
770 bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
771 const LessClangTidyError Less;
772 return !Less(LHS, RHS) && !Less(RHS, LHS);
773 }
774};
775} // end anonymous namespace
776
777std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() {
778 finalizeLastError();
779
780 llvm::stable_sort(Errors, LessClangTidyError());
781 Errors.erase(llvm::unique(Errors, EqualClangTidyError()), Errors.end());
782 if (RemoveIncompatibleErrors)
783 removeIncompatibleErrors();
784 return std::move(Errors);
785}
786
787namespace {
788struct LessClangTidyErrorWithoutDiagnosticName {
789 bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const {
790 const tooling::DiagnosticMessage &M1 = LHS->Message;
791 const tooling::DiagnosticMessage &M2 = RHS->Message;
792
793 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
794 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
795 }
796};
797} // end anonymous namespace
798
799void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {
800 using UniqueErrorSet =
801 std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>;
802 UniqueErrorSet UniqueErrors;
803
804 auto IT = Errors.begin();
805 while (IT != Errors.end()) {
806 ClangTidyError &Error = *IT;
807 const std::pair<UniqueErrorSet::iterator, bool> Inserted =
808 UniqueErrors.insert(&Error);
809
810 // Unique error, we keep it and move along.
811 if (Inserted.second) {
812 ++IT;
813 } else {
814 ClangTidyError &ExistingError = **Inserted.first;
815 const llvm::StringMap<tooling::Replacements> &CandidateFix =
816 Error.Message.Fix;
817 const llvm::StringMap<tooling::Replacements> &ExistingFix =
818 (*Inserted.first)->Message.Fix;
819
820 if (CandidateFix != ExistingFix) {
821 // In case of a conflict, don't suggest any fix-it.
822 ExistingError.Message.Fix.clear();
823 ExistingError.Notes.emplace_back(
824 llvm::formatv("cannot apply fix-it because an alias checker has "
825 "suggested a different fix-it; please remove one of "
826 "the checkers ('{0}', '{1}') or "
827 "ensure they are both configured the same",
828 ExistingError.DiagnosticName, Error.DiagnosticName)
829 .str());
830 }
831
832 if (Error.IsWarningAsError)
833 ExistingError.IsWarningAsError = true;
834
835 // Since it is the same error, we should take it as alias and remove it.
836 ExistingError.EnabledDiagnosticAliases.emplace_back(Error.DiagnosticName);
837 IT = Errors.erase(IT);
838 }
839 }
840}
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).