21 #include "clang/AST/ASTDiagnostic.h" 22 #include "clang/Basic/DiagnosticOptions.h" 23 #include "clang/Frontend/DiagnosticRenderer.h" 24 #include "llvm/ADT/STLExtras.h" 25 #include "llvm/ADT/SmallString.h" 28 using namespace clang;
32 class ClangTidyDiagnosticRenderer :
public DiagnosticRenderer {
34 ClangTidyDiagnosticRenderer(
const LangOptions &LangOpts,
35 DiagnosticOptions *DiagOpts,
37 : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
40 void emitDiagnosticMessage(FullSourceLoc
Loc, PresumedLoc PLoc,
41 DiagnosticsEngine::Level Level, StringRef
Message,
42 ArrayRef<CharSourceRange> Ranges,
43 DiagOrStoredDiag
Info)
override {
48 std::string CheckNameInMessage =
" [" + Error.DiagnosticName +
"]";
49 if (Message.endswith(CheckNameInMessage))
50 Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
54 ? tooling::DiagnosticMessage(Message, Loc.getManager(),
Loc)
55 : tooling::DiagnosticMessage(Message);
56 if (Level == DiagnosticsEngine::Note) {
57 Error.Notes.push_back(TidyMessage);
60 assert(Error.Message.Message.empty() &&
"Overwriting a diagnostic message");
61 Error.Message = TidyMessage;
64 void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
65 DiagnosticsEngine::Level Level,
66 ArrayRef<CharSourceRange> Ranges)
override {}
68 void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
69 SmallVectorImpl<CharSourceRange> &Ranges,
70 ArrayRef<FixItHint> Hints)
override {
71 assert(Loc.isValid());
72 for (
const auto &FixIt : Hints) {
73 CharSourceRange
Range = FixIt.RemoveRange;
74 assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
75 "Invalid range in the fix-it hint.");
76 assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
77 "Only file locations supported in fix-it hints.");
79 tooling::Replacement Replacement(Loc.getManager(),
Range,
81 llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement);
85 llvm::errs() <<
"Fix conflicts with existing fix! " 87 assert(
false &&
"Fix conflicts with existing fix!");
92 void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc)
override {}
94 void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
95 StringRef ModuleName)
override {}
97 void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
98 StringRef ModuleName)
override {}
100 void endDiagnostic(DiagOrStoredDiag D,
101 DiagnosticsEngine::Level Level)
override {
102 assert(!Error.Message.Message.empty() &&
"Message has not been set");
111 ClangTidyError::Level DiagLevel,
112 StringRef BuildDirectory,
bool IsWarningAsError)
113 : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
114 IsWarningAsError(IsWarningAsError) {}
119 GlobList = GlobList.trim(
" \r\n");
120 if (GlobList.startswith(
"-")) {
121 GlobList = GlobList.substr(1);
129 StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(
','));
130 StringRef Glob = UntrimmedGlob.trim(
' ');
131 GlobList = GlobList.substr(UntrimmedGlob.size() + 1);
132 SmallString<128> RegexText(
"^");
133 StringRef MetaChars(
"()^$|*+?.[]\\{}");
134 for (
char C : Glob) {
136 RegexText.push_back(
'.');
137 else if (MetaChars.find(C) != StringRef::npos)
138 RegexText.push_back(
'\\');
139 RegexText.push_back(C);
141 RegexText.push_back(
'$');
142 return llvm::Regex(RegexText);
147 NextGlob(Globs.empty() ? nullptr : new
GlobList(Globs)) {}
154 Contains = NextGlob->contains(S, Contains);
163 switch (
auto &Result = Cache[S]) {
164 case Yes:
return true;
165 case No:
return false;
167 Result = Globs.contains(S) ? Yes : No;
168 return Result == Yes;
170 llvm_unreachable(
"invalid enum");
175 enum Tristate { None, Yes, No };
176 llvm::StringMap<Tristate> Cache;
180 std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
181 : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
191 StringRef CheckName, SourceLocation Loc, StringRef Description,
192 DiagnosticIDs::Level Level ) {
193 assert(Loc.isValid());
194 unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
195 Level, (Description +
" [" + CheckName +
"]").str());
196 CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
197 return DiagEngine->Report(Loc, ID);
200 void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
205 DiagEngine->setSourceManager(SourceMgr);
212 WarningAsErrorFilter =
217 DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
218 LangOpts = Context->getLangOpts();
222 return OptionsProvider->getGlobalOptions();
226 return CurrentOptions;
233 OptionsProvider->getOptions(File));
239 assert(CheckFilter !=
nullptr);
240 return CheckFilter->contains(CheckName);
244 assert(WarningAsErrorFilter !=
nullptr);
245 return WarningAsErrorFilter->contains(CheckName);
250 Errors.push_back(Error);
254 llvm::DenseMap<unsigned, std::string>::const_iterator I =
255 CheckNamesByDiagnosticID.find(DiagnosticID);
256 if (I != CheckNamesByDiagnosticID.end())
263 : Context(Ctx), RemoveIncompatibleErrors(RemoveIncompatibleErrors),
264 LastErrorRelatesToUserCode(false), LastErrorPassesLineFilter(false),
265 LastErrorWasIgnored(false) {
266 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts =
new DiagnosticOptions();
267 Diags = llvm::make_unique<DiagnosticsEngine>(
268 IntrusiveRefCntPtr<DiagnosticIDs>(
new DiagnosticIDs), &*DiagOpts,
this,
270 Context.setDiagnosticsEngine(Diags.get());
273 void ClangTidyDiagnosticConsumer::finalizeLastError() {
274 if (!Errors.empty()) {
277 Error.DiagLevel != ClangTidyError::Error) {
280 }
else if (!LastErrorRelatesToUserCode) {
283 }
else if (!LastErrorPassesLineFilter) {
290 LastErrorRelatesToUserCode =
false;
291 LastErrorPassesLineFilter =
false;
296 const size_t NolintIndex = Line.find(NolintDirectiveText);
297 if (NolintIndex == StringRef::npos)
300 size_t BracketIndex = NolintIndex + NolintDirectiveText.size();
302 if (BracketIndex < Line.size() && Line[BracketIndex] ==
'(') {
304 const size_t BracketEndIndex = Line.find(
')', BracketIndex);
305 if (BracketEndIndex != StringRef::npos) {
306 StringRef ChecksStr =
307 Line.substr(BracketIndex, BracketEndIndex - BracketIndex);
309 if (ChecksStr !=
"*") {
312 SmallVector<StringRef, 1>
Checks;
313 ChecksStr.split(Checks,
',', -1,
false);
314 llvm::transform(Checks, Checks.begin(),
315 [](StringRef S) {
return S.trim(); });
316 return llvm::find(Checks, CheckName) != Checks.end();
327 const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
332 const char *P = CharacterData;
333 while (*P !=
'\0' && *P !=
'\r' && *P !=
'\n')
335 StringRef RestOfLine(CharacterData, P - CharacterData + 1);
340 const char *BufBegin =
341 SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
342 if (Invalid || P == BufBegin)
347 while (P != BufBegin && *P !=
'\n')
356 const char *LineEnd = P;
359 while (P != BufBegin && *P !=
'\n')
362 RestOfLine = StringRef(P, LineEnd - P + 1);
363 if (
IsNOLINTFound(
"NOLINTNEXTLINE", RestOfLine, DiagID, Context))
375 if (!Loc.isMacroID())
377 Loc = SM.getImmediateExpansionRange(Loc).first;
383 DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
384 if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
387 if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
388 DiagLevel != DiagnosticsEngine::Fatal &&
390 Info.getLocation(), Info.getID(),
394 LastErrorWasIgnored =
true;
398 LastErrorWasIgnored =
false;
400 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
402 if (DiagLevel == DiagnosticsEngine::Note) {
403 assert(!Errors.empty() &&
404 "A diagnostic note can only be appended to a message.");
407 StringRef WarningOption =
408 Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
410 std::string CheckName = !WarningOption.empty()
411 ? (
"clang-diagnostic-" + WarningOption).str()
414 if (CheckName.empty()) {
418 case DiagnosticsEngine::Error:
419 case DiagnosticsEngine::Fatal:
420 CheckName =
"clang-diagnostic-error";
422 case DiagnosticsEngine::Warning:
423 CheckName =
"clang-diagnostic-warning";
426 CheckName =
"clang-diagnostic-unknown";
431 ClangTidyError::Level Level = ClangTidyError::Warning;
432 if (DiagLevel == DiagnosticsEngine::Error ||
433 DiagLevel == DiagnosticsEngine::Fatal) {
436 Level = ClangTidyError::Error;
437 LastErrorRelatesToUserCode =
true;
438 LastErrorPassesLineFilter =
true;
440 bool IsWarningAsError = DiagLevel == DiagnosticsEngine::Warning &&
446 ClangTidyDiagnosticRenderer Converter(
447 Context.
getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
450 Info.FormatDiagnostic(Message);
452 (Info.getLocation().isInvalid())
454 : FullSourceLoc(Info.getLocation(), Info.getSourceManager());
455 Converter.emitDiagnostic(Loc, DiagLevel, Message, Info.getRanges(),
456 Info.getFixItHints());
458 checkFilters(Info.getLocation());
461 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
462 unsigned LineNumber)
const {
466 if (FileName.endswith(Filter.Name)) {
467 if (Filter.LineRanges.empty())
470 if (Range.first <= LineNumber && LineNumber <= Range.second)
479 void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation
Location) {
481 if (!Location.isValid()) {
482 LastErrorRelatesToUserCode =
true;
483 LastErrorPassesLineFilter =
true;
487 const SourceManager &Sources = Diags->getSourceManager();
489 Sources.isInSystemHeader(Location))
495 FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
496 const FileEntry *
File = Sources.getFileEntryForID(FID);
501 LastErrorRelatesToUserCode =
true;
502 LastErrorPassesLineFilter =
true;
506 StringRef FileName(File->getName());
507 LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
508 Sources.isInMainFile(Location) ||
509 getHeaderFilter()->match(FileName);
511 unsigned LineNumber = Sources.getExpansionLineNumber(Location);
512 LastErrorPassesLineFilter =
513 LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
516 llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
520 return HeaderFilter.get();
523 void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
524 SmallVectorImpl<ClangTidyError> &Errors)
const {
540 Event(
unsigned Begin,
unsigned End, EventType Type,
unsigned ErrorId,
542 : Type(Type), ErrorId(ErrorId) {
568 if (Type == ET_Begin)
569 Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
571 Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
574 bool operator<(
const Event &Other)
const {
575 return Priority < Other.Priority;
584 std::tuple<unsigned, EventType, int, int, unsigned> Priority;
588 std::vector<int> Sizes;
589 for (
const auto &Error : Errors) {
591 for (
const auto &FileAndReplaces : Error.Fix) {
592 for (
const auto &Replace : FileAndReplaces.second)
593 Size += Replace.getLength();
595 Sizes.push_back(Size);
599 std::map<std::string, std::vector<Event>> FileEvents;
600 for (
unsigned I = 0; I < Errors.size(); ++I) {
601 for (
const auto &FileAndReplace : Errors[I].
Fix) {
602 for (
const auto &Replace : FileAndReplace.second) {
603 unsigned Begin = Replace.getOffset();
604 unsigned End = Begin + Replace.getLength();
605 const std::string &FilePath = Replace.getFilePath();
609 auto &Events = FileEvents[FilePath];
610 Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
611 Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
616 std::vector<bool> Apply(Errors.size(),
true);
617 for (
auto &FileAndEvents : FileEvents) {
618 std::vector<Event> &Events = FileAndEvents.second;
620 std::sort(Events.begin(), Events.end());
621 int OpenIntervals = 0;
622 for (
const auto &Event : Events) {
623 if (Event.Type == Event::ET_End)
627 if (OpenIntervals != 0)
628 Apply[Event.ErrorId] =
false;
629 if (Event.Type == Event::ET_Begin)
632 assert(OpenIntervals == 0 &&
"Amount of begin/end points doesn't match");
635 for (
unsigned I = 0; I < Errors.size(); ++I) {
637 Errors[I].Fix.clear();
638 Errors[I].Notes.emplace_back(
639 "this fix will not be applied because it overlaps with another fix");
645 struct LessClangTidyError {
647 const tooling::DiagnosticMessage &M1 = LHS.Message;
648 const tooling::DiagnosticMessage &M2 = RHS.Message;
650 return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
651 std::tie(M2.FilePath, M2.FileOffset, M2.Message);
654 struct EqualClangTidyError {
656 LessClangTidyError Less;
657 return !Less(LHS, RHS) && !Less(RHS, LHS);
666 std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
667 Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
670 if (RemoveIncompatibleErrors)
671 removeIncompatibleErrors(Errors);
674 Context.storeError(Error);
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.
GlobList(StringRef Globs)
GlobList is a comma-separated list of globs (only '*' metacharacter is supported) with optional '-' p...
const ClangTidyGlobalOptions & getGlobalOptions() const
Returns global options.
bool isCheckEnabled(StringRef CheckName) const
Returns true if the check is enabled for the CurrentFile.
void finish() override
Flushes the internal diagnostics buffer to the ClangTidyContext.
bool contains(StringRef S)
Returns true if the pattern matches S.
llvm::Optional< std::string > HeaderFilterRegex
Output warnings from headers matching this filter.
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
static const StringRef Message
Contains options for clang-tidy.
unsigned ErrorsIgnoredCheckFilter
static bool ConsumeNegativeIndicator(StringRef &GlobList)
unsigned ErrorsIgnoredNOLINT
bool contains(StringRef S)
llvm::Optional< bool > SystemHeaders
Output warnings from system headers matching HeaderFilterRegex.
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 llvm::Regex ConsumeGlob(StringRef &GlobList)
DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc, StringRef Message, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Report any errors detected using this method.
unsigned ErrorsIgnoredNonUserCode
void setCheckProfileData(ProfileData *Profile)
Set the output struct for profile data.
ClangTidyContext(std::unique_ptr< ClangTidyOptionsProvider > OptionsProvider)
Initializes ClangTidyContext instance.
const LangOptions & getLangOpts() const
Gets the language options from the AST context.
ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory, bool IsWarningAsError)
StringRef getCheckName(unsigned DiagnosticID) const
Returns the name of the clang-tidy check which produced this diagnostic ID.
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.
std::vector< FileFilter > LineFilter
Output warnings from certain line ranges of certain files only.
unsigned ErrorsIgnoredLineFilter
llvm::Optional< std::string > WarningsAsErrors
WarningsAsErrors filter.
void setSourceManager(SourceManager *SourceMgr)
Sets the SourceManager of the used DiagnosticsEngine.
CachedGlobList(StringRef Globs)
Contains a list of line ranges in a single file.
static bool IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line, unsigned DiagID, const ClangTidyContext &Context)
CharSourceRange Range
SourceRange for the file name.
A detected error complete with information to display diagnostic and automatic fix.
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.
static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)
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.
ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx, bool RemoveIncompatibleErrors=true)
Container for clang-tidy profiling data.
const std::string & getCurrentBuildDirectory()
Returns build directory of the current translation unit.
static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc, unsigned DiagID, const ClangTidyContext &Context)