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