diff options
5 files changed, 858 insertions, 858 deletions
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp index c4a5ede0e81..c4ab236967a 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -1,619 +1,619 @@ -//===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== //
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext
-/// and ClangTidyError classes.
-///
-/// This tool uses the Clang Tooling infrastructure, see
-/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
-/// for details on setting it up with LLVM source tree.
-///
-//===----------------------------------------------------------------------===//
-
-#include "ClangTidyDiagnosticConsumer.h"
-#include "ClangTidyOptions.h"
-#include "clang/AST/ASTDiagnostic.h"
-#include "clang/Basic/DiagnosticOptions.h"
-#include "clang/Frontend/DiagnosticRenderer.h"
-#include "llvm/ADT/SmallString.h"
-#include <tuple>
-#include <vector>
-using namespace clang;
-using namespace tidy;
-
-namespace {
-class ClangTidyDiagnosticRenderer : public DiagnosticRenderer {
-public:
- ClangTidyDiagnosticRenderer(const LangOptions &LangOpts,
- DiagnosticOptions *DiagOpts,
- ClangTidyError &Error)
- : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {}
-
-protected:
- void emitDiagnosticMessage(SourceLocation Loc, PresumedLoc PLoc,
- DiagnosticsEngine::Level Level, StringRef Message,
- ArrayRef<CharSourceRange> Ranges,
- const SourceManager *SM,
- DiagOrStoredDiag Info) override {
- // Remove check name from the message.
- // FIXME: Remove this once there's a better way to pass check names than
- // appending the check name to the message in ClangTidyContext::diag and
- // using getCustomDiagID.
- std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]";
- if (Message.endswith(CheckNameInMessage))
- Message = Message.substr(0, Message.size() - CheckNameInMessage.size());
-
- auto TidyMessage = Loc.isValid()
- ? tooling::DiagnosticMessage(Message, *SM, Loc)
- : tooling::DiagnosticMessage(Message);
- if (Level == DiagnosticsEngine::Note) {
- Error.Notes.push_back(TidyMessage);
- return;
- }
- assert(Error.Message.Message.empty() && "Overwriting a diagnostic message");
- Error.Message = TidyMessage;
- }
-
- void emitDiagnosticLoc(SourceLocation Loc, PresumedLoc PLoc,
- DiagnosticsEngine::Level Level,
- ArrayRef<CharSourceRange> Ranges,
- const SourceManager &SM) override {}
-
- void emitCodeContext(SourceLocation Loc, DiagnosticsEngine::Level Level,
- SmallVectorImpl<CharSourceRange> &Ranges,
- ArrayRef<FixItHint> Hints,
- const SourceManager &SM) override {
- assert(Loc.isValid());
- for (const auto &FixIt : Hints) {
- CharSourceRange Range = FixIt.RemoveRange;
- assert(Range.getBegin().isValid() && Range.getEnd().isValid() &&
- "Invalid range in the fix-it hint.");
- assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() &&
- "Only file locations supported in fix-it hints.");
-
- tooling::Replacement Replacement(SM, Range, FixIt.CodeToInsert);
- llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement);
- // FIXME: better error handling (at least, don't let other replacements be
- // applied).
- if (Err) {
- llvm::errs() << "Fix conflicts with existing fix! "
- << llvm::toString(std::move(Err)) << "\n";
- assert(false && "Fix conflicts with existing fix!");
- }
- }
- }
-
- void emitIncludeLocation(SourceLocation Loc, PresumedLoc PLoc,
- const SourceManager &SM) override {}
-
- void emitImportLocation(SourceLocation Loc, PresumedLoc PLoc,
- StringRef ModuleName,
- const SourceManager &SM) override {}
-
- void emitBuildingModuleLocation(SourceLocation Loc, PresumedLoc PLoc,
- StringRef ModuleName,
- const SourceManager &SM) override {}
-
- void endDiagnostic(DiagOrStoredDiag D,
- DiagnosticsEngine::Level Level) override {
- assert(!Error.Message.Message.empty() && "Message has not been set");
- }
-
-private:
- ClangTidyError &Error;
-};
-} // end anonymous namespace
-
-ClangTidyError::ClangTidyError(StringRef CheckName,
- ClangTidyError::Level DiagLevel,
- StringRef BuildDirectory, bool IsWarningAsError)
- : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory),
- IsWarningAsError(IsWarningAsError) {}
-
-// Returns true if GlobList starts with the negative indicator ('-'), removes it
-// from the GlobList.
-static bool ConsumeNegativeIndicator(StringRef &GlobList) {
- GlobList = GlobList.trim(' ');
- if (GlobList.startswith("-")) {
- GlobList = GlobList.substr(1);
- return true;
- }
- return false;
-}
-// Converts first glob from the comma-separated list of globs to Regex and
-// removes it and the trailing comma from the GlobList.
-static llvm::Regex ConsumeGlob(StringRef &GlobList) {
- StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(','));
- StringRef Glob = UntrimmedGlob.trim(' ');
- GlobList = GlobList.substr(UntrimmedGlob.size() + 1);
- SmallString<128> RegexText("^");
- StringRef MetaChars("()^$|*+?.[]\\{}");
- for (char C : Glob) {
- if (C == '*')
- RegexText.push_back('.');
- else if (MetaChars.find(C) != StringRef::npos)
- RegexText.push_back('\\');
- RegexText.push_back(C);
- }
- RegexText.push_back('$');
- return llvm::Regex(RegexText);
-}
-
-GlobList::GlobList(StringRef Globs)
- : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)),
- NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {}
-
-bool GlobList::contains(StringRef S, bool Contains) {
- if (Regex.match(S))
- Contains = Positive;
-
- if (NextGlob)
- Contains = NextGlob->contains(S, Contains);
- return Contains;
-}
-
-ClangTidyContext::ClangTidyContext(
- std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
- : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)),
- Profile(nullptr) {
- // Before the first translation unit we can get errors related to command-line
- // parsing, use empty string for the file name in this case.
- setCurrentFile("");
-}
-
-DiagnosticBuilder ClangTidyContext::diag(
- StringRef CheckName, SourceLocation Loc, StringRef Description,
- DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) {
- assert(Loc.isValid());
- unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID(
- Level, (Description + " [" + CheckName + "]").str());
- CheckNamesByDiagnosticID.try_emplace(ID, CheckName);
- return DiagEngine->Report(Loc, ID);
-}
-
-void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) {
- DiagEngine = Engine;
-}
-
-void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) {
- DiagEngine->setSourceManager(SourceMgr);
-}
-
-void ClangTidyContext::setCurrentFile(StringRef File) {
- CurrentFile = File;
- CurrentOptions = getOptionsForFile(CurrentFile);
- CheckFilter.reset(new GlobList(*getOptions().Checks));
- WarningAsErrorFilter.reset(new GlobList(*getOptions().WarningsAsErrors));
-}
-
-void ClangTidyContext::setASTContext(ASTContext *Context) {
- DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context);
- LangOpts = Context->getLangOpts();
-}
-
-const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const {
- return OptionsProvider->getGlobalOptions();
-}
-
-const ClangTidyOptions &ClangTidyContext::getOptions() const {
- return CurrentOptions;
-}
-
-ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const {
- // Merge options on top of getDefaults() as a safeguard against options with
- // unset values.
- return ClangTidyOptions::getDefaults().mergeWith(
- OptionsProvider->getOptions(File));
-}
-
-void ClangTidyContext::setCheckProfileData(ProfileData *P) { Profile = P; }
-
-GlobList &ClangTidyContext::getChecksFilter() {
- assert(CheckFilter != nullptr);
- return *CheckFilter;
-}
-
-GlobList &ClangTidyContext::getWarningAsErrorFilter() {
- assert(WarningAsErrorFilter != nullptr);
- return *WarningAsErrorFilter;
-}
-
-/// \brief Store a \c ClangTidyError.
-void ClangTidyContext::storeError(const ClangTidyError &Error) {
- Errors.push_back(Error);
-}
-
-StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const {
- llvm::DenseMap<unsigned, std::string>::const_iterator I =
- CheckNamesByDiagnosticID.find(DiagnosticID);
- if (I != CheckNamesByDiagnosticID.end())
- return I->second;
- return "";
-}
-
-ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx)
- : Context(Ctx), LastErrorRelatesToUserCode(false),
- LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) {
- IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
- Diags.reset(new DiagnosticsEngine(
- IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, this,
- /*ShouldOwnClient=*/false));
- Context.setDiagnosticsEngine(Diags.get());
-}
-
-void ClangTidyDiagnosticConsumer::finalizeLastError() {
- if (!Errors.empty()) {
- ClangTidyError &Error = Errors.back();
- if (!Context.getChecksFilter().contains(Error.DiagnosticName) &&
- Error.DiagLevel != ClangTidyError::Error) {
- ++Context.Stats.ErrorsIgnoredCheckFilter;
- Errors.pop_back();
- } else if (!LastErrorRelatesToUserCode) {
- ++Context.Stats.ErrorsIgnoredNonUserCode;
- Errors.pop_back();
- } else if (!LastErrorPassesLineFilter) {
- ++Context.Stats.ErrorsIgnoredLineFilter;
- Errors.pop_back();
- } else {
- ++Context.Stats.ErrorsDisplayed;
- }
- }
- LastErrorRelatesToUserCode = false;
- LastErrorPassesLineFilter = false;
-}
-
-static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc) {
- bool Invalid;
- const char *CharacterData = SM.getCharacterData(Loc, &Invalid);
- if (Invalid)
- return false;
-
- // Check if there's a NOLINT on this line.
- const char *P = CharacterData;
- while (*P != '\0' && *P != '\r' && *P != '\n')
- ++P;
- StringRef RestOfLine(CharacterData, P - CharacterData + 1);
- // FIXME: Handle /\bNOLINT\b(\([^)]*\))?/ as cpplint.py does.
- if (RestOfLine.find("NOLINT") != StringRef::npos)
- return true;
-
- // Check if there's a NOLINTNEXTLINE on the previous line.
- const char *BufBegin =
- SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid);
- if (Invalid || P == BufBegin)
- return false;
-
- // Scan backwards over the current line.
- P = CharacterData;
- while (P != BufBegin && *P != '\n')
- --P;
-
- // If we reached the begin of the file there is no line before it.
- if (P == BufBegin)
- return false;
-
- // Skip over the newline.
- --P;
- const char *LineEnd = P;
-
- // Now we're on the previous line. Skip to the beginning of it.
- while (P != BufBegin && *P != '\n')
- --P;
-
- RestOfLine = StringRef(P, LineEnd - P + 1);
- if (RestOfLine.find("NOLINTNEXTLINE") != StringRef::npos)
- return true;
-
- return false;
-}
-
-static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM,
- SourceLocation Loc) {
- while (true) {
- if (LineIsMarkedWithNOLINT(SM, Loc))
- return true;
- if (!Loc.isMacroID())
- return false;
- Loc = SM.getImmediateExpansionRange(Loc).first;
- }
- return false;
-}
-
-void ClangTidyDiagnosticConsumer::HandleDiagnostic(
- DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
- if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
- return;
-
- if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error &&
- DiagLevel != DiagnosticsEngine::Fatal &&
- LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(),
- Info.getLocation())) {
- ++Context.Stats.ErrorsIgnoredNOLINT;
- // Ignored a warning, should ignore related notes as well
- LastErrorWasIgnored = true;
- return;
- }
-
- LastErrorWasIgnored = false;
- // Count warnings/errors.
- DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
-
- if (DiagLevel == DiagnosticsEngine::Note) {
- assert(!Errors.empty() &&
- "A diagnostic note can only be appended to a message.");
- } else {
- finalizeLastError();
- StringRef WarningOption =
- Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(
- Info.getID());
- std::string CheckName = !WarningOption.empty()
- ? ("clang-diagnostic-" + WarningOption).str()
- : Context.getCheckName(Info.getID()).str();
-
- if (CheckName.empty()) {
- // This is a compiler diagnostic without a warning option. Assign check
- // name based on its level.
- switch (DiagLevel) {
- case DiagnosticsEngine::Error:
- case DiagnosticsEngine::Fatal:
- CheckName = "clang-diagnostic-error";
- break;
- case DiagnosticsEngine::Warning:
- CheckName = "clang-diagnostic-warning";
- break;
- default:
- CheckName = "clang-diagnostic-unknown";
- break;
- }
- }
-
- ClangTidyError::Level Level = ClangTidyError::Warning;
- if (DiagLevel == DiagnosticsEngine::Error ||
- DiagLevel == DiagnosticsEngine::Fatal) {
- // Force reporting of Clang errors regardless of filters and non-user
- // code.
- Level = ClangTidyError::Error;
- LastErrorRelatesToUserCode = true;
- LastErrorPassesLineFilter = true;
- }
- bool IsWarningAsError =
- DiagLevel == DiagnosticsEngine::Warning &&
- Context.getWarningAsErrorFilter().contains(CheckName);
- Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(),
- IsWarningAsError);
- }
-
- ClangTidyDiagnosticRenderer Converter(
- Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(),
- Errors.back());
- SmallString<100> Message;
- Info.FormatDiagnostic(Message);
- SourceManager *Sources = nullptr;
- if (Info.hasSourceManager())
- Sources = &Info.getSourceManager();
- Converter.emitDiagnostic(Info.getLocation(), DiagLevel, Message,
- Info.getRanges(), Info.getFixItHints(), Sources);
-
- checkFilters(Info.getLocation());
-}
-
-bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
- unsigned LineNumber) const {
- if (Context.getGlobalOptions().LineFilter.empty())
- return true;
- for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
- if (FileName.endswith(Filter.Name)) {
- if (Filter.LineRanges.empty())
- return true;
- for (const FileFilter::LineRange &Range : Filter.LineRanges) {
- if (Range.first <= LineNumber && LineNumber <= Range.second)
- return true;
- }
- return false;
- }
- }
- return false;
-}
-
-void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) {
- // Invalid location may mean a diagnostic in a command line, don't skip these.
- if (!Location.isValid()) {
- LastErrorRelatesToUserCode = true;
- LastErrorPassesLineFilter = true;
- return;
- }
-
- const SourceManager &Sources = Diags->getSourceManager();
- if (!*Context.getOptions().SystemHeaders &&
- Sources.isInSystemHeader(Location))
- return;
-
- // FIXME: We start with a conservative approach here, but the actual type of
- // location needed depends on the check (in particular, where this check wants
- // to apply fixes).
- FileID FID = Sources.getDecomposedExpansionLoc(Location).first;
- const FileEntry *File = Sources.getFileEntryForID(FID);
-
- // -DMACRO definitions on the command line have locations in a virtual buffer
- // that doesn't have a FileEntry. Don't skip these as well.
- if (!File) {
- LastErrorRelatesToUserCode = true;
- LastErrorPassesLineFilter = true;
- return;
- }
-
- StringRef FileName(File->getName());
- LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
- Sources.isInMainFile(Location) ||
- getHeaderFilter()->match(FileName);
-
- unsigned LineNumber = Sources.getExpansionLineNumber(Location);
- LastErrorPassesLineFilter =
- LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
-}
-
-llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
- if (!HeaderFilter)
- HeaderFilter.reset(
- new llvm::Regex(*Context.getOptions().HeaderFilterRegex));
- return HeaderFilter.get();
-}
-
-void ClangTidyDiagnosticConsumer::removeIncompatibleErrors(
- SmallVectorImpl<ClangTidyError> &Errors) const {
- // Each error is modelled as the set of intervals in which it applies
- // replacements. To detect overlapping replacements, we use a sweep line
- // algorithm over these sets of intervals.
- // An event here consists of the opening or closing of an interval. During the
- // process, we maintain a counter with the amount of open intervals. If we
- // find an endpoint of an interval and this counter is different from 0, it
- // means that this interval overlaps with another one, so we set it as
- // inapplicable.
- struct Event {
- // An event can be either the begin or the end of an interval.
- enum EventType {
- ET_Begin = 1,
- ET_End = -1,
- };
-
- Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId,
- unsigned ErrorSize)
- : Type(Type), ErrorId(ErrorId) {
- // The events are going to be sorted by their position. In case of draw:
- //
- // * If an interval ends at the same position at which other interval
- // begins, this is not an overlapping, so we want to remove the ending
- // interval before adding the starting one: end events have higher
- // priority than begin events.
- //
- // * If we have several begin points at the same position, we will mark as
- // inapplicable the ones that we process later, so the first one has to
- // be the one with the latest end point, because this one will contain
- // all the other intervals. For the same reason, if we have several end
- // points in the same position, the last one has to be the one with the
- // earliest begin point. In both cases, we sort non-increasingly by the
- // position of the complementary.
- //
- // * In case of two equal intervals, the one whose error is bigger can
- // potentially contain the other one, so we want to process its begin
- // points before and its end points later.
- //
- // * Finally, if we have two equal intervals whose errors have the same
- // size, none of them will be strictly contained inside the other.
- // Sorting by ErrorId will guarantee that the begin point of the first
- // one will be processed before, disallowing the second one, and the
- // end point of the first one will also be processed before,
- // disallowing the first one.
- if (Type == ET_Begin)
- Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId);
- else
- Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId);
- }
-
- bool operator<(const Event &Other) const {
- return Priority < Other.Priority;
- }
-
- // Determines if this event is the begin or the end of an interval.
- EventType Type;
- // The index of the error to which the interval that generated this event
- // belongs.
- unsigned ErrorId;
- // The events will be sorted based on this field.
- std::tuple<unsigned, EventType, int, int, unsigned> Priority;
- };
-
- // Compute error sizes.
- std::vector<int> Sizes;
- for (const auto &Error : Errors) {
- int Size = 0;
- for (const auto &FileAndReplaces : Error.Fix) {
- for (const auto &Replace : FileAndReplaces.second)
- Size += Replace.getLength();
- }
- Sizes.push_back(Size);
- }
-
- // Build events from error intervals.
- std::map<std::string, std::vector<Event>> FileEvents;
- for (unsigned I = 0; I < Errors.size(); ++I) {
- for (const auto &FileAndReplace : Errors[I].Fix) {
- for (const auto &Replace : FileAndReplace.second) {
- unsigned Begin = Replace.getOffset();
- unsigned End = Begin + Replace.getLength();
- const std::string &FilePath = Replace.getFilePath();
- // FIXME: Handle empty intervals, such as those from insertions.
- if (Begin == End)
- continue;
- auto &Events = FileEvents[FilePath];
- Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]);
- Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]);
- }
- }
- }
-
- std::vector<bool> Apply(Errors.size(), true);
- for (auto &FileAndEvents : FileEvents) {
- std::vector<Event> &Events = FileAndEvents.second;
- // Sweep.
- std::sort(Events.begin(), Events.end());
- int OpenIntervals = 0;
- for (const auto &Event : Events) {
- if (Event.Type == Event::ET_End)
- --OpenIntervals;
- // This has to be checked after removing the interval from the count if it
- // is an end event, or before adding it if it is a begin event.
- if (OpenIntervals != 0)
- Apply[Event.ErrorId] = false;
- if (Event.Type == Event::ET_Begin)
- ++OpenIntervals;
- }
- assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match");
- }
-
- for (unsigned I = 0; I < Errors.size(); ++I) {
- if (!Apply[I]) {
- Errors[I].Fix.clear();
- Errors[I].Notes.emplace_back(
- "this fix will not be applied because it overlaps with another fix");
- }
- }
-}
-
-namespace {
-struct LessClangTidyError {
- bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
- const tooling::DiagnosticMessage &M1 = LHS.Message;
- const tooling::DiagnosticMessage &M2 = RHS.Message;
-
- return std::tie(M1.FilePath, M1.FileOffset, M1.Message) <
- std::tie(M2.FilePath, M2.FileOffset, M2.Message);
- }
-};
-struct EqualClangTidyError {
- bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const {
- LessClangTidyError Less;
- return !Less(LHS, RHS) && !Less(RHS, LHS);
- }
-};
-} // end anonymous namespace
-
-// Flushes the internal diagnostics buffer to the ClangTidyContext.
-void ClangTidyDiagnosticConsumer::finish() {
- finalizeLastError();
-
- std::sort(Errors.begin(), Errors.end(), LessClangTidyError());
- Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()),
- Errors.end());
- removeIncompatibleErrors(Errors);
-
- for (const ClangTidyError &Error : Errors)
- Context.storeError(Error);
- Errors.clear();
-}
+//===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== // +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext +/// and ClangTidyError classes. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyOptions.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Frontend/DiagnosticRenderer.h" +#include "llvm/ADT/SmallString.h" +#include <tuple> +#include <vector> +using namespace clang; +using namespace tidy; + +namespace { +class ClangTidyDiagnosticRenderer : public DiagnosticRenderer { +public: + ClangTidyDiagnosticRenderer(const LangOptions &LangOpts, + DiagnosticOptions *DiagOpts, + ClangTidyError &Error) + : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {} + +protected: + void emitDiagnosticMessage(SourceLocation Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, StringRef Message, + ArrayRef<CharSourceRange> Ranges, + const SourceManager *SM, + DiagOrStoredDiag Info) override { + // Remove check name from the message. + // FIXME: Remove this once there's a better way to pass check names than + // appending the check name to the message in ClangTidyContext::diag and + // using getCustomDiagID. + std::string CheckNameInMessage = " [" + Error.DiagnosticName + "]"; + if (Message.endswith(CheckNameInMessage)) + Message = Message.substr(0, Message.size() - CheckNameInMessage.size()); + + auto TidyMessage = Loc.isValid() + ? tooling::DiagnosticMessage(Message, *SM, Loc) + : tooling::DiagnosticMessage(Message); + if (Level == DiagnosticsEngine::Note) { + Error.Notes.push_back(TidyMessage); + return; + } + assert(Error.Message.Message.empty() && "Overwriting a diagnostic message"); + Error.Message = TidyMessage; + } + + void emitDiagnosticLoc(SourceLocation Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, + ArrayRef<CharSourceRange> Ranges, + const SourceManager &SM) override {} + + void emitCodeContext(SourceLocation Loc, DiagnosticsEngine::Level Level, + SmallVectorImpl<CharSourceRange> &Ranges, + ArrayRef<FixItHint> Hints, + const SourceManager &SM) override { + assert(Loc.isValid()); + for (const auto &FixIt : Hints) { + CharSourceRange Range = FixIt.RemoveRange; + assert(Range.getBegin().isValid() && Range.getEnd().isValid() && + "Invalid range in the fix-it hint."); + assert(Range.getBegin().isFileID() && Range.getEnd().isFileID() && + "Only file locations supported in fix-it hints."); + + tooling::Replacement Replacement(SM, Range, FixIt.CodeToInsert); + llvm::Error Err = Error.Fix[Replacement.getFilePath()].add(Replacement); + // FIXME: better error handling (at least, don't let other replacements be + // applied). + if (Err) { + llvm::errs() << "Fix conflicts with existing fix! " + << llvm::toString(std::move(Err)) << "\n"; + assert(false && "Fix conflicts with existing fix!"); + } + } + } + + void emitIncludeLocation(SourceLocation Loc, PresumedLoc PLoc, + const SourceManager &SM) override {} + + void emitImportLocation(SourceLocation Loc, PresumedLoc PLoc, + StringRef ModuleName, + const SourceManager &SM) override {} + + void emitBuildingModuleLocation(SourceLocation Loc, PresumedLoc PLoc, + StringRef ModuleName, + const SourceManager &SM) override {} + + void endDiagnostic(DiagOrStoredDiag D, + DiagnosticsEngine::Level Level) override { + assert(!Error.Message.Message.empty() && "Message has not been set"); + } + +private: + ClangTidyError &Error; +}; +} // end anonymous namespace + +ClangTidyError::ClangTidyError(StringRef CheckName, + ClangTidyError::Level DiagLevel, + StringRef BuildDirectory, bool IsWarningAsError) + : tooling::Diagnostic(CheckName, DiagLevel, BuildDirectory), + IsWarningAsError(IsWarningAsError) {} + +// Returns true if GlobList starts with the negative indicator ('-'), removes it +// from the GlobList. +static bool ConsumeNegativeIndicator(StringRef &GlobList) { + GlobList = GlobList.trim(' '); + if (GlobList.startswith("-")) { + GlobList = GlobList.substr(1); + return true; + } + return false; +} +// Converts first glob from the comma-separated list of globs to Regex and +// removes it and the trailing comma from the GlobList. +static llvm::Regex ConsumeGlob(StringRef &GlobList) { + StringRef UntrimmedGlob = GlobList.substr(0, GlobList.find(',')); + StringRef Glob = UntrimmedGlob.trim(' '); + GlobList = GlobList.substr(UntrimmedGlob.size() + 1); + SmallString<128> RegexText("^"); + StringRef MetaChars("()^$|*+?.[]\\{}"); + for (char C : Glob) { + if (C == '*') + RegexText.push_back('.'); + else if (MetaChars.find(C) != StringRef::npos) + RegexText.push_back('\\'); + RegexText.push_back(C); + } + RegexText.push_back('$'); + return llvm::Regex(RegexText); +} + +GlobList::GlobList(StringRef Globs) + : Positive(!ConsumeNegativeIndicator(Globs)), Regex(ConsumeGlob(Globs)), + NextGlob(Globs.empty() ? nullptr : new GlobList(Globs)) {} + +bool GlobList::contains(StringRef S, bool Contains) { + if (Regex.match(S)) + Contains = Positive; + + if (NextGlob) + Contains = NextGlob->contains(S, Contains); + return Contains; +} + +ClangTidyContext::ClangTidyContext( + std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider) + : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)), + Profile(nullptr) { + // Before the first translation unit we can get errors related to command-line + // parsing, use empty string for the file name in this case. + setCurrentFile(""); +} + +DiagnosticBuilder ClangTidyContext::diag( + StringRef CheckName, SourceLocation Loc, StringRef Description, + DiagnosticIDs::Level Level /* = DiagnosticIDs::Warning*/) { + assert(Loc.isValid()); + unsigned ID = DiagEngine->getDiagnosticIDs()->getCustomDiagID( + Level, (Description + " [" + CheckName + "]").str()); + CheckNamesByDiagnosticID.try_emplace(ID, CheckName); + return DiagEngine->Report(Loc, ID); +} + +void ClangTidyContext::setDiagnosticsEngine(DiagnosticsEngine *Engine) { + DiagEngine = Engine; +} + +void ClangTidyContext::setSourceManager(SourceManager *SourceMgr) { + DiagEngine->setSourceManager(SourceMgr); +} + +void ClangTidyContext::setCurrentFile(StringRef File) { + CurrentFile = File; + CurrentOptions = getOptionsForFile(CurrentFile); + CheckFilter.reset(new GlobList(*getOptions().Checks)); + WarningAsErrorFilter.reset(new GlobList(*getOptions().WarningsAsErrors)); +} + +void ClangTidyContext::setASTContext(ASTContext *Context) { + DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context); + LangOpts = Context->getLangOpts(); +} + +const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const { + return OptionsProvider->getGlobalOptions(); +} + +const ClangTidyOptions &ClangTidyContext::getOptions() const { + return CurrentOptions; +} + +ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const { + // Merge options on top of getDefaults() as a safeguard against options with + // unset values. + return ClangTidyOptions::getDefaults().mergeWith( + OptionsProvider->getOptions(File)); +} + +void ClangTidyContext::setCheckProfileData(ProfileData *P) { Profile = P; } + +GlobList &ClangTidyContext::getChecksFilter() { + assert(CheckFilter != nullptr); + return *CheckFilter; +} + +GlobList &ClangTidyContext::getWarningAsErrorFilter() { + assert(WarningAsErrorFilter != nullptr); + return *WarningAsErrorFilter; +} + +/// \brief Store a \c ClangTidyError. +void ClangTidyContext::storeError(const ClangTidyError &Error) { + Errors.push_back(Error); +} + +StringRef ClangTidyContext::getCheckName(unsigned DiagnosticID) const { + llvm::DenseMap<unsigned, std::string>::const_iterator I = + CheckNamesByDiagnosticID.find(DiagnosticID); + if (I != CheckNamesByDiagnosticID.end()) + return I->second; + return ""; +} + +ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx) + : Context(Ctx), LastErrorRelatesToUserCode(false), + LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) { + IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); + Diags.reset(new DiagnosticsEngine( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, this, + /*ShouldOwnClient=*/false)); + Context.setDiagnosticsEngine(Diags.get()); +} + +void ClangTidyDiagnosticConsumer::finalizeLastError() { + if (!Errors.empty()) { + ClangTidyError &Error = Errors.back(); + if (!Context.getChecksFilter().contains(Error.DiagnosticName) && + Error.DiagLevel != ClangTidyError::Error) { + ++Context.Stats.ErrorsIgnoredCheckFilter; + Errors.pop_back(); + } else if (!LastErrorRelatesToUserCode) { + ++Context.Stats.ErrorsIgnoredNonUserCode; + Errors.pop_back(); + } else if (!LastErrorPassesLineFilter) { + ++Context.Stats.ErrorsIgnoredLineFilter; + Errors.pop_back(); + } else { + ++Context.Stats.ErrorsDisplayed; + } + } + LastErrorRelatesToUserCode = false; + LastErrorPassesLineFilter = false; +} + +static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc) { + bool Invalid; + const char *CharacterData = SM.getCharacterData(Loc, &Invalid); + if (Invalid) + return false; + + // Check if there's a NOLINT on this line. + const char *P = CharacterData; + while (*P != '\0' && *P != '\r' && *P != '\n') + ++P; + StringRef RestOfLine(CharacterData, P - CharacterData + 1); + // FIXME: Handle /\bNOLINT\b(\([^)]*\))?/ as cpplint.py does. + if (RestOfLine.find("NOLINT") != StringRef::npos) + return true; + + // Check if there's a NOLINTNEXTLINE on the previous line. + const char *BufBegin = + SM.getCharacterData(SM.getLocForStartOfFile(SM.getFileID(Loc)), &Invalid); + if (Invalid || P == BufBegin) + return false; + + // Scan backwards over the current line. + P = CharacterData; + while (P != BufBegin && *P != '\n') + --P; + + // If we reached the begin of the file there is no line before it. + if (P == BufBegin) + return false; + + // Skip over the newline. + --P; + const char *LineEnd = P; + + // Now we're on the previous line. Skip to the beginning of it. + while (P != BufBegin && *P != '\n') + --P; + + RestOfLine = StringRef(P, LineEnd - P + 1); + if (RestOfLine.find("NOLINTNEXTLINE") != StringRef::npos) + return true; + + return false; +} + +static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, + SourceLocation Loc) { + while (true) { + if (LineIsMarkedWithNOLINT(SM, Loc)) + return true; + if (!Loc.isMacroID()) + return false; + Loc = SM.getImmediateExpansionRange(Loc).first; + } + return false; +} + +void ClangTidyDiagnosticConsumer::HandleDiagnostic( + DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { + if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note) + return; + + if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error && + DiagLevel != DiagnosticsEngine::Fatal && + LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(), + Info.getLocation())) { + ++Context.Stats.ErrorsIgnoredNOLINT; + // Ignored a warning, should ignore related notes as well + LastErrorWasIgnored = true; + return; + } + + LastErrorWasIgnored = false; + // Count warnings/errors. + DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); + + if (DiagLevel == DiagnosticsEngine::Note) { + assert(!Errors.empty() && + "A diagnostic note can only be appended to a message."); + } else { + finalizeLastError(); + StringRef WarningOption = + Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag( + Info.getID()); + std::string CheckName = !WarningOption.empty() + ? ("clang-diagnostic-" + WarningOption).str() + : Context.getCheckName(Info.getID()).str(); + + if (CheckName.empty()) { + // This is a compiler diagnostic without a warning option. Assign check + // name based on its level. + switch (DiagLevel) { + case DiagnosticsEngine::Error: + case DiagnosticsEngine::Fatal: + CheckName = "clang-diagnostic-error"; + break; + case DiagnosticsEngine::Warning: + CheckName = "clang-diagnostic-warning"; + break; + default: + CheckName = "clang-diagnostic-unknown"; + break; + } + } + + ClangTidyError::Level Level = ClangTidyError::Warning; + if (DiagLevel == DiagnosticsEngine::Error || + DiagLevel == DiagnosticsEngine::Fatal) { + // Force reporting of Clang errors regardless of filters and non-user + // code. + Level = ClangTidyError::Error; + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + } + bool IsWarningAsError = + DiagLevel == DiagnosticsEngine::Warning && + Context.getWarningAsErrorFilter().contains(CheckName); + Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(), + IsWarningAsError); + } + + ClangTidyDiagnosticRenderer Converter( + Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(), + Errors.back()); + SmallString<100> Message; + Info.FormatDiagnostic(Message); + SourceManager *Sources = nullptr; + if (Info.hasSourceManager()) + Sources = &Info.getSourceManager(); + Converter.emitDiagnostic(Info.getLocation(), DiagLevel, Message, + Info.getRanges(), Info.getFixItHints(), Sources); + + checkFilters(Info.getLocation()); +} + +bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName, + unsigned LineNumber) const { + if (Context.getGlobalOptions().LineFilter.empty()) + return true; + for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) { + if (FileName.endswith(Filter.Name)) { + if (Filter.LineRanges.empty()) + return true; + for (const FileFilter::LineRange &Range : Filter.LineRanges) { + if (Range.first <= LineNumber && LineNumber <= Range.second) + return true; + } + return false; + } + } + return false; +} + +void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location) { + // Invalid location may mean a diagnostic in a command line, don't skip these. + if (!Location.isValid()) { + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + return; + } + + const SourceManager &Sources = Diags->getSourceManager(); + if (!*Context.getOptions().SystemHeaders && + Sources.isInSystemHeader(Location)) + return; + + // FIXME: We start with a conservative approach here, but the actual type of + // location needed depends on the check (in particular, where this check wants + // to apply fixes). + FileID FID = Sources.getDecomposedExpansionLoc(Location).first; + const FileEntry *File = Sources.getFileEntryForID(FID); + + // -DMACRO definitions on the command line have locations in a virtual buffer + // that doesn't have a FileEntry. Don't skip these as well. + if (!File) { + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + return; + } + + StringRef FileName(File->getName()); + LastErrorRelatesToUserCode = LastErrorRelatesToUserCode || + Sources.isInMainFile(Location) || + getHeaderFilter()->match(FileName); + + unsigned LineNumber = Sources.getExpansionLineNumber(Location); + LastErrorPassesLineFilter = + LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber); +} + +llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() { + if (!HeaderFilter) + HeaderFilter.reset( + new llvm::Regex(*Context.getOptions().HeaderFilterRegex)); + return HeaderFilter.get(); +} + +void ClangTidyDiagnosticConsumer::removeIncompatibleErrors( + SmallVectorImpl<ClangTidyError> &Errors) const { + // Each error is modelled as the set of intervals in which it applies + // replacements. To detect overlapping replacements, we use a sweep line + // algorithm over these sets of intervals. + // An event here consists of the opening or closing of an interval. During the + // process, we maintain a counter with the amount of open intervals. If we + // find an endpoint of an interval and this counter is different from 0, it + // means that this interval overlaps with another one, so we set it as + // inapplicable. + struct Event { + // An event can be either the begin or the end of an interval. + enum EventType { + ET_Begin = 1, + ET_End = -1, + }; + + Event(unsigned Begin, unsigned End, EventType Type, unsigned ErrorId, + unsigned ErrorSize) + : Type(Type), ErrorId(ErrorId) { + // The events are going to be sorted by their position. In case of draw: + // + // * If an interval ends at the same position at which other interval + // begins, this is not an overlapping, so we want to remove the ending + // interval before adding the starting one: end events have higher + // priority than begin events. + // + // * If we have several begin points at the same position, we will mark as + // inapplicable the ones that we process later, so the first one has to + // be the one with the latest end point, because this one will contain + // all the other intervals. For the same reason, if we have several end + // points in the same position, the last one has to be the one with the + // earliest begin point. In both cases, we sort non-increasingly by the + // position of the complementary. + // + // * In case of two equal intervals, the one whose error is bigger can + // potentially contain the other one, so we want to process its begin + // points before and its end points later. + // + // * Finally, if we have two equal intervals whose errors have the same + // size, none of them will be strictly contained inside the other. + // Sorting by ErrorId will guarantee that the begin point of the first + // one will be processed before, disallowing the second one, and the + // end point of the first one will also be processed before, + // disallowing the first one. + if (Type == ET_Begin) + Priority = std::make_tuple(Begin, Type, -End, -ErrorSize, ErrorId); + else + Priority = std::make_tuple(End, Type, -Begin, ErrorSize, ErrorId); + } + + bool operator<(const Event &Other) const { + return Priority < Other.Priority; + } + + // Determines if this event is the begin or the end of an interval. + EventType Type; + // The index of the error to which the interval that generated this event + // belongs. + unsigned ErrorId; + // The events will be sorted based on this field. + std::tuple<unsigned, EventType, int, int, unsigned> Priority; + }; + + // Compute error sizes. + std::vector<int> Sizes; + for (const auto &Error : Errors) { + int Size = 0; + for (const auto &FileAndReplaces : Error.Fix) { + for (const auto &Replace : FileAndReplaces.second) + Size += Replace.getLength(); + } + Sizes.push_back(Size); + } + + // Build events from error intervals. + std::map<std::string, std::vector<Event>> FileEvents; + for (unsigned I = 0; I < Errors.size(); ++I) { + for (const auto &FileAndReplace : Errors[I].Fix) { + for (const auto &Replace : FileAndReplace.second) { + unsigned Begin = Replace.getOffset(); + unsigned End = Begin + Replace.getLength(); + const std::string &FilePath = Replace.getFilePath(); + // FIXME: Handle empty intervals, such as those from insertions. + if (Begin == End) + continue; + auto &Events = FileEvents[FilePath]; + Events.emplace_back(Begin, End, Event::ET_Begin, I, Sizes[I]); + Events.emplace_back(Begin, End, Event::ET_End, I, Sizes[I]); + } + } + } + + std::vector<bool> Apply(Errors.size(), true); + for (auto &FileAndEvents : FileEvents) { + std::vector<Event> &Events = FileAndEvents.second; + // Sweep. + std::sort(Events.begin(), Events.end()); + int OpenIntervals = 0; + for (const auto &Event : Events) { + if (Event.Type == Event::ET_End) + --OpenIntervals; + // This has to be checked after removing the interval from the count if it + // is an end event, or before adding it if it is a begin event. + if (OpenIntervals != 0) + Apply[Event.ErrorId] = false; + if (Event.Type == Event::ET_Begin) + ++OpenIntervals; + } + assert(OpenIntervals == 0 && "Amount of begin/end points doesn't match"); + } + + for (unsigned I = 0; I < Errors.size(); ++I) { + if (!Apply[I]) { + Errors[I].Fix.clear(); + Errors[I].Notes.emplace_back( + "this fix will not be applied because it overlaps with another fix"); + } + } +} + +namespace { +struct LessClangTidyError { + bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { + const tooling::DiagnosticMessage &M1 = LHS.Message; + const tooling::DiagnosticMessage &M2 = RHS.Message; + + return std::tie(M1.FilePath, M1.FileOffset, M1.Message) < + std::tie(M2.FilePath, M2.FileOffset, M2.Message); + } +}; +struct EqualClangTidyError { + bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { + LessClangTidyError Less; + return !Less(LHS, RHS) && !Less(RHS, LHS); + } +}; +} // end anonymous namespace + +// Flushes the internal diagnostics buffer to the ClangTidyContext. +void ClangTidyDiagnosticConsumer::finish() { + finalizeLastError(); + + std::sort(Errors.begin(), Errors.end(), LessClangTidyError()); + Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()), + Errors.end()); + removeIncompatibleErrors(Errors); + + for (const ClangTidyError &Error : Errors) + Context.storeError(Error); + Errors.clear(); +} diff --git a/clang-tools-extra/clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp b/clang-tools-extra/clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp index 9b9a21b9ef3..2efa53597b5 100644 --- a/clang-tools-extra/clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp @@ -1,146 +1,146 @@ -//===--- ForwardingReferenceOverloadCheck.cpp - clang-tidy-----------------===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "ForwardingReferenceOverloadCheck.h"
-#include "clang/AST/ASTContext.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
-#include <algorithm>
-
-using namespace clang::ast_matchers;
-
-namespace clang {
-namespace tidy {
-namespace misc {
-
-namespace {
-// Check if the given type is related to std::enable_if.
-AST_MATCHER(QualType, isEnableIf) {
- auto CheckTemplate = [](const TemplateSpecializationType *Spec) {
- if (!Spec || !Spec->getTemplateName().getAsTemplateDecl()) {
- return false;
- }
- const NamedDecl *TypeDecl =
- Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl();
- return TypeDecl->isInStdNamespace() &&
- (TypeDecl->getName().equals("enable_if") ||
- TypeDecl->getName().equals("enable_if_t"));
- };
- const Type *BaseType = Node.getTypePtr();
- // Case: pointer or reference to enable_if.
- while (BaseType->isPointerType() || BaseType->isReferenceType()) {
- BaseType = BaseType->getPointeeType().getTypePtr();
- }
- // Case: type parameter dependent (enable_if<is_integral<T>>).
- if (const auto *Dependent = BaseType->getAs<DependentNameType>()) {
- BaseType = Dependent->getQualifier()->getAsType();
- }
- if (CheckTemplate(BaseType->getAs<TemplateSpecializationType>())) {
- return true; // Case: enable_if_t< >.
- } else if (const auto *Elaborated = BaseType->getAs<ElaboratedType>()) {
- if (const auto *Qualifier = Elaborated->getQualifier()->getAsType()) {
- if (CheckTemplate(Qualifier->getAs<TemplateSpecializationType>())) {
- return true; // Case: enable_if< >::type.
- }
- }
- }
- return false;
-}
-AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument,
- clang::ast_matchers::internal::Matcher<QualType>, TypeMatcher) {
- return Node.hasDefaultArgument() &&
- TypeMatcher.matches(Node.getDefaultArgument(), Finder, Builder);
-}
-} // namespace
-
-void ForwardingReferenceOverloadCheck::registerMatchers(MatchFinder *Finder) {
- // Forwarding references require C++11 or later.
- if (!getLangOpts().CPlusPlus11)
- return;
-
- auto ForwardingRefParm =
- parmVarDecl(
- hasType(qualType(rValueReferenceType(),
- references(templateTypeParmType(hasDeclaration(
- templateTypeParmDecl().bind("type-parm-decl")))),
- unless(references(isConstQualified())))))
- .bind("parm-var");
-
- DeclarationMatcher findOverload =
- cxxConstructorDecl(
- hasParameter(0, ForwardingRefParm),
- unless(hasAnyParameter(
- // No warning: enable_if as constructor parameter.
- parmVarDecl(hasType(isEnableIf())))),
- unless(hasParent(functionTemplateDecl(has(templateTypeParmDecl(
- // No warning: enable_if as type parameter.
- hasDefaultArgument(isEnableIf())))))))
- .bind("ctor");
- Finder->addMatcher(findOverload, this);
-}
-
-void ForwardingReferenceOverloadCheck::check(
- const MatchFinder::MatchResult &Result) {
- const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var");
- const auto *TypeParmDecl =
- Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl");
-
- // Get the FunctionDecl and FunctionTemplateDecl containing the function
- // parameter.
- const auto *FuncForParam = dyn_cast<FunctionDecl>(ParmVar->getDeclContext());
- if (!FuncForParam)
- return;
- const FunctionTemplateDecl *FuncTemplate =
- FuncForParam->getDescribedFunctionTemplate();
- if (!FuncTemplate)
- return;
-
- // Check that the template type parameter belongs to the same function
- // template as the function parameter of that type. (This implies that type
- // deduction will happen on the type.)
- const TemplateParameterList *Params = FuncTemplate->getTemplateParameters();
- if (std::find(Params->begin(), Params->end(), TypeParmDecl) == Params->end())
- return;
-
- // Every parameter after the first must have a default value.
- const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
- for (auto Iter = Ctor->param_begin() + 1; Iter != Ctor->param_end(); ++Iter) {
- if (!(*Iter)->hasDefaultArg())
- return;
- }
- bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false,
- DisabledMove = false;
- for (const auto *OtherCtor : Ctor->getParent()->ctors()) {
- if (OtherCtor->isCopyOrMoveConstructor()) {
- if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private)
- (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true;
- else
- (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true;
- }
- }
- bool Copy = (!EnabledMove && !DisabledMove && !DisabledCopy) || EnabledCopy;
- bool Move = !DisabledMove || EnabledMove;
- if (!Copy && !Move)
- return;
- diag(Ctor->getLocation(),
- "constructor accepting a forwarding reference can "
- "hide the %select{copy|move|copy and move}0 constructor%s1")
- << (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move;
- for (const auto *OtherCtor : Ctor->getParent()->ctors()) {
- if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() &&
- OtherCtor->getAccess() != AS_private) {
- diag(OtherCtor->getLocation(),
- "%select{copy|move}0 constructor declared here", DiagnosticIDs::Note)
- << OtherCtor->isMoveConstructor();
- }
- }
-}
-
-} // namespace misc
-} // namespace tidy
-} // namespace clang
+//===--- ForwardingReferenceOverloadCheck.cpp - clang-tidy-----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ForwardingReferenceOverloadCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include <algorithm> + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +// Check if the given type is related to std::enable_if. +AST_MATCHER(QualType, isEnableIf) { + auto CheckTemplate = [](const TemplateSpecializationType *Spec) { + if (!Spec || !Spec->getTemplateName().getAsTemplateDecl()) { + return false; + } + const NamedDecl *TypeDecl = + Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl(); + return TypeDecl->isInStdNamespace() && + (TypeDecl->getName().equals("enable_if") || + TypeDecl->getName().equals("enable_if_t")); + }; + const Type *BaseType = Node.getTypePtr(); + // Case: pointer or reference to enable_if. + while (BaseType->isPointerType() || BaseType->isReferenceType()) { + BaseType = BaseType->getPointeeType().getTypePtr(); + } + // Case: type parameter dependent (enable_if<is_integral<T>>). + if (const auto *Dependent = BaseType->getAs<DependentNameType>()) { + BaseType = Dependent->getQualifier()->getAsType(); + } + if (CheckTemplate(BaseType->getAs<TemplateSpecializationType>())) { + return true; // Case: enable_if_t< >. + } else if (const auto *Elaborated = BaseType->getAs<ElaboratedType>()) { + if (const auto *Qualifier = Elaborated->getQualifier()->getAsType()) { + if (CheckTemplate(Qualifier->getAs<TemplateSpecializationType>())) { + return true; // Case: enable_if< >::type. + } + } + } + return false; +} +AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument, + clang::ast_matchers::internal::Matcher<QualType>, TypeMatcher) { + return Node.hasDefaultArgument() && + TypeMatcher.matches(Node.getDefaultArgument(), Finder, Builder); +} +} // namespace + +void ForwardingReferenceOverloadCheck::registerMatchers(MatchFinder *Finder) { + // Forwarding references require C++11 or later. + if (!getLangOpts().CPlusPlus11) + return; + + auto ForwardingRefParm = + parmVarDecl( + hasType(qualType(rValueReferenceType(), + references(templateTypeParmType(hasDeclaration( + templateTypeParmDecl().bind("type-parm-decl")))), + unless(references(isConstQualified()))))) + .bind("parm-var"); + + DeclarationMatcher findOverload = + cxxConstructorDecl( + hasParameter(0, ForwardingRefParm), + unless(hasAnyParameter( + // No warning: enable_if as constructor parameter. + parmVarDecl(hasType(isEnableIf())))), + unless(hasParent(functionTemplateDecl(has(templateTypeParmDecl( + // No warning: enable_if as type parameter. + hasDefaultArgument(isEnableIf()))))))) + .bind("ctor"); + Finder->addMatcher(findOverload, this); +} + +void ForwardingReferenceOverloadCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var"); + const auto *TypeParmDecl = + Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl"); + + // Get the FunctionDecl and FunctionTemplateDecl containing the function + // parameter. + const auto *FuncForParam = dyn_cast<FunctionDecl>(ParmVar->getDeclContext()); + if (!FuncForParam) + return; + const FunctionTemplateDecl *FuncTemplate = + FuncForParam->getDescribedFunctionTemplate(); + if (!FuncTemplate) + return; + + // Check that the template type parameter belongs to the same function + // template as the function parameter of that type. (This implies that type + // deduction will happen on the type.) + const TemplateParameterList *Params = FuncTemplate->getTemplateParameters(); + if (std::find(Params->begin(), Params->end(), TypeParmDecl) == Params->end()) + return; + + // Every parameter after the first must have a default value. + const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); + for (auto Iter = Ctor->param_begin() + 1; Iter != Ctor->param_end(); ++Iter) { + if (!(*Iter)->hasDefaultArg()) + return; + } + bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false, + DisabledMove = false; + for (const auto *OtherCtor : Ctor->getParent()->ctors()) { + if (OtherCtor->isCopyOrMoveConstructor()) { + if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private) + (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true; + else + (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true; + } + } + bool Copy = (!EnabledMove && !DisabledMove && !DisabledCopy) || EnabledCopy; + bool Move = !DisabledMove || EnabledMove; + if (!Copy && !Move) + return; + diag(Ctor->getLocation(), + "constructor accepting a forwarding reference can " + "hide the %select{copy|move|copy and move}0 constructor%s1") + << (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move; + for (const auto *OtherCtor : Ctor->getParent()->ctors()) { + if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() && + OtherCtor->getAccess() != AS_private) { + diag(OtherCtor->getLocation(), + "%select{copy|move}0 constructor declared here", DiagnosticIDs::Note) + << OtherCtor->isMoveConstructor(); + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/test/clang-tidy/misc-non-copyable-objects.c b/clang-tools-extra/test/clang-tidy/misc-non-copyable-objects.c index 98e52b95f69..ac6198e8b37 100644 --- a/clang-tools-extra/test/clang-tidy/misc-non-copyable-objects.c +++ b/clang-tools-extra/test/clang-tidy/misc-non-copyable-objects.c @@ -1,43 +1,43 @@ -// RUN: %check_clang_tidy %s misc-non-copyable-objects %t
-
-typedef struct FILE {} FILE;
-typedef struct pthread_cond_t {} pthread_cond_t;
-typedef int pthread_mutex_t;
-
-// CHECK-MESSAGES: :[[@LINE+1]]:13: warning: 'f' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'? [misc-non-copyable-objects]
-void g(FILE f);
-// CHECK-MESSAGES: :[[@LINE+1]]:24: warning: 'm' declared as type 'pthread_mutex_t', which is unsafe to copy; did you mean 'pthread_mutex_t *'?
-void h(pthread_mutex_t m);
-// CHECK-MESSAGES: :[[@LINE+1]]:23: warning: 'c' declared as type 'pthread_cond_t', which is unsafe to copy; did you mean 'pthread_cond_t *'?
-void i(pthread_cond_t c);
-
-struct S {
- pthread_cond_t c; // ok
- // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: 'f' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'?
- FILE f;
-};
-
-void func(FILE *f) {
- // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: 'f1' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'?
- FILE f1; // match
- // CHECK-MESSAGES: :[[@LINE+2]]:8: warning: 'f2' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'?
- // CHECK-MESSAGES: :[[@LINE+1]]:13: warning: expression has opaque data structure type 'FILE'; type should only be used as a pointer and not dereferenced
- FILE f2 = *f;
- // CHECK-MESSAGES: :[[@LINE+1]]:15: warning: 'f3' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'?
- struct FILE f3;
- // CHECK-MESSAGES: :[[@LINE+1]]:16: warning: expression has opaque data structure type 'FILE'; type should only be used as a pointer and not dereferenced
- (void)sizeof(*f);
- (void)sizeof(FILE);
- // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: expression has opaque data structure type 'FILE'; type should only be used as a pointer and not dereferenced
- g(*f);
-
- pthread_mutex_t m; // ok
- h(m); // ok
-
- pthread_cond_t c; // ok
- i(c); // ok
-
- pthread_mutex_t *m1 = &m; // ok
- // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: expression has opaque data structure type 'pthread_mutex_t'; type should only be used as a pointer and not dereferenced
- h(*m1);
-}
\ No newline at end of file +// RUN: %check_clang_tidy %s misc-non-copyable-objects %t + +typedef struct FILE {} FILE; +typedef struct pthread_cond_t {} pthread_cond_t; +typedef int pthread_mutex_t; + +// CHECK-MESSAGES: :[[@LINE+1]]:13: warning: 'f' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'? [misc-non-copyable-objects] +void g(FILE f); +// CHECK-MESSAGES: :[[@LINE+1]]:24: warning: 'm' declared as type 'pthread_mutex_t', which is unsafe to copy; did you mean 'pthread_mutex_t *'? +void h(pthread_mutex_t m); +// CHECK-MESSAGES: :[[@LINE+1]]:23: warning: 'c' declared as type 'pthread_cond_t', which is unsafe to copy; did you mean 'pthread_cond_t *'? +void i(pthread_cond_t c); + +struct S { + pthread_cond_t c; // ok + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: 'f' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'? + FILE f; +}; + +void func(FILE *f) { + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: 'f1' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'? + FILE f1; // match + // CHECK-MESSAGES: :[[@LINE+2]]:8: warning: 'f2' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'? + // CHECK-MESSAGES: :[[@LINE+1]]:13: warning: expression has opaque data structure type 'FILE'; type should only be used as a pointer and not dereferenced + FILE f2 = *f; + // CHECK-MESSAGES: :[[@LINE+1]]:15: warning: 'f3' declared as type 'FILE', which is unsafe to copy; did you mean 'FILE *'? + struct FILE f3; + // CHECK-MESSAGES: :[[@LINE+1]]:16: warning: expression has opaque data structure type 'FILE'; type should only be used as a pointer and not dereferenced + (void)sizeof(*f); + (void)sizeof(FILE); + // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: expression has opaque data structure type 'FILE'; type should only be used as a pointer and not dereferenced + g(*f); + + pthread_mutex_t m; // ok + h(m); // ok + + pthread_cond_t c; // ok + i(c); // ok + + pthread_mutex_t *m1 = &m; // ok + // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: expression has opaque data structure type 'pthread_mutex_t'; type should only be used as a pointer and not dereferenced + h(*m1); +} diff --git a/clang-tools-extra/test/clang-tidy/misc-static-assert.c b/clang-tools-extra/test/clang-tidy/misc-static-assert.c index 0fca94583cf..e3e8304977c 100644 --- a/clang-tools-extra/test/clang-tidy/misc-static-assert.c +++ b/clang-tools-extra/test/clang-tidy/misc-static-assert.c @@ -1,27 +1,27 @@ -// RUN: %check_clang_tidy %s misc-static-assert %t -- -- -std=c11
-// RUN: clang-tidy %s -checks=-*,misc-static-assert -- -std=c99 | count 0
-
-void abort() {}
-#ifdef NDEBUG
-#define assert(x) 1
-#else
-#define assert(x) \
- if (!(x)) \
- abort()
-#endif
-
-void f(void) {
- int x = 1;
- assert(x == 0);
- // CHECK-FIXES: {{^ }}assert(x == 0);
-
- #define static_assert(x, msg) _Static_assert(x, msg)
- assert(11 == 5 + 6);
- // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be
- // CHECK-FIXES: {{^ }}static_assert(11 == 5 + 6, "");
- #undef static_assert
-
- assert(10 == 5 + 5);
- // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be
- // CHECK-FIXES: {{^ }}static_assert(10 == 5 + 5, "");
-}
+// RUN: %check_clang_tidy %s misc-static-assert %t -- -- -std=c11 +// RUN: clang-tidy %s -checks=-*,misc-static-assert -- -std=c99 | count 0 + +void abort() {} +#ifdef NDEBUG +#define assert(x) 1 +#else +#define assert(x) \ + if (!(x)) \ + abort() +#endif + +void f(void) { + int x = 1; + assert(x == 0); + // CHECK-FIXES: {{^ }}assert(x == 0); + + #define static_assert(x, msg) _Static_assert(x, msg) + assert(11 == 5 + 6); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be + // CHECK-FIXES: {{^ }}static_assert(11 == 5 + 6, ""); + #undef static_assert + + assert(10 == 5 + 5); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: found assert() that could be + // CHECK-FIXES: {{^ }}static_assert(10 == 5 + 5, ""); +} diff --git a/clang-tools-extra/test/clang-tidy/misc-undelegated-constructor-cxx98.cpp b/clang-tools-extra/test/clang-tidy/misc-undelegated-constructor-cxx98.cpp index e614f22bf5c..78b53bb7840 100644 --- a/clang-tools-extra/test/clang-tidy/misc-undelegated-constructor-cxx98.cpp +++ b/clang-tools-extra/test/clang-tidy/misc-undelegated-constructor-cxx98.cpp @@ -1,23 +1,23 @@ -// RUN: clang-tidy %s -checks=-*,misc-undelegated-constructor -- -std=c++98 | count 0
-
-// Note: this test expects no diagnostics, but FileCheck cannot handle that,
-// hence the use of | count 0.
-
-struct Ctor;
-Ctor foo();
-
-struct Ctor {
- Ctor();
- Ctor(int);
- Ctor(int, int);
- Ctor(Ctor *i) {
- Ctor();
- Ctor(0);
- Ctor(1, 2);
- foo();
- }
-};
-
-Ctor::Ctor() {
- Ctor(1);
-}
+// RUN: clang-tidy %s -checks=-*,misc-undelegated-constructor -- -std=c++98 | count 0 + +// Note: this test expects no diagnostics, but FileCheck cannot handle that, +// hence the use of | count 0. + +struct Ctor; +Ctor foo(); + +struct Ctor { + Ctor(); + Ctor(int); + Ctor(int, int); + Ctor(Ctor *i) { + Ctor(); + Ctor(0); + Ctor(1, 2); + foo(); + } +}; + +Ctor::Ctor() { + Ctor(1); +} |