21 #include "clang/AST/ASTDiagnostic.h" 22 #include "clang/AST/Attr.h" 23 #include "clang/Basic/Diagnostic.h" 24 #include "clang/Basic/DiagnosticOptions.h" 25 #include "clang/Frontend/DiagnosticRenderer.h" 26 #include "clang/Tooling/Core/Diagnostic.h" 27 #include "llvm/ADT/STLExtras.h" 28 #include "llvm/ADT/SmallString.h" 31 using namespace clang;
35 class ClangTidyDiagnosticRenderer :
public DiagnosticRenderer {
37 ClangTidyDiagnosticRenderer(
const LangOptions &LangOpts,
38 DiagnosticOptions *DiagOpts,
40 : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
43 void emitDiagnosticMessage(FullSourceLoc
Loc, PresumedLoc PLoc,
44 DiagnosticsEngine::Level Level, StringRef
Message,
45 ArrayRef<CharSourceRange> Ranges,
46 DiagOrStoredDiag Info)
override {
51 std::string CheckNameInMessage =
" [" +
Error.DiagnosticName +
"]";
52 if (Message.endswith(CheckNameInMessage))
53 Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
57 ? tooling::DiagnosticMessage(Message, Loc.getManager(),
Loc)
58 : tooling::DiagnosticMessage(Message);
59 if (Level == DiagnosticsEngine::Note) {
60 Error.Notes.push_back(TidyMessage);
63 assert(
Error.Message.Message.empty() &&
"Overwriting a diagnostic message");
64 Error.Message = TidyMessage;
67 void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
68 DiagnosticsEngine::Level Level,
69 ArrayRef<CharSourceRange> Ranges)
override {}
71 void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
72 SmallVectorImpl<CharSourceRange> &Ranges,
73 ArrayRef<FixItHint>
Hints)
override {
74 assert(Loc.isValid());
75 tooling::DiagnosticMessage *DiagWithFix =
76 Level == DiagnosticsEngine::Note ? &
Error.Notes.back() : &
Error.Message;
78 for (
const auto &
FixIt : Hints) {
80 assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
81 "Invalid range in the fix-it hint.");
82 assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
83 "Only file locations supported in fix-it hints.");
85 tooling::Replacement Replacement(Loc.getManager(),
Range,
88 DiagWithFix->Fix[Replacement.getFilePath()].add(Replacement);
92 llvm::errs() <<
"Fix conflicts with existing fix! " 94 assert(
false &&
"Fix conflicts with existing fix!");
99 void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc)
override {}
101 void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
102 StringRef ModuleName)
override {}
104 void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
105 StringRef ModuleName)
override {}
107 void endDiagnostic(DiagOrStoredDiag D,
108 DiagnosticsEngine::Level Level)
override {
109 assert(!
Error.Message.Message.empty() &&
"Message has not been set");
118 ClangTidyError::Level DiagLevel,
119 StringRef BuildDirectory,
bool IsWarningAsError)
120 : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
121 IsWarningAsError(IsWarningAsError) {}
129 switch (
auto &Result = Cache[S]) {
135 Result = Globs.contains(S) ? Yes : No;
136 return Result == Yes;
138 llvm_unreachable(
"invalid enum");
143 enum Tristate { None, Yes, No };
144 llvm::StringMap<Tristate> Cache;
148 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
150 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
152 AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers) {
161 StringRef CheckName, SourceLocation Loc, StringRef
Description,
162 DiagnosticIDs::Level Level ) {
163 assert(Loc.isValid());
164 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
165 Level, (Description +
" [" + CheckName +
"]").str());
166 CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
167 return DiagEngine->Report(Loc, ID);
171 DiagEngine->setSourceManager(SourceMgr);
178 WarningAsErrorFilter =
183 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
184 LangOpts = Context->getLangOpts();
188 return OptionsProvider->getGlobalOptions();
192 return CurrentOptions;
199 OptionsProvider->getOptions(File));
205 ProfilePrefix = Prefix;
208 llvm::Optional<ClangTidyProfiling::StorageParams>
210 if (ProfilePrefix.empty())
217 assert(CheckFilter !=
nullptr);
218 return CheckFilter->contains(CheckName);
222 assert(WarningAsErrorFilter !=
nullptr);
223 return WarningAsErrorFilter->contains(CheckName);
227 std::string ClangWarningOption =
228 DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(DiagnosticID);
229 if (!ClangWarningOption.empty())
230 return "clang-diagnostic-" + ClangWarningOption;
231 llvm::DenseMap<unsigned, std::string>::const_iterator I =
232 CheckNamesByDiagnosticID.find(DiagnosticID);
233 if (I != CheckNamesByDiagnosticID.end())
240 bool RemoveIncompatibleErrors)
241 : Context(Ctx), ExternalDiagEngine(ExternalDiagEngine),
242 RemoveIncompatibleErrors(RemoveIncompatibleErrors),
243 LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
244 LastErrorWasIgnored(false) {}
246 void ClangTidyDiagnosticConsumer::finalizeLastError() {
247 if (!Errors.empty()) {
250 Error.DiagLevel != ClangTidyError::Error) {
253 }
else if (!LastErrorRelatesToUserCode) {
256 }
else if (!LastErrorPassesLineFilter) {
263 LastErrorRelatesToUserCode =
false;
264 LastErrorPassesLineFilter =
false;
269 const size_t NolintIndex = Line.find(NolintDirectiveText);
270 if (NolintIndex == StringRef::npos)
273 size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
275 if (BracketIndex < Line.size() && Line[BracketIndex] ==
'(') {
277 const size_t BracketEndIndex = Line.find(
')', BracketIndex);
278 if (BracketEndIndex != StringRef::npos) {
279 StringRef ChecksStr =
280 Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
282 if (ChecksStr !=
"*") {
285 SmallVector<StringRef, 1>
Checks;
286 ChecksStr.split(Checks,
',', -1,
false);
287 llvm::transform(Checks, Checks.begin(),
288 [](StringRef S) {
return S.trim(); });
289 return llvm::find(Checks, CheckName) != Checks.end();
300 const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
305 const char *P = CharacterData;
306 while (*P !=
'\0' && *P !=
'\r' && *P !=
'\n')
308 StringRef RestOfLine(CharacterData, P - CharacterData + 1);
313 const char *BufBegin =
314 SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
315 if (Invalid || P == BufBegin)
320 while (P != BufBegin && *P !=
'\n')
329 const char *LineEnd = P;
332 while (P != BufBegin && *P !=
'\n')
335 RestOfLine = StringRef(P, LineEnd - P + 1);
336 if (
IsNOLINTFound(
"NOLINTNEXTLINE", RestOfLine, DiagID, Context))
343 SourceLocation Loc,
unsigned DiagID,
348 if (!Loc.isMacroID())
350 Loc = SM.getImmediateExpansionRange(Loc).getBegin();
360 bool CheckMacroExpansion) {
361 return Info.getLocation().isValid() &&
362 DiagLevel != DiagnosticsEngine::Error &&
363 DiagLevel != DiagnosticsEngine::Fatal &&
367 Info.getID(), Context);
374 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
375 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
379 ++Context.Stats.ErrorsIgnoredNOLINT;
381 LastErrorWasIgnored =
true;
385 LastErrorWasIgnored =
false;
387 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
389 if (DiagLevel == DiagnosticsEngine::Note) {
390 assert(!Errors.empty() &&
391 "A diagnostic note can only be appended to a message.");
394 std::string CheckName = Context.getCheckName(Info.getID());
395 if (CheckName.empty()) {
399 case DiagnosticsEngine::Error:
400 case DiagnosticsEngine::Fatal:
401 CheckName =
"clang-diagnostic-error";
403 case DiagnosticsEngine::Warning:
404 CheckName =
"clang-diagnostic-warning";
407 CheckName =
"clang-diagnostic-unknown";
412 ClangTidyError::Level Level = ClangTidyError::Warning;
413 if (DiagLevel == DiagnosticsEngine::Error ||
414 DiagLevel == DiagnosticsEngine::Fatal) {
417 Level = ClangTidyError::Error;
418 LastErrorRelatesToUserCode =
true;
419 LastErrorPassesLineFilter =
true;
421 bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
422 Context.treatAsError(CheckName);
423 Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),
427 if (ExternalDiagEngine) {
430 forwardDiagnostic(Info);
432 ClangTidyDiagnosticRenderer Converter(
433 Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
436 Info.FormatDiagnostic(Message);
438 if (Info.getLocation().isValid() && Info.hasSourceManager())
439 Loc = FullSourceLoc(Info.getLocation(), Info.getSourceManager());
440 Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
441 Info.getFixItHints());
444 if (Info.hasSourceManager())
445 checkFilters(Info.getLocation(), Info.getSourceManager());
448 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef
FileName,
449 unsigned LineNumber)
const {
450 if (Context.getGlobalOptions().LineFilter.empty())
452 for (
const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
453 if (FileName.endswith(Filter.Name)) {
454 if (Filter.LineRanges.empty())
457 if (Range.first <= LineNumber && LineNumber <= Range.second)
466 void ClangTidyDiagnosticConsumer::forwardDiagnostic(
const Diagnostic &Info) {
468 auto DiagLevelAndFormatString =
469 Context.getDiagLevelAndFormatString(Info.getID(), Info.getLocation());
470 unsigned ExternalID = ExternalDiagEngine->getDiagnosticIDs()->getCustomDiagID(
471 DiagLevelAndFormatString.first, DiagLevelAndFormatString.second);
474 auto Builder = ExternalDiagEngine->Report(Info.getLocation(), ExternalID);
475 for (
auto Hint : Info.getFixItHints())
477 for (
auto Range : Info.getRanges())
480 DiagnosticsEngine::ArgumentKind
Kind = Info.getArgKind(
Index);
482 case clang::DiagnosticsEngine::ak_std_string:
485 case clang::DiagnosticsEngine::ak_c_string:
488 case clang::DiagnosticsEngine::ak_sint:
491 case clang::DiagnosticsEngine::ak_uint:
494 case clang::DiagnosticsEngine::ak_tokenkind:
495 Builder << static_cast<tok::TokenKind>(Info.getRawArg(
Index));
497 case clang::DiagnosticsEngine::ak_identifierinfo:
500 case clang::DiagnosticsEngine::ak_qual:
501 Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(
Index));
503 case clang::DiagnosticsEngine::ak_qualtype:
504 Builder << QualType::getFromOpaquePtr((
void *)Info.getRawArg(
Index));
506 case clang::DiagnosticsEngine::ak_declarationname:
507 Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(
Index));
509 case clang::DiagnosticsEngine::ak_nameddecl:
510 Builder << reinterpret_cast<const NamedDecl *>(Info.getRawArg(
Index));
512 case clang::DiagnosticsEngine::ak_nestednamespec:
513 Builder << reinterpret_cast<NestedNameSpecifier *>(Info.getRawArg(
Index));
515 case clang::DiagnosticsEngine::ak_declcontext:
516 Builder << reinterpret_cast<DeclContext *>(Info.getRawArg(
Index));
518 case clang::DiagnosticsEngine::ak_qualtype_pair:
521 case clang::DiagnosticsEngine::ak_attr:
522 Builder << reinterpret_cast<Attr *>(Info.getRawArg(
Index));
524 case clang::DiagnosticsEngine::ak_addrspace:
525 Builder << static_cast<LangAS>(Info.getRawArg(
Index));
531 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location,
532 const SourceManager &Sources) {
534 if (!Location.isValid()) {
535 LastErrorRelatesToUserCode =
true;
536 LastErrorPassesLineFilter =
true;
540 if (!*Context.getOptions().SystemHeaders &&
541 Sources.isInSystemHeader(Location))
547 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
548 const FileEntry *File = Sources.getFileEntryForID(FID);
553 LastErrorRelatesToUserCode =
true;
554 LastErrorPassesLineFilter =
true;
558 StringRef
FileName(File->getName());
559 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
560 Sources.isInMainFile(Location) ||
561 getHeaderFilter()->match(FileName);
563 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
564 LastErrorPassesLineFilter =
565 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
568 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
571 std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
572 return HeaderFilter.get();
575 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
591 Event(
unsigned Begin,
unsigned End, EventType
Type,
unsigned ErrorId,
593 :
Type(Type), ErrorId(ErrorId) {
619 if (Type == ET_Begin)
620 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
622 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
625 bool operator<(
const Event &Other)
const {
626 return Priority < Other.Priority;
635 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
639 std::vector<int> Sizes;
641 std::pair<ClangTidyError *, llvm::StringMap<tooling::Replacements> *>>
643 for (
auto &Error : Errors) {
644 if (
const auto *
Fix = tooling::selectFirstFix(Error))
645 ErrorFixes.emplace_back(
646 &Error,
const_cast<llvm::StringMap<tooling::Replacements> *
>(
Fix));
648 for (
const auto &ErrorAndFix : ErrorFixes) {
650 for (
const auto &FileAndReplaces : *ErrorAndFix.second) {
651 for (
const auto &Replace : FileAndReplaces.second)
652 Size += Replace.getLength();
654 Sizes.push_back(Size);
658 std::map<std::string, std::vector<Event>> FileEvents;
659 for (
unsigned I = 0; I < ErrorFixes.size(); ++I) {
660 for (
const auto &FileAndReplace : *ErrorFixes[I].second) {
661 for (
const auto &Replace : FileAndReplace.second) {
662 unsigned Begin = Replace.getOffset();
663 unsigned End = Begin + Replace.getLength();
664 const std::string &FilePath = Replace.getFilePath();
668 auto &Events = FileEvents[FilePath];
669 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
670 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
675 std::vector<bool> Apply(ErrorFixes.size(),
true);
676 for (
auto &FileAndEvents : FileEvents) {
677 std::vector<Event> &Events = FileAndEvents.second;
679 std::sort(Events.begin(), Events.end());
680 int OpenIntervals = 0;
681 for (
const auto &Event : Events) {
682 if (Event.Type == Event::ET_End)
686 if (OpenIntervals != 0)
687 Apply[Event.ErrorId] =
false;
688 if (Event.Type == Event::ET_Begin)
691 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
694 for (
unsigned I = 0; I < ErrorFixes.size(); ++I) {
696 ErrorFixes[I].second->clear();
697 ErrorFixes[I].first->Notes.emplace_back(
698 "this fix will not be applied because it overlaps with another fix");
704 struct LessClangTidyError {
706 const tooling::DiagnosticMessage &M1 = LHS.Message;
707 const tooling::DiagnosticMessage &M2 = RHS.Message;
710 std::tie(M1.FilePath, M1.FileOffset, LHS.DiagnosticName, M1.Message) <
711 std::tie(M2.FilePath, M2.FileOffset, RHS.DiagnosticName, M2.Message);
714 struct EqualClangTidyError {
716 LessClangTidyError Less;
717 return !Less(LHS, RHS) && !Less(RHS, LHS);
725 std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
726 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
728 if (RemoveIncompatibleErrors)
729 removeIncompatibleErrors();
730 return std::move(Errors);
llvm::Optional< std::string > Checks
Checks filter.
SourceLocation Loc
'#' location in the include directive
ClangTidyOptions mergeWith(const ClangTidyOptions &Other) const
Creates a new ClangTidyOptions instance combined from all fields of this instance overridden by the f...
Read-only set of strings represented as a list of positive and negative globs.
const ClangTidyGlobalOptions & getGlobalOptions() const
Returns global options.
static bool LineIsMarkedWithNOLINTinMacro(const SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check is enabled for the CurrentFile.
constexpr llvm::StringLiteral Message
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
Contains options for clang-tidy.
unsigned ErrorsIgnoredCheckFilter
bool contains(StringRef S)
std::pair< unsigned, unsigned > LineRange
LineRange is a pair<start, end> (inclusive).
void setCurrentFile(StringRef File)
Should be called when starting to process new translation unit.
static cl::opt< bool > AllowEnablingAnalyzerAlphaCheckers("allow-enabling-analyzer-alpha-checkers", cl::init(false), cl::Hidden, cl::cat(ClangTidyCategory))
This option allows enabling the experimental alpha checkers from the static analyzer.
DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc, StringRef Message, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Report any errors detected using this method.
unsigned ErrorsIgnoredNonUserCode
std::vector< ClangTidyError > take()
bool operator<(const Ref &L, const Ref &R)
llvm::Optional< ClangTidyProfiling::StorageParams > getProfileStorageParams() const
ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, DiagnosticsEngine *ExternalDiagEngine=nullptr, bool RemoveIncompatibleErrors=true)
ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory, bool IsWarningAsError)
ClangTidyOptions getOptionsForFile(StringRef File) const
Returns options for File.
const ClangTidyOptions & getOptions() const
Returns options for CurrentFile.
void setASTContext(ASTContext *Context)
Sets ASTContext for the current translation unit.
void setProfileStoragePrefix(StringRef ProfilePrefix)
Control storage of profile date.
unsigned ErrorsIgnoredLineFilter
llvm::Optional< std::string > WarningsAsErrors
WarningsAsErrors filter.
void setSourceManager(SourceManager *SourceMgr)
Sets the SourceManager of the used DiagnosticsEngine.
CodeCompletionBuilder Builder
CachedGlobList(StringRef Globs)
Contains a list of line ranges in a single file.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info, ClangTidyContext &Context, bool CheckMacroExpansion)
Check whether a given diagnostic should be suppressed due to the presence of a "NOLINT" suppression c...
ClangTidyContext(std::unique_ptr< ClangTidyOptionsProvider > OptionsProvider, bool AllowEnablingAnalyzerAlphaCheckers=false)
Initializes ClangTidyContext instance.
static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line, unsigned DiagID, const ClangTidyContext &Context)
std::vector< FixItHint > Hints
CharSourceRange Range
SourceRange for the file name.
A detected error complete with information to display diagnostic and automatic fix.
std::string getCheckName(unsigned DiagnosticID) const
Returns the name of the clang-tidy check which produced this diagnostic ID.
static cl::opt< std::string > Checks("checks", cl::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.
llvm::Optional< FixItHint > FixIt
static ClangTidyOptions getDefaults()
These options are used for all settings that haven't been overridden by the OptionsProvider.
static cl::opt< bool > Fix("fix", cl::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))
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override
bool treatAsError(StringRef CheckName) const
Returns true if the check should be upgraded to error for the CurrentFile.
void setEnableProfiling(bool Profile)
Control profile collection in clang-tidy.
static bool LineIsMarkedWithNOLINT(const SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)
const SymbolIndex * Index