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