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