22 #include "clang/AST/ASTContext.h"
23 #include "clang/AST/ASTDiagnostic.h"
24 #include "clang/AST/Attr.h"
25 #include "clang/Basic/Diagnostic.h"
26 #include "clang/Basic/DiagnosticOptions.h"
27 #include "clang/Basic/FileManager.h"
28 #include "clang/Basic/SourceManager.h"
29 #include "clang/Frontend/DiagnosticRenderer.h"
30 #include "clang/Lex/Lexer.h"
31 #include "clang/Tooling/Core/Diagnostic.h"
32 #include "clang/Tooling/Core/Replacement.h"
33 #include "llvm/ADT/BitVector.h"
34 #include "llvm/ADT/STLExtras.h"
35 #include "llvm/ADT/SmallString.h"
36 #include "llvm/ADT/StringMap.h"
37 #include "llvm/Support/FormatVariadic.h"
38 #include "llvm/Support/Regex.h"
42 using namespace clang;
46 class ClangTidyDiagnosticRenderer :
public DiagnosticRenderer {
48 ClangTidyDiagnosticRenderer(
const LangOptions &
LangOpts,
49 DiagnosticOptions *DiagOpts,
54 void emitDiagnosticMessage(FullSourceLoc
Loc, PresumedLoc PLoc,
55 DiagnosticsEngine::Level Level, StringRef
Message,
56 ArrayRef<CharSourceRange> Ranges,
57 DiagOrStoredDiag Info)
override {
62 std::string CheckNameInMessage =
" [" +
Error.DiagnosticName +
"]";
63 if (
Message.endswith(CheckNameInMessage))
69 : tooling::DiagnosticMessage(
Message);
75 auto ToCharRange = [
this, &
Loc](
const CharSourceRange &SourceRange) {
76 if (SourceRange.isCharRange())
78 assert(SourceRange.isTokenRange());
79 SourceLocation End = Lexer::getLocForEndOfToken(
80 SourceRange.getEnd(), 0,
Loc.getManager(),
LangOpts);
81 return CharSourceRange::getCharRange(SourceRange.getBegin(), End);
86 llvm::make_filter_range(Ranges, [](
const CharSourceRange &R) {
87 return R.getAsRange().
isValid();
90 if (Level == DiagnosticsEngine::Note) {
91 Error.Notes.push_back(TidyMessage);
92 for (
const CharSourceRange &SourceRange : ValidRanges)
93 Error.Notes.back().Ranges.emplace_back(
Loc.getManager(),
94 ToCharRange(SourceRange));
97 assert(
Error.Message.Message.empty() &&
"Overwriting a diagnostic message");
98 Error.Message = TidyMessage;
99 for (
const CharSourceRange &SourceRange : ValidRanges)
100 Error.Message.Ranges.emplace_back(
Loc.getManager(),
101 ToCharRange(SourceRange));
104 void emitDiagnosticLoc(FullSourceLoc
Loc, PresumedLoc PLoc,
105 DiagnosticsEngine::Level Level,
106 ArrayRef<CharSourceRange> Ranges)
override {}
108 void emitCodeContext(FullSourceLoc
Loc, DiagnosticsEngine::Level Level,
109 SmallVectorImpl<CharSourceRange> &Ranges,
110 ArrayRef<FixItHint>
Hints)
override {
111 assert(
Loc.isValid());
112 tooling::DiagnosticMessage *DiagWithFix =
113 Level == DiagnosticsEngine::Note ? &
Error.Notes.back() : &
Error.Message;
117 assert(
Range.getBegin().isValid() &&
Range.getEnd().isValid() &&
118 "Invalid range in the fix-it hint.");
119 assert(
Range.getBegin().isFileID() &&
Range.getEnd().isFileID() &&
120 "Only file locations supported in fix-it hints.");
122 tooling::Replacement Replacement(
Loc.getManager(),
Range,
125 DiagWithFix->Fix[Replacement.getFilePath()].add(Replacement);
129 llvm::errs() <<
"Fix conflicts with existing fix! "
131 assert(
false &&
"Fix conflicts with existing fix!");
136 void emitIncludeLocation(FullSourceLoc
Loc, PresumedLoc PLoc)
override {}
138 void emitImportLocation(FullSourceLoc
Loc, PresumedLoc PLoc,
139 StringRef ModuleName)
override {}
141 void emitBuildingModuleLocation(FullSourceLoc
Loc, PresumedLoc PLoc,
142 StringRef ModuleName)
override {}
144 void endDiagnostic(DiagOrStoredDiag
D,
145 DiagnosticsEngine::Level Level)
override {
146 assert(!
Error.Message.Message.empty() &&
"Message has not been set");
155 ClangTidyError::Level DiagLevel,
156 StringRef BuildDirectory,
bool IsWarningAsError)
157 : tooling::
Diagnostic(CheckName, DiagLevel, BuildDirectory),
158 IsWarningAsError(IsWarningAsError) {}
161 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
163 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
166 SelfContainedDiags(false) {
176 DiagnosticIDs::Level Level ) {
177 assert(
Loc.isValid());
178 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
179 Level, (
Description +
" [" + CheckName +
"]").str());
180 CheckNamesByDiagnosticID.try_emplace(
ID, CheckName);
181 return DiagEngine->Report(
Loc,
ID);
186 DiagnosticIDs::Level Level ) {
187 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
188 Level, (
Description +
" [" + CheckName +
"]").str());
189 CheckNamesByDiagnosticID.try_emplace(
ID, CheckName);
190 return DiagEngine->Report(
ID);
194 SourceManager &SM = DiagEngine->getSourceManager();
195 llvm::ErrorOr<const FileEntry *> File =
196 SM.getFileManager().getFile(Error.Message.FilePath);
197 FileID
ID = SM.getOrCreateFileID(*File, SrcMgr::C_User);
198 SourceLocation FileStartLoc = SM.getLocForStartOfFile(
ID);
199 SourceLocation
Loc = FileStartLoc.getLocWithOffset(
200 static_cast<SourceLocation::IntTy
>(Error.Message.FileOffset));
201 return diag(Error.DiagnosticName,
Loc, Error.Message.Message,
202 static_cast<DiagnosticIDs::Level
>(Error.DiagLevel));
207 DiagnosticIDs::Level Level ) {
212 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info,
213 SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
bool AllowIO,
214 bool EnableNoLintBlocks) {
217 AllowIO, EnableNoLintBlocks);
225 CurrentFile = std::string(File);
228 WarningAsErrorFilter =
233 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
234 LangOpts = Context->getLangOpts();
238 return OptionsProvider->getGlobalOptions();
242 return CurrentOptions;
249 OptionsProvider->getOptions(File), 0);
255 ProfilePrefix = std::string(Prefix);
258 llvm::Optional<ClangTidyProfiling::StorageParams>
260 if (ProfilePrefix.empty())
267 assert(CheckFilter !=
nullptr);
268 return CheckFilter->contains(CheckName);
272 assert(WarningAsErrorFilter !=
nullptr);
273 return WarningAsErrorFilter->contains(CheckName);
277 std::string ClangWarningOption = std::string(
278 DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID));
279 if (!ClangWarningOption.empty())
280 return "clang-diagnostic-" + ClangWarningOption;
281 llvm::DenseMap<unsigned, std::string>::const_iterator I =
282 CheckNamesByDiagnosticID.find(DiagnosticID);
283 if (I != CheckNamesByDiagnosticID.end())
290 bool RemoveIncompatibleErrors,
bool GetFixesFromNotes,
291 bool EnableNolintBlocks)
292 : Context(
Ctx), ExternalDiagEngine(ExternalDiagEngine),
293 RemoveIncompatibleErrors(RemoveIncompatibleErrors),
294 GetFixesFromNotes(GetFixesFromNotes),
295 EnableNolintBlocks(EnableNolintBlocks), LastErrorRelatesToUserCode(false),
296 LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) {}
298 void ClangTidyDiagnosticConsumer::finalizeLastError() {
299 if (!Errors.empty()) {
301 if (Error.DiagnosticName ==
"clang-tidy-config") {
307 }
else if (!LastErrorRelatesToUserCode) {
310 }
else if (!LastErrorPassesLineFilter) {
317 LastErrorRelatesToUserCode =
false;
318 LastErrorPassesLineFilter =
false;
324 const llvm::StringMap<tooling::Replacements> *
328 if (!GetFixFromNotes)
330 const llvm::StringMap<tooling::Replacements> *Result =
nullptr;
332 if (!Note.Fix.empty()) {
346 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
347 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
350 SmallVector<tooling::Diagnostic, 1> SuppressionErrors;
352 EnableNolintBlocks)) {
355 LastErrorWasIgnored =
true;
356 Context.DiagEngine->Clear();
357 for (
const auto &Error : SuppressionErrors)
362 LastErrorWasIgnored =
false;
364 DiagnosticConsumer::HandleDiagnostic(DiagLevel,
Info);
366 if (DiagLevel == DiagnosticsEngine::Note) {
367 assert(!Errors.empty() &&
368 "A diagnostic note can only be appended to a message.");
372 if (CheckName.empty()) {
377 case DiagnosticsEngine::Fatal:
378 CheckName =
"clang-diagnostic-error";
381 CheckName =
"clang-diagnostic-warning";
383 case DiagnosticsEngine::Remark:
384 CheckName =
"clang-diagnostic-remark";
387 CheckName =
"clang-diagnostic-unknown";
394 DiagLevel == DiagnosticsEngine::Fatal) {
398 LastErrorRelatesToUserCode =
true;
399 LastErrorPassesLineFilter =
true;
400 }
else if (DiagLevel == DiagnosticsEngine::Remark) {
401 Level = ClangTidyError::Remark;
410 if (ExternalDiagEngine) {
413 forwardDiagnostic(
Info);
415 ClangTidyDiagnosticRenderer Converter(
416 Context.
getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
421 if (
Info.getLocation().isValid() &&
Info.hasSourceManager())
422 Loc = FullSourceLoc(
Info.getLocation(),
Info.getSourceManager());
423 Converter.emitDiagnostic(
Loc, DiagLevel,
Message,
Info.getRanges(),
424 Info.getFixItHints());
427 if (
Info.hasSourceManager())
428 checkFilters(
Info.getLocation(),
Info.getSourceManager());
430 Context.DiagEngine->Clear();
431 for (
const auto &Error : SuppressionErrors)
435 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef
FileName,
436 unsigned LineNumber)
const {
440 if (
FileName.endswith(Filter.Name)) {
441 if (Filter.LineRanges.empty())
444 if (
Range.first <= LineNumber && LineNumber <=
Range.second)
453 void ClangTidyDiagnosticConsumer::forwardDiagnostic(
const Diagnostic &Info) {
455 auto DiagLevelAndFormatString =
457 unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID(
458 DiagLevelAndFormatString.first, DiagLevelAndFormatString.second);
461 auto Builder = ExternalDiagEngine->Report(
Info.getLocation(), ExternalID);
462 for (
auto Hint :
Info.getFixItHints())
467 DiagnosticsEngine::ArgumentKind
Kind =
Info.getArgKind(
Index);
469 case clang::DiagnosticsEngine::ak_std_string:
472 case clang::DiagnosticsEngine::ak_c_string:
475 case clang::DiagnosticsEngine::ak_sint:
478 case clang::DiagnosticsEngine::ak_uint:
481 case clang::DiagnosticsEngine::ak_tokenkind:
482 Builder << static_cast<tok::TokenKind>(
Info.getRawArg(
Index));
484 case clang::DiagnosticsEngine::ak_identifierinfo:
487 case clang::DiagnosticsEngine::ak_qual:
490 case clang::DiagnosticsEngine::ak_qualtype:
493 case clang::DiagnosticsEngine::ak_declarationname:
496 case clang::DiagnosticsEngine::ak_nameddecl:
497 Builder << reinterpret_cast<const NamedDecl *>(
Info.getRawArg(
Index));
499 case clang::DiagnosticsEngine::ak_nestednamespec:
500 Builder << reinterpret_cast<NestedNameSpecifier *>(
Info.getRawArg(
Index));
502 case clang::DiagnosticsEngine::ak_declcontext:
503 Builder << reinterpret_cast<DeclContext *>(
Info.getRawArg(
Index));
505 case clang::DiagnosticsEngine::ak_qualtype_pair:
508 case clang::DiagnosticsEngine::ak_attr:
509 Builder << reinterpret_cast<Attr *>(
Info.getRawArg(
Index));
511 case clang::DiagnosticsEngine::ak_addrspace:
512 Builder << static_cast<LangAS>(
Info.getRawArg(
Index));
518 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location,
519 const SourceManager &Sources) {
522 LastErrorRelatesToUserCode =
true;
523 LastErrorPassesLineFilter =
true;
534 FileID FID = Sources.getDecomposedExpansionLoc(
Location).first;
535 const FileEntry *File = Sources.getFileEntryForID(FID);
540 LastErrorRelatesToUserCode =
true;
541 LastErrorPassesLineFilter =
true;
545 StringRef
FileName(File->getName());
546 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
550 unsigned LineNumber = Sources.getExpansionLineNumber(
Location);
551 LastErrorPassesLineFilter =
552 LastErrorPassesLineFilter || passesLineFilter(
FileName, LineNumber);
555 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
559 return HeaderFilter.get();
562 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
579 Event(
unsigned Begin,
unsigned End, EventType Type,
unsigned ErrorId,
609 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
612 Priority = std::make_tuple(Begin, Type, -End, ErrorSize, ErrorId);
615 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
620 bool operator<(
const Event &Other)
const {
621 return Priority < Other.Priority;
630 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
633 removeDuplicatedDiagnosticsOfAliasCheckers();
636 std::vector<int> Sizes;
638 std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>>
640 for (
auto &Error : Errors) {
641 if (
const auto *
Fix =
getFixIt(Error, GetFixesFromNotes))
642 ErrorFixes.emplace_back(
643 &Error,
const_cast<llvm::StringMap<tooling::Replacements> *
>(
Fix));
645 for (
const auto &ErrorAndFix : ErrorFixes) {
647 for (
const auto &FileAndReplaces : *ErrorAndFix.second) {
648 for (
const auto &Replace : FileAndReplaces.second)
649 Size += Replace.getLength();
651 Sizes.push_back(Size);
655 llvm::StringMap<std::vector<Event>> FileEvents;
656 for (
unsigned I = 0; I < ErrorFixes.size(); ++I) {
657 for (
const auto &FileAndReplace : *ErrorFixes[I].second) {
658 for (
const auto &Replace : FileAndReplace.second) {
659 unsigned Begin = Replace.getOffset();
660 unsigned End = Begin + Replace.getLength();
661 auto &Events = FileEvents[Replace.getFilePath()];
663 Events.emplace_back(Begin, End, Event::ET_Insert, I, Sizes[I]);
665 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
666 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
672 llvm::BitVector
Apply(ErrorFixes.size(),
true);
673 for (
auto &FileAndEvents : FileEvents) {
674 std::vector<Event> &Events = FileAndEvents.second;
677 int OpenIntervals = 0;
678 for (
const auto &Event : Events) {
679 switch (
Event.Type) {
680 case Event::ET_Begin:
681 if (OpenIntervals++ != 0)
684 case Event::ET_Insert:
685 if (OpenIntervals != 0)
689 if (--OpenIntervals != 0)
694 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
697 for (
unsigned I = 0; I < ErrorFixes.size(); ++I) {
699 ErrorFixes[I].second->clear();
700 ErrorFixes[I].first->Notes.emplace_back(
701 "this fix will not be applied because it overlaps with another fix");
707 struct LessClangTidyError {
709 const tooling::DiagnosticMessage &M1 = LHS.Message;
710 const tooling::DiagnosticMessage &M2 = RHS.Message;
712 return std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName,
714 std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message);
717 struct EqualClangTidyError {
719 LessClangTidyError Less;
720 return !Less(LHS, RHS) && !Less(RHS, LHS);
728 llvm::stable_sort(Errors, LessClangTidyError());
729 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
731 if (RemoveIncompatibleErrors)
732 removeIncompatibleErrors();
733 return std::move(Errors);
737 struct LessClangTidyErrorWithoutDiagnosticName {
739 const tooling::DiagnosticMessage &M1 = LHS->Message;
740 const tooling::DiagnosticMessage &M2 = RHS->Message;
742 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
743 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
748 void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {
749 using UniqueErrorSet =
750 std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>;
751 UniqueErrorSet UniqueErrors;
753 auto IT = Errors.begin();
754 while (IT != Errors.end()) {
756 std::pair<UniqueErrorSet::iterator, bool> Inserted =
757 UniqueErrors.insert(&Error);
760 if (Inserted.second) {
764 const llvm::StringMap<tooling::Replacements> &CandidateFix =
766 const llvm::StringMap<tooling::Replacements> &ExistingFix =
767 (*Inserted.first)->
Message.Fix;
769 if (CandidateFix != ExistingFix) {
772 ExistingError.Message.Fix.clear();
773 ExistingError.Notes.emplace_back(
774 llvm::formatv(
"cannot apply fix-it because an alias checker has "
775 "suggested a different fix-it; please remove one of "
776 "the checkers ('{0}', '{1}') or "
777 "ensure they are both configured the same",
778 ExistingError.DiagnosticName,
Error.DiagnosticName)
782 if (
Error.IsWarningAsError)
787 IT = Errors.erase(IT);