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