21 #include "clang/AST/ASTContext.h"
22 #include "clang/AST/ASTDiagnostic.h"
23 #include "clang/AST/Attr.h"
24 #include "clang/Basic/Diagnostic.h"
25 #include "clang/Basic/DiagnosticOptions.h"
26 #include "clang/Basic/FileManager.h"
27 #include "clang/Basic/SourceManager.h"
28 #include "clang/Frontend/DiagnosticRenderer.h"
29 #include "clang/Tooling/Core/Diagnostic.h"
30 #include "clang/Tooling/Core/Replacement.h"
31 #include "llvm/ADT/STLExtras.h"
32 #include "llvm/ADT/SmallString.h"
33 #include "llvm/ADT/StringMap.h"
34 #include "llvm/Support/FormatVariadic.h"
35 #include "llvm/Support/Regex.h"
38 using namespace clang;
42 class ClangTidyDiagnosticRenderer :
public DiagnosticRenderer {
44 ClangTidyDiagnosticRenderer(
const LangOptions &LangOpts,
45 DiagnosticOptions *DiagOpts,
47 : DiagnosticRenderer(LangOpts, DiagOpts),
Error(
Error) {}
50 void emitDiagnosticMessage(FullSourceLoc
Loc, PresumedLoc PLoc,
51 DiagnosticsEngine::Level Level, StringRef
Message,
52 ArrayRef<CharSourceRange> Ranges,
53 DiagOrStoredDiag Info)
override {
58 std::string CheckNameInMessage =
" [" +
Error.DiagnosticName +
"]";
59 if (
Message.endswith(CheckNameInMessage))
65 : tooling::DiagnosticMessage(
Message);
66 if (Level == DiagnosticsEngine::Note) {
67 Error.Notes.push_back(TidyMessage);
70 assert(
Error.Message.Message.empty() &&
"Overwriting a diagnostic message");
71 Error.Message = TidyMessage;
72 for (
const CharSourceRange &SourceRange : Ranges) {
73 Error.Ranges.emplace_back(
Loc.getManager(), SourceRange);
77 void emitDiagnosticLoc(FullSourceLoc
Loc, PresumedLoc PLoc,
78 DiagnosticsEngine::Level Level,
79 ArrayRef<CharSourceRange> Ranges)
override {}
81 void emitCodeContext(FullSourceLoc
Loc, DiagnosticsEngine::Level Level,
82 SmallVectorImpl<CharSourceRange> &Ranges,
83 ArrayRef<FixItHint>
Hints)
override {
84 assert(
Loc.isValid());
85 tooling::DiagnosticMessage *DiagWithFix =
86 Level == DiagnosticsEngine::Note ? &
Error.Notes.back() : &
Error.Message;
90 assert(
Range.getBegin().isValid() &&
Range.getEnd().isValid() &&
91 "Invalid range in the fix-it hint.");
92 assert(
Range.getBegin().isFileID() &&
Range.getEnd().isFileID() &&
93 "Only file locations supported in fix-it hints.");
95 tooling::Replacement Replacement(
Loc.getManager(),
Range,
98 DiagWithFix->Fix[Replacement.getFilePath()].add(Replacement);
102 llvm::errs() <<
"Fix conflicts with existing fix! "
104 assert(
false &&
"Fix conflicts with existing fix!");
109 void emitIncludeLocation(FullSourceLoc
Loc, PresumedLoc PLoc)
override {}
111 void emitImportLocation(FullSourceLoc
Loc, PresumedLoc PLoc,
112 StringRef ModuleName)
override {}
114 void emitBuildingModuleLocation(FullSourceLoc
Loc, PresumedLoc PLoc,
115 StringRef ModuleName)
override {}
117 void endDiagnostic(DiagOrStoredDiag D,
118 DiagnosticsEngine::Level Level)
override {
119 assert(!
Error.Message.Message.empty() &&
"Message has not been set");
128 ClangTidyError::Level DiagLevel,
129 StringRef BuildDirectory,
bool IsWarningAsError)
130 : tooling::
Diagnostic(CheckName, DiagLevel, BuildDirectory),
131 IsWarningAsError(IsWarningAsError) {}
138 switch (
auto &Result = Cache[S]) {
144 Result = Globs.contains(S) ? Yes : No;
145 return Result == Yes;
147 llvm_unreachable(
"invalid enum");
152 enum Tristate { None, Yes, No };
153 llvm::StringMap<Tristate> Cache;
157 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
159 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
171 DiagnosticIDs::Level Level ) {
172 assert(
Loc.isValid());
173 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
174 Level, (
Description +
" [" + CheckName +
"]").str());
175 CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
176 return DiagEngine->Report(
Loc, ID);
184 CurrentFile = std::string(File);
187 WarningAsErrorFilter =
192 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
193 LangOpts = Context->getLangOpts();
197 return OptionsProvider->getGlobalOptions();
201 return CurrentOptions;
208 OptionsProvider->getOptions(File), 0);
214 ProfilePrefix = std::string(Prefix);
217 llvm::Optional<ClangTidyProfiling::StorageParams>
219 if (ProfilePrefix.empty())
226 assert(CheckFilter !=
nullptr);
227 return CheckFilter->contains(CheckName);
231 assert(WarningAsErrorFilter !=
nullptr);
232 return WarningAsErrorFilter->contains(CheckName);
236 std::string ClangWarningOption = std::string(
237 DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID));
238 if (!ClangWarningOption.empty())
239 return "clang-diagnostic-" + ClangWarningOption;
240 llvm::DenseMap<unsigned, std::string>::const_iterator I =
241 CheckNamesByDiagnosticID.find(DiagnosticID);
242 if (I != CheckNamesByDiagnosticID.end())
249 bool RemoveIncompatibleErrors)
250 : Context(
Ctx), ExternalDiagEngine(ExternalDiagEngine),
251 RemoveIncompatibleErrors(RemoveIncompatibleErrors),
252 LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
253 LastErrorWasIgnored(false) {}
255 void ClangTidyDiagnosticConsumer::finalizeLastError() {
256 if (!Errors.empty()) {
262 }
else if (!LastErrorRelatesToUserCode) {
265 }
else if (!LastErrorPassesLineFilter) {
272 LastErrorRelatesToUserCode =
false;
273 LastErrorPassesLineFilter =
false;
278 const size_t NolintIndex =
Line.find(NolintDirectiveText);
279 if (NolintIndex == StringRef::npos)
282 size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
284 if (BracketIndex <
Line.size() &&
Line[BracketIndex] ==
'(') {
286 const size_t BracketEndIndex =
Line.find(
')', BracketIndex);
287 if (BracketEndIndex != StringRef::npos) {
288 StringRef ChecksStr =
289 Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
291 if (ChecksStr !=
"*") {
292 std::string CheckName = Context.getCheckName(DiagID);
294 SmallVector<StringRef, 1>
Checks;
295 ChecksStr.split(
Checks,
',', -1,
false);
297 [](StringRef S) {
return S.trim(); });
305 static llvm::Optional<StringRef>
getBuffer(
const SourceManager &SM, FileID File,
311 bool CharDataInvalid =
false;
312 const SrcMgr::SLocEntry &
Entry = SM.getSLocEntry(File, &CharDataInvalid);
313 if (CharDataInvalid || !
Entry.isFile())
315 const SrcMgr::ContentCache *Cache =
Entry.getFile().getContentCache();
316 const llvm::MemoryBuffer *Buffer =
317 AllowIO ? Cache->getBuffer(SM.getDiagnostics(), SM.getFileManager(),
318 SourceLocation(), &CharDataInvalid)
319 : Cache->getRawBuffer();
320 if (!Buffer || CharDataInvalid)
322 return Buffer->getBuffer();
331 std::tie(File,
Offset) = SM.getDecomposedSpellingLoc(
Loc);
332 llvm::Optional<StringRef> Buffer =
getBuffer(SM, File, AllowIO);
337 StringRef RestOfLine = Buffer->substr(
Offset).split(
'\n').first;
343 Buffer->substr(0,
Offset).rsplit(
'\n').first.rsplit(
'\n').second;
344 return IsNOLINTFound(
"NOLINTNEXTLINE", PrevLine, DiagID, Context);
348 SourceLocation
Loc,
unsigned DiagID,
354 if (!
Loc.isMacroID())
356 Loc = SM.getImmediateExpansionRange(
Loc).getBegin();
367 return Info.getLocation().isValid() &&
369 DiagLevel != DiagnosticsEngine::Fatal &&
379 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
380 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
386 LastErrorWasIgnored =
true;
390 LastErrorWasIgnored =
false;
392 DiagnosticConsumer::HandleDiagnostic(DiagLevel,
Info);
394 if (DiagLevel == DiagnosticsEngine::Note) {
395 assert(!Errors.empty() &&
396 "A diagnostic note can only be appended to a message.");
400 if (CheckName.empty()) {
405 case DiagnosticsEngine::Fatal:
406 CheckName =
"clang-diagnostic-error";
409 CheckName =
"clang-diagnostic-warning";
412 CheckName =
"clang-diagnostic-unknown";
419 DiagLevel == DiagnosticsEngine::Fatal) {
423 LastErrorRelatesToUserCode =
true;
424 LastErrorPassesLineFilter =
true;
432 if (ExternalDiagEngine) {
435 forwardDiagnostic(
Info);
437 ClangTidyDiagnosticRenderer Converter(
438 Context.
getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
443 if (
Info.getLocation().isValid() &&
Info.hasSourceManager())
444 Loc = FullSourceLoc(
Info.getLocation(),
Info.getSourceManager());
445 Converter.emitDiagnostic(
Loc, DiagLevel,
Message,
Info.getRanges(),
446 Info.getFixItHints());
449 if (
Info.hasSourceManager())
450 checkFilters(
Info.getLocation(),
Info.getSourceManager());
453 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef
FileName,
454 unsigned LineNumber)
const {
458 if (
FileName.endswith(Filter.Name)) {
459 if (Filter.LineRanges.empty())
462 if (
Range.first <= LineNumber && LineNumber <=
Range.second)
471 void ClangTidyDiagnosticConsumer::forwardDiagnostic(
const Diagnostic &Info) {
473 auto DiagLevelAndFormatString =
475 unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID(
476 DiagLevelAndFormatString.first, DiagLevelAndFormatString.second);
479 auto Builder = ExternalDiagEngine->Report(
Info.getLocation(), ExternalID);
480 for (
auto Hint :
Info.getFixItHints())
485 DiagnosticsEngine::ArgumentKind
Kind =
Info.getArgKind(
Index);
487 case clang::DiagnosticsEngine::ak_std_string:
490 case clang::DiagnosticsEngine::ak_c_string:
493 case clang::DiagnosticsEngine::ak_sint:
496 case clang::DiagnosticsEngine::ak_uint:
499 case clang::DiagnosticsEngine::ak_tokenkind:
500 Builder << static_cast<tok::TokenKind>(
Info.getRawArg(
Index));
502 case clang::DiagnosticsEngine::ak_identifierinfo:
505 case clang::DiagnosticsEngine::ak_qual:
508 case clang::DiagnosticsEngine::ak_qualtype:
511 case clang::DiagnosticsEngine::ak_declarationname:
514 case clang::DiagnosticsEngine::ak_nameddecl:
515 Builder << reinterpret_cast<const NamedDecl *>(
Info.getRawArg(
Index));
517 case clang::DiagnosticsEngine::ak_nestednamespec:
518 Builder << reinterpret_cast<NestedNameSpecifier *>(
Info.getRawArg(
Index));
520 case clang::DiagnosticsEngine::ak_declcontext:
521 Builder << reinterpret_cast<DeclContext *>(
Info.getRawArg(
Index));
523 case clang::DiagnosticsEngine::ak_qualtype_pair:
526 case clang::DiagnosticsEngine::ak_attr:
527 Builder << reinterpret_cast<Attr *>(
Info.getRawArg(
Index));
529 case clang::DiagnosticsEngine::ak_addrspace:
530 Builder << static_cast<LangAS>(
Info.getRawArg(
Index));
536 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location,
537 const SourceManager &Sources) {
540 LastErrorRelatesToUserCode =
true;
541 LastErrorPassesLineFilter =
true;
552 FileID FID = Sources.getDecomposedExpansionLoc(
Location).first;
553 const FileEntry *File = Sources.getFileEntryForID(FID);
558 LastErrorRelatesToUserCode =
true;
559 LastErrorPassesLineFilter =
true;
563 StringRef
FileName(File->getName());
564 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
568 unsigned LineNumber = Sources.getExpansionLineNumber(
Location);
569 LastErrorPassesLineFilter =
570 LastErrorPassesLineFilter || passesLineFilter(
FileName, LineNumber);
573 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
577 return HeaderFilter.get();
580 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
596 Event(
unsigned Begin,
unsigned End, EventType
Type,
unsigned ErrorId,
624 if (
Type == ET_Begin)
625 Priority = std::make_tuple(Begin,
Type, -End, -ErrorSize, ErrorId);
627 Priority = std::make_tuple(End,
Type, -Begin, ErrorSize, ErrorId);
630 bool operator<(
const Event &Other)
const {
631 return Priority < Other.Priority;
640 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
643 removeDuplicatedDiagnosticsOfAliasCheckers();
646 std::vector<int> Sizes;
648 std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>>
650 for (
auto &Error : Errors) {
651 if (
const auto *
Fix = tooling::selectFirstFix(Error))
652 ErrorFixes.emplace_back(
653 &Error,
const_cast<llvm::StringMap<tooling::Replacements> *
>(
Fix));
655 for (
const auto &ErrorAndFix : ErrorFixes) {
657 for (
const auto &FileAndReplaces : *ErrorAndFix.second) {
658 for (
const auto &Replace : FileAndReplaces.second)
659 Size += Replace.getLength();
661 Sizes.push_back(Size);
665 std::map<std::string, std::vector<Event>> FileEvents;
666 for (
unsigned I = 0; I < ErrorFixes.size(); ++I) {
667 for (
const auto &FileAndReplace : *ErrorFixes[I].second) {
668 for (
const auto &Replace : FileAndReplace.second) {
669 unsigned Begin = Replace.getOffset();
670 unsigned End = Begin + Replace.getLength();
671 const std::string &FilePath = std::string(Replace.getFilePath());
675 auto &Events = FileEvents[FilePath];
676 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
677 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
682 std::vector<bool>
Apply(ErrorFixes.size(),
true);
683 for (
auto &FileAndEvents : FileEvents) {
684 std::vector<Event> &Events = FileAndEvents.second;
687 int OpenIntervals = 0;
688 for (
const auto &Event : Events) {
689 if (
Event.Type == Event::ET_End)
693 if (OpenIntervals != 0)
695 if (
Event.Type == Event::ET_Begin)
698 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
701 for (
unsigned I = 0; I < ErrorFixes.size(); ++I) {
703 ErrorFixes[I].second->clear();
704 ErrorFixes[I].first->Notes.emplace_back(
705 "this fix will not be applied because it overlaps with another fix");
711 struct LessClangTidyError {
713 const tooling::DiagnosticMessage &M1 = LHS.Message;
714 const tooling::DiagnosticMessage &M2 = RHS.Message;
716 return std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName,
718 std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message);
721 struct EqualClangTidyError {
723 LessClangTidyError Less;
724 return !Less(LHS, RHS) && !Less(RHS, LHS);
732 llvm::sort(Errors, LessClangTidyError());
733 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
735 if (RemoveIncompatibleErrors)
736 removeIncompatibleErrors();
737 return std::move(Errors);
741 struct LessClangTidyErrorWithoutDiagnosticName {
743 const tooling::DiagnosticMessage &M1 = LHS->Message;
744 const tooling::DiagnosticMessage &M2 = RHS->Message;
746 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
747 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
752 void ClangTidyDiagnosticConsumer::removeDuplicatedDiagnosticsOfAliasCheckers() {
753 using UniqueErrorSet =
754 std::set<ClangTidyError *, LessClangTidyErrorWithoutDiagnosticName>;
755 UniqueErrorSet UniqueErrors;
757 auto IT = Errors.begin();
758 while (IT != Errors.end()) {
760 std::pair<UniqueErrorSet::iterator, bool> Inserted =
761 UniqueErrors.insert(&Error);
764 if (Inserted.second) {
768 const llvm::StringMap<tooling::Replacements> &CandidateFix =
770 const llvm::StringMap<tooling::Replacements> &ExistingFix =
771 (*Inserted.first)->
Message.Fix;
773 if (CandidateFix != ExistingFix) {
776 ExistingError.Message.Fix.clear();
777 ExistingError.Notes.emplace_back(
778 llvm::formatv(
"cannot apply fix-it because an alias checker has "
779 "suggested a different fix-it; please remove one of "
780 "the checkers ('{0}', '{1}') or "
781 "ensure they are both configured the same",
782 ExistingError.DiagnosticName,
Error.DiagnosticName)
786 if (
Error.IsWarningAsError)
791 IT = Errors.erase(IT);