clang-tools 22.0.0git
ClangTidyDiagnosticConsumer.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext
10/// and ClangTidyError classes.
11///
12/// This tool uses the Clang Tooling infrastructure, see
13/// https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
14/// for details on setting it up with LLVM source tree.
15///
16//===----------------------------------------------------------------------===//
17
19#include "ClangTidyOptions.h"
20#include "GlobList.h"
22#include "clang/AST/ASTContext.h"
23#include "clang/AST/ASTDiagnostic.h"
24#include "clang/AST/Attr.h"
25#include "clang/AST/Expr.h"
26#include "clang/Basic/CharInfo.h"
27#include "clang/Basic/Diagnostic.h"
28#include "clang/Basic/DiagnosticOptions.h"
29#include "clang/Basic/FileManager.h"
30#include "clang/Basic/SourceManager.h"
31#include "clang/Frontend/DiagnosticRenderer.h"
32#include "clang/Lex/Lexer.h"
33#include "clang/Tooling/Core/Diagnostic.h"
34#include "clang/Tooling/Core/Replacement.h"
35#include "llvm/ADT/BitVector.h"
36#include "llvm/ADT/STLExtras.h"
37#include "llvm/ADT/StringMap.h"
38#include "llvm/Support/FormatVariadic.h"
39#include "llvm/Support/Regex.h"
40#include <optional>
41#include <tuple>
42#include <utility>
43#include <vector>
44using namespace clang;
45using namespace tidy;
46
47namespace {
48class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {
49public:
50 ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,
51 DiagnosticOptions &DiagOpts,
52 ClangTidyError &Error)
53 : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
54
55protected:
56 void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc,
57 DiagnosticsEngine::Level Level, StringRef Message,
58 ArrayRef<CharSourceRange> Ranges,
59 DiagOrStoredDiag Info) override {
60 // Remove check name from the message.
61 // FIXME: Remove this once there's a better way to pass check names than
62 // appending the check name to the message in ClangTidyContext::diag and
63 // using getCustomDiagID.
64 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 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 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 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 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 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 FileEntryRef File = llvm::cantFail(FM.getFileRef(Error.Message.FilePath));
199 FileID ID = SM.getOrCreateFileID(File, SrcMgr::C_User);
200 SourceLocation FileStartLoc = SM.getLocForStartOfFile(ID);
201 SourceLocation Loc = FileStartLoc.getLocWithOffset(
202 static_cast<SourceLocation::IntTy>(Error.Message.FileOffset));
203 return diag(Error.DiagnosticName, Loc, Error.Message.Message,
204 static_cast<DiagnosticIDs::Level>(Error.DiagLevel));
205}
206
208 StringRef Message,
209 DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
210 return diag("clang-tidy-config", Message, Level);
211}
212
214 DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info,
215 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors, bool AllowIO,
216 bool EnableNoLintBlocks) {
217 std::string CheckName = getCheckName(Info.getID());
218 return NoLintHandler.shouldSuppress(DiagLevel, Info, CheckName, NoLintErrors,
219 AllowIO, EnableNoLintBlocks);
220}
221
222void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
223 DiagEngine->setSourceManager(SourceMgr);
224}
225
226static bool parseFileExtensions(llvm::ArrayRef<std::string> AllFileExtensions,
227 FileExtensionsSet &FileExtensions) {
228 FileExtensions.clear();
229 for (StringRef Suffix : AllFileExtensions) {
230 StringRef Extension = Suffix.trim();
231 if (!llvm::all_of(Extension, isAlphanumeric))
232 return false;
233 FileExtensions.insert(Extension);
234 }
235 return true;
236}
237
239 CurrentFile = std::string(File);
240 CurrentOptions = getOptionsForFile(CurrentFile);
241 CheckFilter = std::make_unique<CachedGlobList>(*getOptions().Checks);
242 WarningAsErrorFilter =
243 std::make_unique<CachedGlobList>(*getOptions().WarningsAsErrors);
244 if (!parseFileExtensions(*getOptions().HeaderFileExtensions,
245 HeaderFileExtensions))
246 this->configurationDiag("Invalid header file extensions");
247 if (!parseFileExtensions(*getOptions().ImplementationFileExtensions,
248 ImplementationFileExtensions))
249 this->configurationDiag("Invalid implementation file extensions");
250}
251
252void ClangTidyContext::setASTContext(ASTContext *Context) {
253 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
254 LangOpts = Context->getLangOpts();
255}
256
258 return OptionsProvider->getGlobalOptions();
259}
260
262 return CurrentOptions;
263}
264
266 // Merge options on top of getDefaults() as a safeguard against options with
267 // unset values.
269 OptionsProvider->getOptions(File), 0);
270}
271
272void ClangTidyContext::setEnableProfiling(bool P) { Profile = P; }
273
275 ProfilePrefix = std::string(Prefix);
276}
277
278std::optional<ClangTidyProfiling::StorageParams>
280 if (ProfilePrefix.empty())
281 return std::nullopt;
282
283 return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile);
284}
285
286bool ClangTidyContext::isCheckEnabled(StringRef CheckName) const {
287 assert(CheckFilter != nullptr);
288 return CheckFilter->contains(CheckName);
289}
290
291bool ClangTidyContext::treatAsError(StringRef CheckName) const {
292 assert(WarningAsErrorFilter != nullptr);
293 return WarningAsErrorFilter->contains(CheckName);
294}
295
296std::string ClangTidyContext::getCheckName(unsigned DiagnosticID) const {
297 std::string ClangWarningOption = std::string(
298 DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID));
299 if (!ClangWarningOption.empty())
300 return "clang-diagnostic-" + ClangWarningOption;
301 llvm::DenseMap<unsigned, std::string>::const_iterator I =
302 CheckNamesByDiagnosticID.find(DiagnosticID);
303 if (I != CheckNamesByDiagnosticID.end())
304 return I->second;
305 return "";
306}
307
309 ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine,
310 bool RemoveIncompatibleErrors, bool GetFixesFromNotes,
311 bool EnableNolintBlocks)
312 : Context(Ctx), ExternalDiagEngine(ExternalDiagEngine),
313 RemoveIncompatibleErrors(RemoveIncompatibleErrors),
314 GetFixesFromNotes(GetFixesFromNotes),
315 EnableNolintBlocks(EnableNolintBlocks) {}
316
317void ClangTidyDiagnosticConsumer::finalizeLastError() {
318 if (!Errors.empty()) {
319 ClangTidyError &Error = Errors.back();
320 if (Error.DiagnosticName == "clang-tidy-config") {
321 // Never ignore these.
322 } else if (!Context.isCheckEnabled(Error.DiagnosticName) &&
323 Error.DiagLevel != ClangTidyError::Error) {
324 ++Context.Stats.ErrorsIgnoredCheckFilter;
325 Errors.pop_back();
326 } else if (!LastErrorRelatesToUserCode) {
327 ++Context.Stats.ErrorsIgnoredNonUserCode;
328 Errors.pop_back();
329 } else if (!LastErrorPassesLineFilter) {
330 ++Context.Stats.ErrorsIgnoredLineFilter;
331 Errors.pop_back();
332 } else {
333 ++Context.Stats.ErrorsDisplayed;
334 }
335 }
336 LastErrorRelatesToUserCode = false;
337 LastErrorPassesLineFilter = false;
338}
339
340namespace clang::tidy {
341
342const llvm::StringMap<tooling::Replacements> *
343getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix) {
344 if (!Diagnostic.Message.Fix.empty())
345 return &Diagnostic.Message.Fix;
346 if (!AnyFix)
347 return nullptr;
348 const llvm::StringMap<tooling::Replacements> *Result = nullptr;
349 for (const auto &Note : Diagnostic.Notes) {
350 if (!Note.Fix.empty()) {
351 if (Result)
352 // We have 2 different fixes in notes, bail out.
353 return nullptr;
354 Result = &Note.Fix;
355 }
356 }
357 return Result;
358}
359
360} // namespace clang::tidy
361
362void ClangTidyDiagnosticConsumer::BeginSourceFile(const LangOptions &LangOpts,
363 const Preprocessor *PP) {
364 DiagnosticConsumer::BeginSourceFile(LangOpts, PP);
365
366 assert(!InSourceFile);
367 InSourceFile = true;
368}
369
371 assert(InSourceFile);
372 InSourceFile = false;
373
374 DiagnosticConsumer::EndSourceFile();
375}
376
378 DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
379 // A diagnostic should not be reported outside of a
380 // BeginSourceFile()/EndSourceFile() pair if it has a source location.
381 assert(InSourceFile || Info.getLocation().isInvalid());
382
383 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
384 return;
385
386 SmallVector<tooling::Diagnostic, 1> SuppressionErrors;
387 if (Context.shouldSuppressDiagnostic(DiagLevel, Info, SuppressionErrors,
388 EnableNolintBlocks)) {
389 ++Context.Stats.ErrorsIgnoredNOLINT;
390 // Ignored a warning, should ignore related notes as well
391 LastErrorWasIgnored = true;
392 for (const auto &Error : SuppressionErrors)
393 Context.diag(Error);
394 return;
395 }
396
397 LastErrorWasIgnored = false;
398 // Count warnings/errors.
399 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
400
401 if (DiagLevel == DiagnosticsEngine::Note) {
402 assert(!Errors.empty() &&
403 "A diagnostic note can only be appended to a message.");
404 } else {
405 finalizeLastError();
406 std::string CheckName = Context.getCheckName(Info.getID());
407 if (CheckName.empty()) {
408 // This is a compiler diagnostic without a warning option. Assign check
409 // name based on its level.
410 switch (DiagLevel) {
411 case DiagnosticsEngine::Error:
412 case DiagnosticsEngine::Fatal:
413 CheckName = "clang-diagnostic-error";
414 break;
415 case DiagnosticsEngine::Warning:
416 CheckName = "clang-diagnostic-warning";
417 break;
418 case DiagnosticsEngine::Remark:
419 CheckName = "clang-diagnostic-remark";
420 break;
421 default:
422 CheckName = "clang-diagnostic-unknown";
423 break;
424 }
425 }
426
427 ClangTidyError::Level Level = ClangTidyError::Warning;
428 if (DiagLevel == DiagnosticsEngine::Error ||
429 DiagLevel == DiagnosticsEngine::Fatal) {
430 // Force reporting of Clang errors regardless of filters and non-user
431 // code.
432 Level = ClangTidyError::Error;
433 LastErrorRelatesToUserCode = true;
434 LastErrorPassesLineFilter = true;
435 } else if (DiagLevel == DiagnosticsEngine::Remark) {
436 Level = ClangTidyError::Remark;
437 }
438
439 bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
440 Context.treatAsError(CheckName);
441 Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),
442 IsWarningAsError);
443 }
444
445 if (ExternalDiagEngine) {
446 // If there is an external diagnostics engine, like in the
447 // ClangTidyPluginAction case, forward the diagnostics to it.
448 forwardDiagnostic(Info);
449 } else {
450 ClangTidyDiagnosticRenderer Converter(
451 Context.getLangOpts(), Context.DiagEngine->getDiagnosticOptions(),
452 Errors.back());
453 SmallString<100> Message;
454 Info.FormatDiagnostic(Message);
455 FullSourceLoc Loc;
456 if (Info.hasSourceManager())
457 Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager());
458 else if (Context.DiagEngine->hasSourceManager())
459 Loc = FullSourceLoc(Info.getLocation(),
460 Context.DiagEngine->getSourceManager());
461 Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
462 Info.getFixItHints());
463 }
464
465 if (Info.hasSourceManager())
466 checkFilters(Info.getLocation(), Info.getSourceManager());
467
468 for (const auto &Error : SuppressionErrors)
469 Context.diag(Error);
470}
471
472bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
473 unsigned LineNumber) const {
474 if (Context.getGlobalOptions().LineFilter.empty())
475 return true;
476 for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
477 if (FileName.ends_with(Filter.Name)) {
478 if (Filter.LineRanges.empty())
479 return true;
480 for (const FileFilter::LineRange &Range : Filter.LineRanges) {
481 if (Range.first <= LineNumber && LineNumber <= Range.second)
482 return true;
483 }
484 return false;
485 }
486 }
487 return false;
488}
489
490void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) {
491 // Acquire a diagnostic ID also in the external diagnostics engine.
492 auto DiagLevelAndFormatString =
493 Context.getDiagLevelAndFormatString(Info.getID(), Info.getLocation());
494 unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID(
495 DiagLevelAndFormatString.first, DiagLevelAndFormatString.second);
496
497 // Forward the details.
498 auto Builder = ExternalDiagEngine->Report(Info.getLocation(), ExternalID);
499 for (const FixItHint &Hint : Info.getFixItHints())
500 Builder << Hint;
501 for (auto Range : Info.getRanges())
502 Builder << Range;
503 for (unsigned Index = 0; Index < Info.getNumArgs(); ++Index) {
504 DiagnosticsEngine::ArgumentKind Kind = Info.getArgKind(Index);
505 switch (Kind) {
506 case clang::DiagnosticsEngine::ak_std_string:
507 Builder << Info.getArgStdStr(Index);
508 break;
509 case clang::DiagnosticsEngine::ak_c_string:
510 Builder << Info.getArgCStr(Index);
511 break;
512 case clang::DiagnosticsEngine::ak_sint:
513 Builder << Info.getArgSInt(Index);
514 break;
515 case clang::DiagnosticsEngine::ak_uint:
516 Builder << Info.getArgUInt(Index);
517 break;
518 case clang::DiagnosticsEngine::ak_tokenkind:
519 Builder << static_cast<tok::TokenKind>(Info.getRawArg(Index));
520 break;
521 case clang::DiagnosticsEngine::ak_identifierinfo:
522 Builder << Info.getArgIdentifier(Index);
523 break;
524 case clang::DiagnosticsEngine::ak_qual:
525 Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(Index));
526 break;
527 case clang::DiagnosticsEngine::ak_qualtype:
528 Builder << QualType::getFromOpaquePtr((void *)Info.getRawArg(Index));
529 break;
530 case clang::DiagnosticsEngine::ak_declarationname:
531 Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(Index));
532 break;
533 case clang::DiagnosticsEngine::ak_nameddecl:
534 Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(Index));
535 break;
536 case clang::DiagnosticsEngine::ak_nestednamespec:
537 Builder << NestedNameSpecifier::getFromVoidPointer(
538 reinterpret_cast<void *>(Info.getRawArg(Index)));
539 break;
540 case clang::DiagnosticsEngine::ak_declcontext:
541 Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(Index));
542 break;
543 case clang::DiagnosticsEngine::ak_qualtype_pair:
544 assert(false); // This one is not passed around.
545 break;
546 case clang::DiagnosticsEngine::ak_attr:
547 Builder << reinterpret_cast<Attr *>(Info.getRawArg(Index));
548 break;
549 case clang::DiagnosticsEngine::ak_attr_info:
550 Builder << reinterpret_cast<AttributeCommonInfo *>(Info.getRawArg(Index));
551 break;
552 case clang::DiagnosticsEngine::ak_addrspace:
553 Builder << static_cast<LangAS>(Info.getRawArg(Index));
554 break;
555 case clang::DiagnosticsEngine::ak_expr:
556 Builder << reinterpret_cast<const Expr *>(Info.getRawArg(Index));
557 }
558 }
559}
560
561void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
562 const SourceManager &Sources) {
563 // Invalid location may mean a diagnostic in a command line, don't skip these.
564 if (!Location.isValid()) {
565 LastErrorRelatesToUserCode = true;
566 LastErrorPassesLineFilter = true;
567 return;
568 }
569
570 if (!*Context.getOptions().SystemHeaders &&
571 (Sources.isInSystemHeader(Location) || Sources.isInSystemMacro(Location)))
572 return;
573
574 // FIXME: We start with a conservative approach here, but the actual type of
575 // location needed depends on the check (in particular, where this check wants
576 // to apply fixes).
577 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
578 OptionalFileEntryRef File = Sources.getFileEntryRefForID(FID);
579
580 // -DMACRO definitions on the command line have locations in a virtual buffer
581 // that doesn't have a FileEntry. Don't skip these as well.
582 if (!File) {
583 LastErrorRelatesToUserCode = true;
584 LastErrorPassesLineFilter = true;
585 return;
586 }
587
588 StringRef FileName(File->getName());
589 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
590 Sources.isInMainFile(Location) ||
591 (getHeaderFilter()->match(FileName) &&
592 !getExcludeHeaderFilter()->match(FileName));
593
594 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
595 LastErrorPassesLineFilter =
596 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
597}
598
599llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
600 if (!HeaderFilter)
601 HeaderFilter =
602 std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
603 return HeaderFilter.get();
604}
605
606llvm::Regex *ClangTidyDiagnosticConsumer::getExcludeHeaderFilter() {
607 if (!ExcludeHeaderFilter)
608 ExcludeHeaderFilter = std::make_unique<llvm::Regex>(
609 *Context.getOptions().ExcludeHeaderFilterRegex);
610 return ExcludeHeaderFilter.get();
611}
612
613void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
614 // Each error is modelled as the set of intervals in which it applies
615 // replacements. To detect overlapping replacements, we use a sweep line
616 // algorithm over these sets of intervals.
617 // An event here consists of the opening or closing of an interval. During the
618 // process, we maintain a counter with the amount of open intervals. If we
619 // find an endpoint of an interval and this counter is different from 0, it
620 // means that this interval overlaps with another one, so we set it as
621 // inapplicable.
622 struct Event {
623 // An event can be either the begin or the end of an interval.
624 enum EventType {
625 ET_Begin = 1,
626 ET_Insert = 0,
627 ET_End = -1,
628 };
629
630 Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,
631 unsigned ErrorSize)
632 : Type(Type), ErrorId(ErrorId) {
633 // The events are going to be sorted by their position. In case of draw:
634 //
635 // * If an interval ends at the same position at which other interval
636 // begins, this is not an overlapping, so we want to remove the ending
637 // interval before adding the starting one: end events have higher
638 // priority than begin events.
639 //
640 // * If we have several begin points at the same position, we will mark as
641 // inapplicable the ones that we process later, so the first one has to
642 // be the one with the latest end point, because this one will contain
643 // all the other intervals. For the same reason, if we have several end
644 // points in the same position, the last one has to be the one with the
645 // earliest begin point. In both cases, we sort non-increasingly by the
646 // position of the complementary.
647 //
648 // * In case of two equal intervals, the one whose error is bigger can
649 // potentially contain the other one, so we want to process its begin
650 // points before and its end points later.
651 //
652 // * Finally, if we have two equal intervals whose errors have the same
653 // size, none of them will be strictly contained inside the other.
654 // Sorting by ErrorId will guarantee that the begin point of the first
655 // one will be processed before, disallowing the second one, and the
656 // end point of the first one will also be processed before,
657 // disallowing the first one.
658 switch (Type) {
659 case ET_Begin:
660 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
661 break;
662 case ET_Insert:
663 Priority = std::make_tuple(Begin, Type, -End, ErrorSize, ErrorId);
664 break;
665 case ET_End:
666 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
667 break;
668 }
669 }
670
671 bool operator<(const Event &Other) const {
672 return Priority < Other.Priority;
673 }
674
675 // Determines if this event is the begin or the end of an interval.
676 EventType Type;
677 // The index of the error to which the interval that generated this event
678 // belongs.
679 unsigned ErrorId;
680 // The events will be sorted based on this field.
681 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
682 };
683
684 removeDuplicatedDiagnosticsOfAliasCheckers();
685
686 // Compute error sizes.
687 std::vector<int> Sizes;
688 std::vector<
689 std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>>
690 ErrorFixes;
691 for (auto &Error : Errors) {
692 if (const auto *Fix = getFixIt(Error, GetFixesFromNotes))
693 ErrorFixes.emplace_back(
694 &Error, const_cast<llvm::StringMap<tooling::Replacements> *>(Fix));
695 }
696 for (const auto &ErrorAndFix : ErrorFixes) {
697 int Size = 0;
698 for (const auto &FileAndReplaces : *ErrorAndFix.second) {
699 for (const auto &Replace : FileAndReplaces.second)
700 Size += Replace.getLength();
701 }
702 Sizes.push_back(Size);
703 }
704
705 // Build events from error intervals.
706 llvm::StringMap<std::vector<Event>> FileEvents;
707 for (unsigned I = 0; I < ErrorFixes.size(); ++I) {
708 for (const auto &FileAndReplace : *ErrorFixes[I].second) {
709 for (const auto &Replace : FileAndReplace.second) {
710 unsigned Begin = Replace.getOffset();
711 unsigned End = Begin + Replace.getLength();
712 auto &Events = FileEvents[Replace.getFilePath()];
713 if (Begin == End) {
714 Events.emplace_back(Begin, End, Event::ET_Insert, I, Sizes[I]);
715 } else {
716 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
717 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
718 }
719 }
720 }
721 }
722
723 llvm::BitVector Apply(ErrorFixes.size(), true);
724 for (auto &FileAndEvents : FileEvents) {
725 std::vector<Event> &Events = FileAndEvents.second;
726 // Sweep.
727 llvm::sort(Events);
728 int OpenIntervals = 0;
729 for (const auto &Event : Events) {
730 switch (Event.Type) {
731 case Event::ET_Begin:
732 if (OpenIntervals++ != 0)
733 Apply[Event.ErrorId] = false;
734 break;
735 case Event::ET_Insert:
736 if (OpenIntervals != 0)
737 Apply[Event.ErrorId] = false;
738 break;
739 case Event::ET_End:
740 if (--OpenIntervals != 0)
741 Apply[Event.ErrorId] = false;
742 break;
743 }
744 }
745 assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");
746 }
747
748 for (unsigned I = 0; I < ErrorFixes.size(); ++I) {
749 if (!Apply[I]) {
750 ErrorFixes[I].second->clear();
751 ErrorFixes[I].first->Notes.emplace_back(
752 "this fix will not be applied because it overlaps with another fix");
753 }
754 }
755}
756
757namespace {
758struct LessClangTidyError {
759 bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
760 const tooling::DiagnosticMessage &M1 = LHS.Message;
761 const tooling::DiagnosticMessage &M2 = RHS.Message;
762
763 return std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName,
764 M1.Message) <
765 std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message);
766 }
767};
768struct EqualClangTidyError {
769 bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
770 LessClangTidyError Less;
771 return !Less(LHS, RHS) && !Less(RHS, LHS);
772 }
773};
774} // end anonymous namespace
775
776std::vector<ClangTidyError> ClangTidyDiagnosticConsumer::take() {
777 finalizeLastError();
778
779 llvm::stable_sort(Errors, LessClangTidyError());
780 Errors.erase(llvm::unique(Errors, EqualClangTidyError()), Errors.end());
781 if (RemoveIncompatibleErrors)
782 removeIncompatibleErrors();
783 return std::move(Errors);
784}
785
786namespace {
787struct LessClangTidyErrorWithoutDiagnosticName {
788 bool operator()(const ClangTidyError *LHS, const ClangTidyError *RHS) const {
789 const tooling::DiagnosticMessage &M1 = LHS->Message;
790 const tooling::DiagnosticMessage &M2 = RHS->Message;
791
792 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
793 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
794 }
795};
796} // end anonymous namespace
797
798void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {
799 using UniqueErrorSet =
800 std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>;
801 UniqueErrorSet UniqueErrors;
802
803 auto IT = Errors.begin();
804 while (IT != Errors.end()) {
805 ClangTidyError &Error = *IT;
806 std::pair<UniqueErrorSet::iterator, bool> Inserted =
807 UniqueErrors.insert(&Error);
808
809 // Unique error, we keep it and move along.
810 if (Inserted.second) {
811 ++IT;
812 } else {
813 ClangTidyError &ExistingError = **Inserted.first;
814 const llvm::StringMap<tooling::Replacements> &CandidateFix =
815 Error.Message.Fix;
816 const llvm::StringMap<tooling::Replacements> &ExistingFix =
817 (*Inserted.first)->Message.Fix;
818
819 if (CandidateFix != ExistingFix) {
820
821 // In case of a conflict, don't suggest any fix-it.
822 ExistingError.Message.Fix.clear();
823 ExistingError.Notes.emplace_back(
824 llvm::formatv("cannot apply fix-it because an alias checker has "
825 "suggested a different fix-it; please remove one of "
826 "the checkers ('{0}', '{1}') or "
827 "ensure they are both configured the same",
828 ExistingError.DiagnosticName, Error.DiagnosticName)
829 .str());
830 }
831
832 if (Error.IsWarningAsError)
833 ExistingError.IsWarningAsError = true;
834
835 // Since it is the same error, we should take it as alias and remove it.
836 ExistingError.EnabledDiagnosticAliases.emplace_back(Error.DiagnosticName);
837 IT = Errors.erase(IT);
838 }
839 }
840}
static bool parseFileExtensions(llvm::ArrayRef< std::string > AllFileExtensions, FileExtensionsSet &FileExtensions)
static cl::opt< bool > Fix("fix", desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
static cl::opt< std::string > WarningsAsErrors("warnings-as-errors", desc(R"( Upgrades warnings to errors. Same format as '-checks'. This option's value is appended to the value of the 'WarningsAsErrors' option in .clang-tidy file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory))
static cl::opt< std::string > Checks("checks", desc(R"( Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' prefix add checks with matching names to the set, globs with the '-' prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of the 'Checks' option in .clang-tidy file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory))
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
ClangTidyContext(std::unique_ptr< ClangTidyOptionsProvider > OptionsProvider)
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check is enabled for the CurrentFile.
std::string getCheckName(unsigned DiagnosticID) const
Returns the name of the clang-tidy check which produced this diagnostic ID.
const ClangTidyOptions & getOptions() const
Returns options for CurrentFile.
DiagnosticBuilder configurationDiag(StringRef Message, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Report any errors to do with reading the configuration using this method.
void setASTContext(ASTContext *Context)
Sets ASTContext for the current translation unit.
void setProfileStoragePrefix(StringRef ProfilePrefix)
Control storage of profile date.
void setEnableProfiling(bool Profile)
Control profile collection in clang-tidy.
void setCurrentFile(StringRef File)
Should be called when starting to process new translation unit.
bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info, SmallVectorImpl< tooling::Diagnostic > &NoLintErrors, bool AllowIO=true, bool EnableNoLintBlocks=true)
Check whether a given diagnostic should be suppressed due to the presence of a "NOLINT" suppression c...
bool treatAsError(StringRef CheckName) const
Returns true if the check should be upgraded to error for the CurrentFile.
DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Report any errors detected using this method.
ClangTidyOptions getOptionsForFile(StringRef File) const
Returns options for File.
std::optional< ClangTidyProfiling::StorageParams > getProfileStorageParams() const
const ClangTidyGlobalOptions & getGlobalOptions() const
Returns global options.
void setSourceManager(SourceManager *SourceMgr)
Sets the SourceManager of the used DiagnosticsEngine.
ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine=nullptr, bool RemoveIncompatibleErrors=true, bool GetFixesFromNotes=false, bool EnableNolintBlocks=true)
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override
void BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP=nullptr) override
@ Info
An information message.
Definition Protocol.h:738
@ Error
An error message.
Definition Protocol.h:734
bool operator<(const Ref &L, const Ref &R)
Definition Ref.h:98
@ Type
An inlay hint that for a type annotation.
Definition Protocol.h:1672
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::ProBoundsAvoidUncheckedContainerAccess P
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
A detected error complete with information to display diagnostic and automatic fix.
ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory, bool IsWarningAsError)
std::vector< FileFilter > LineFilter
Output warnings from certain line ranges of certain files only.
Contains options for clang-tidy.
ClangTidyOptions merge(const ClangTidyOptions &Other, unsigned Order) const
Creates a new ClangTidyOptions instance combined from all fields of this instance overridden by the f...
static ClangTidyOptions getDefaults()
These options are used for all settings that haven't been overridden by the OptionsProvider.
Contains a list of line ranges in a single file.
std::pair< unsigned int, unsigned int > LineRange
LineRange is a pair<start, end> (inclusive).