[clang-tools-extra] Draft: Create a new check to look for mis-use in calls that take iterators (PR #99917)
Nathan James via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 22 12:02:31 PDT 2024
https://github.com/njames93 created https://github.com/llvm/llvm-project/pull/99917
Looks for various patterns of functions that take arguments calling begin/end or similar for common mistakes.
Still needs a bit of work, but open to suggestions of how this check could be improved
>From a8eb65dd46e9fff572963cc980bf5ea582085ef1 Mon Sep 17 00:00:00 2001
From: Nathan James <n.james93 at hotmail.co.uk>
Date: Mon, 22 Jul 2024 20:00:53 +0100
Subject: [PATCH] Create a new check to look for mis-use in calls that take
iterators
Looks for various patterns of functions that take arguments calling
begin/end or similar for common mistakes
---
.../bugprone/BugproneTidyModule.cpp | 3 +
.../clang-tidy/bugprone/CMakeLists.txt | 1 +
.../bugprone/IncorrectIteratorsCheck.cpp | 362 ++++++++++++++++++
.../bugprone/IncorrectIteratorsCheck.h | 44 +++
clang-tools-extra/docs/ReleaseNotes.rst | 6 +
.../checks/bugprone/incorrect-iterators.rst | 111 ++++++
.../docs/clang-tidy/checks/list.rst | 10 +-
.../checkers/bugprone/incorrect-iterators.cpp | 156 ++++++++
8 files changed, 688 insertions(+), 5 deletions(-)
create mode 100644 clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/incorrect-iterators.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/incorrect-iterators.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 689eb92a3d8d1..cea040b81878a 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -33,6 +33,7 @@
#include "InaccurateEraseCheck.h"
#include "IncDecInConditionsCheck.h"
#include "IncorrectEnableIfCheck.h"
+#include "IncorrectIteratorsCheck.h"
#include "IncorrectRoundingsCheck.h"
#include "InfiniteLoopCheck.h"
#include "IntegerDivisionCheck.h"
@@ -139,6 +140,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-inaccurate-erase");
CheckFactories.registerCheck<IncorrectEnableIfCheck>(
"bugprone-incorrect-enable-if");
+ CheckFactories.registerCheck<IncorrectIteratorsCheck>(
+ "bugprone-incorrect-iterators");
CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
"bugprone-return-const-ref-from-parameter");
CheckFactories.registerCheck<SwitchMissingDefaultCaseCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index cb0d8ae98bac5..8425dbed0505a 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -26,6 +26,7 @@ add_clang_library(clangTidyBugproneModule
ImplicitWideningOfMultiplicationResultCheck.cpp
InaccurateEraseCheck.cpp
IncorrectEnableIfCheck.cpp
+ IncorrectIteratorsCheck.cpp
ReturnConstRefFromParameterCheck.cpp
SuspiciousStringviewDataUsageCheck.cpp
SwitchMissingDefaultCaseCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.cpp
new file mode 100644
index 0000000000000..56429b9100146
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.cpp
@@ -0,0 +1,362 @@
+//===--- IncorrectIteratorsCheck.cpp - clang-tidy -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "IncorrectIteratorsCheck.h"
+#include "../utils/Matchers.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/iterator_range.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+namespace {
+using SVU = llvm::SmallVector<unsigned, 3>;
+/// Checks to see if a function a
+AST_MATCHER_P(FunctionDecl, areParametersSameTemplateType, SVU, Indexes) {
+ const auto *TemplateDecl = Node.getPrimaryTemplate();
+ if (!TemplateDecl)
+ return false;
+ const auto *FuncDecl = TemplateDecl->getTemplatedDecl();
+ if (!FuncDecl)
+ return false;
+ assert(!Indexes.empty());
+ const auto *FirstParam = FuncDecl->getParamDecl(Indexes.front());
+ if (!FirstParam)
+ return false;
+ auto Type = FirstParam->getOriginalType();
+ for (auto Item : llvm::drop_begin(Indexes)) {
+ const auto *Param = FuncDecl->getParamDecl(Item);
+ if (!Param)
+ return false;
+ if (Param->getOriginalType() != Type)
+ return false;
+ }
+ return true;
+}
+struct NameMatchers {
+ ArrayRef<StringRef> FreeNames;
+ ArrayRef<StringRef> MethodNames;
+};
+
+struct NamePairs {
+ NameMatchers BeginNames;
+ NameMatchers EndNames;
+};
+
+struct FullState {
+ NamePairs Forward;
+ NamePairs Reversed;
+ NamePairs Combined;
+ NameMatchers All;
+ ArrayRef<StringRef> MakeReverseIterator;
+};
+
+} // namespace
+
+static constexpr char FirstRangeArg[] = "FirstRangeArg";
+static constexpr char FirstRangeArgExpr[] = "FirstRangeArgExpr";
+static constexpr char ReverseBeginBind[] = "ReverseBeginBind";
+static constexpr char ReverseEndBind[] = "ReverseEndBind";
+static constexpr char SecondRangeArg[] = "SecondRangeArg";
+static constexpr char SecondRangeArgExpr[] = "SecondRangeArgExpr";
+static constexpr char ArgMismatchBegin[] = "ArgMismatchBegin";
+static constexpr char ArgMismatchEnd[] = "ArgMismatchEnd";
+static constexpr char ArgMismatchExpr[] = "ArgMismatchExpr";
+static constexpr char ArgMismatchRevBind[] = "ArgMismatchRevBind";
+static constexpr char Callee[] = "Callee";
+static constexpr char Internal[] = "Internal";
+static constexpr char InternalOther[] = "InternalOther";
+static constexpr char InternalArgument[] = "InternalArgument";
+
+static auto
+makeExprMatcher(ast_matchers::internal::Matcher<Expr> ArgumentMatcher,
+ const NameMatchers &Names, ArrayRef<StringRef> MakeReverse,
+ const NameMatchers &RevNames, StringRef RevBind) {
+ return expr(anyOf(
+ cxxMemberCallExpr(argumentCountIs(0),
+ callee(cxxMethodDecl(hasAnyName(Names.MethodNames))),
+ on(ArgumentMatcher)),
+ callExpr(argumentCountIs(1),
+ hasDeclaration(functionDecl(hasAnyName(Names.FreeNames))),
+ hasArgument(0, ArgumentMatcher)),
+ callExpr(
+ callee(functionDecl(hasAnyName(MakeReverse))), argumentCountIs(1),
+ hasArgument(
+ 0, expr(anyOf(cxxMemberCallExpr(argumentCountIs(0),
+ callee(cxxMethodDecl(hasAnyName(
+ RevNames.MethodNames))),
+ on(ArgumentMatcher)),
+ callExpr(argumentCountIs(1),
+ hasDeclaration(functionDecl(
+ hasAnyName(RevNames.FreeNames))),
+ hasArgument(0, ArgumentMatcher))))))
+ .bind(RevBind)));
+}
+
+// Detects a range function where the argument for the begin call differs from
+// the end call std::find(I.begin(), J.end())
+static auto makeRangeArgMismatch(unsigned BeginExpected, unsigned EndExpected,
+ NamePairs Forward, NamePairs Reverse,
+ ArrayRef<StringRef> MakeReverse) {
+ std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
+
+ for (const auto &[Primary, Backwards] :
+ {std::pair{Forward, Reverse}, std::pair{Reverse, Forward}}) {
+ Matchers.push_back(callExpr(
+ hasArgument(BeginExpected,
+ makeExprMatcher(expr().bind(FirstRangeArg),
+ Primary.BeginNames, MakeReverse,
+ Backwards.EndNames, ReverseBeginBind)
+ .bind(FirstRangeArgExpr)),
+ hasArgument(EndExpected,
+ makeExprMatcher(
+ expr(unless(matchers::isStatementIdenticalToBoundNode(
+ FirstRangeArg)))
+ .bind(SecondRangeArg),
+ Primary.EndNames, MakeReverse, Backwards.BeginNames,
+ ReverseEndBind)
+ .bind(SecondRangeArgExpr))));
+ }
+
+ return callExpr(
+ argumentCountAtLeast(std::max(BeginExpected, EndExpected) + 1),
+ ast_matchers::internal::DynTypedMatcher::constructVariadic(
+ ast_matchers::internal::DynTypedMatcher::VO_AnyOf,
+ ASTNodeKind::getFromNodeKind<CallExpr>(), std::move(Matchers))
+ .convertTo<CallExpr>());
+}
+
+// Detects a range function where we expect a call to begin but get a call to
+// end or vice versa std::find(X.end(), X.end()) // Here we would warn on the
+// first argument that it should be begin
+static auto makeUnexpectedBeginEndMatcher(unsigned Index, StringRef BindTo,
+ NameMatchers Names,
+ ArrayRef<StringRef> MakeReverse,
+ const NameMatchers &RevNames) {
+ return callExpr(hasArgument(Index, makeExprMatcher(expr().bind(BindTo), Names,
+ MakeReverse, RevNames,
+ ArgMismatchRevBind)
+ .bind(ArgMismatchExpr)));
+}
+
+static auto makeUnexpectedBeginEndPair(unsigned BeginExpected,
+ unsigned EndExpected,
+ NamePairs BeginEndPairs,
+ ArrayRef<StringRef> MakeReverse) {
+ // return expr(unless(anything()));
+ return eachOf(makeUnexpectedBeginEndMatcher(
+ BeginExpected, ArgMismatchBegin, BeginEndPairs.EndNames,
+ MakeReverse, BeginEndPairs.BeginNames),
+ makeUnexpectedBeginEndMatcher(
+ EndExpected, ArgMismatchEnd, BeginEndPairs.BeginNames,
+ MakeReverse, BeginEndPairs.EndNames));
+}
+
+/// The full matcher for functions that take a range with 2 arguments
+static auto makePairRangeMatcher(std::vector<StringRef> FuncNames,
+ unsigned BeginExpected, unsigned EndExpected,
+ const FullState &State) {
+
+ return callExpr(callee(functionDecl(hasAnyName(std::move(FuncNames)),
+ areParametersSameTemplateType(
+ {BeginExpected, EndExpected}))),
+ anyOf(makeRangeArgMismatch(BeginExpected, EndExpected,
+ State.Forward, State.Reversed,
+ State.MakeReverseIterator),
+ makeUnexpectedBeginEndPair(BeginExpected, EndExpected,
+ State.Combined,
+ State.MakeReverseIterator)))
+ .bind(Callee);
+}
+
+static auto makeContainerInternalMatcher(std::vector<StringRef> ClassNames,
+ std::vector<StringRef> ClassMethods,
+ unsigned InternalExpected,
+ const FullState &State) {
+ return cxxMemberCallExpr(
+ thisPointerType(cxxRecordDecl(hasAnyName(std::move(ClassNames)))),
+ callee(cxxMethodDecl(hasAnyName(std::move(ClassMethods)))),
+ on(expr().bind(Internal)),
+ hasArgument(
+ InternalExpected,
+ makeExprMatcher(
+ expr(unless(matchers::isStatementIdenticalToBoundNode(Internal)))
+ .bind(InternalOther),
+ State.All, State.MakeReverseIterator, State.All,
+ ArgMismatchRevBind)
+ .bind(InternalArgument))).bind(Callee);
+}
+
+/// Full matcher for class methods that take a range with 2 arguments
+static auto makeContainerPairRangeMatcher(std::vector<StringRef> ClassNames,
+ std::vector<StringRef> ClassMethods,
+ unsigned BeginExpected,
+ unsigned EndExpected,
+ const FullState &State) {
+ return cxxMemberCallExpr(
+ thisPointerType(cxxRecordDecl(hasAnyName(std::move(ClassNames)))),
+ callee(cxxMethodDecl(
+ hasAnyName(std::move(ClassMethods)),
+ areParametersSameTemplateType({BeginExpected, EndExpected}))),
+ anyOf(makeRangeArgMismatch(BeginExpected, EndExpected,
+ State.Forward, State.Reversed,
+ State.MakeReverseIterator),
+ makeUnexpectedBeginEndPair(BeginExpected, EndExpected,
+ State.Combined,
+ State.MakeReverseIterator)))
+ .bind(Callee);
+}
+
+void IncorrectIteratorsCheck::registerMatchers(MatchFinder *Finder) {
+ NamePairs Forward{NameMatchers{BeginFree, BeginMethod},
+ NameMatchers{EndFree, EndMethod}};
+ NamePairs Reverse{NameMatchers{RBeginFree, RBeginMethod},
+ NameMatchers{REndFree, REndMethod}};
+ llvm::SmallVector<StringRef, 8> CombinedFreeBegin{
+ llvm::iterator_range{llvm::concat<StringRef>(BeginFree, RBeginFree)}};
+ llvm::SmallVector<StringRef, 8> CombinedFreeEnd{
+ llvm::iterator_range{llvm::concat<StringRef>(EndFree, REndFree)}};
+ llvm::SmallVector<StringRef, 8> CombinedMethodBegin{
+ llvm::iterator_range{llvm::concat<StringRef>(BeginMethod, RBeginMethod)}};
+ llvm::SmallVector<StringRef, 8> CombinedMethodEnd{
+ llvm::iterator_range{llvm::concat<StringRef>(EndMethod, REndMethod)}};
+ llvm::SmallVector<StringRef, 16> AllFree{llvm::iterator_range{
+ llvm::concat<StringRef>(CombinedFreeBegin, CombinedFreeEnd)}};
+ llvm::SmallVector<StringRef, 16> AllMethod{llvm::iterator_range{
+ llvm::concat<StringRef>(CombinedMethodBegin, CombinedMethodEnd)}};
+ NamePairs Combined{NameMatchers{CombinedFreeBegin, CombinedMethodBegin},
+ NameMatchers{CombinedFreeEnd, CombinedMethodEnd}};
+ FullState State{
+ Forward, Reverse, Combined, {AllFree, AllMethod}, MakeReverseIterator};
+ Finder->addMatcher(makePairRangeMatcher({"::std::find"}, 1, 2, State), this);
+ Finder->addMatcher(makePairRangeMatcher({"::std::find"}, 0, 1, State), this);
+ Finder->addMatcher(
+ makeContainerPairRangeMatcher({"::std::vector", "::std::list"},
+ {"insert"}, 1, 2, State),
+ this);
+ Finder->addMatcher(
+ makeContainerInternalMatcher({"::std::vector", "::std::list"},
+ {"insert", "erase", "emplace"}, 0, State),
+ this);
+}
+
+void IncorrectIteratorsCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs<CallExpr>(Callee);
+ if (const auto *BeginMismatch =
+ Result.Nodes.getNodeAs<Expr>(ArgMismatchBegin)) {
+ diag(BeginMismatch->getBeginLoc(),
+ "'end' iterator supplied where a 'begin' iterator is expected")
+ << Result.Nodes.getNodeAs<Expr>(ArgMismatchExpr)->getSourceRange();
+ if (const auto *Rev =
+ Result.Nodes.getNodeAs<CallExpr>(ArgMismatchRevBind)) {
+ diag(Rev->getBeginLoc(), "%0 changes 'begin' into a 'end' iterator",
+ DiagnosticIDs::Note)
+ << Rev->getSourceRange() << Rev->getDirectCallee();
+ }
+ } else if (const auto *EndMismatch =
+ Result.Nodes.getNodeAs<Expr>(ArgMismatchEnd)) {
+ diag(EndMismatch->getBeginLoc(),
+ "'begin' iterator supplied where an 'end' iterator is expected")
+ << Result.Nodes.getNodeAs<Expr>(ArgMismatchExpr)->getSourceRange();
+ if (const auto *Rev =
+ Result.Nodes.getNodeAs<CallExpr>(ArgMismatchRevBind)) {
+ diag(Rev->getBeginLoc(), "%0 changes 'end' into a 'begin' iterator",
+ DiagnosticIDs::Note)
+ << Rev->getSourceRange() << Rev->getDirectCallee();
+ }
+ } else if (const auto *InternalArg =
+ Result.Nodes.getNodeAs<Expr>(InternalArgument)) {
+ diag(InternalArg->getBeginLoc(),
+ "%0 called with an iterator for a different container")
+ << Call->getDirectCallee();
+ const auto *Object = Result.Nodes.getNodeAs<Expr>(Internal);
+ diag(Object->getBeginLoc(), "container is specified here",
+ DiagnosticIDs::Note)
+ << Object->getSourceRange();
+ const auto *Other = Result.Nodes.getNodeAs<Expr>(InternalOther);
+ diag(Other->getBeginLoc(), "different container provided here",
+ DiagnosticIDs::Note)
+ << Other->getSourceRange();
+ } else {
+ const auto *Range1 = Result.Nodes.getNodeAs<Expr>(FirstRangeArg);
+ const auto *Range2 = Result.Nodes.getNodeAs<Expr>(SecondRangeArg);
+ const auto *FullRange1 = Result.Nodes.getNodeAs<Expr>(FirstRangeArgExpr);
+ const auto *FullRange2 = Result.Nodes.getNodeAs<Expr>(SecondRangeArgExpr);
+ assert(Range1 && Range2 && FullRange1 && FullRange2 && "Unexpected match");
+ diag(Call->getBeginLoc(), "mismatched ranges pass to function")
+ << FullRange1->getSourceRange() << FullRange2->getSourceRange();
+ diag(Range1->getBeginLoc(), "first range passed here to begin",
+ DiagnosticIDs::Note)
+ << FullRange1->getSourceRange();
+ diag(Range2->getBeginLoc(), "different range passed here to end",
+ DiagnosticIDs::Note)
+ << FullRange2->getSourceRange();
+ }
+}
+
+IncorrectIteratorsCheck::IncorrectIteratorsCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ BeginFree(utils::options::parseStringList(
+ Options.get("BeginFree", "::std::begin;::std::cbegin"))),
+ EndFree(utils::options::parseStringList(
+ Options.get("EndFree", "::std::end;::std::cend"))),
+ BeginMethod(utils::options::parseStringList(
+ Options.get("BeginMethod", "begin;cbegin"))),
+ EndMethod(utils::options::parseStringList(
+ Options.get("EndMethod", "end;cend"))),
+ RBeginFree(utils::options::parseStringList(
+ Options.get("RBeginFree", "::std::rbegin;::std::crbegin"))),
+ REndFree(utils::options::parseStringList(
+ Options.get("REndFree", "::std::rend;::std::crend"))),
+ RBeginMethod(utils::options::parseStringList(
+ Options.get("RBeginMethod", "rbegin;crbegin"))),
+ REndMethod(utils::options::parseStringList(
+ Options.get("REndMethod", "rend;crend"))),
+ MakeReverseIterator(utils::options::parseStringList(
+ Options.get("MakeReverseIterator", "::std::make_reverse_iterator"))) {
+}
+
+void IncorrectIteratorsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "BeginFree",
+ utils::options::serializeStringList(BeginFree));
+ Options.store(Opts, "EndFree", utils::options::serializeStringList(EndFree));
+ Options.store(Opts, "BeginMethod",
+ utils::options::serializeStringList(BeginMethod));
+ Options.store(Opts, "EndMethod",
+ utils::options::serializeStringList(EndMethod));
+ Options.store(Opts, "RBeginFree",
+ utils::options::serializeStringList(RBeginFree));
+ Options.store(Opts, "REndFree",
+ utils::options::serializeStringList(REndFree));
+ Options.store(Opts, "RBeginMethod",
+ utils::options::serializeStringList(RBeginMethod));
+ Options.store(Opts, "REndMethod",
+ utils::options::serializeStringList(REndMethod));
+ Options.store(Opts, "MakeReverseIterator",
+ utils::options::serializeStringList(MakeReverseIterator));
+}
+std::optional<TraversalKind>
+IncorrectIteratorsCheck::getCheckTraversalKind() const {
+ return TK_IgnoreUnlessSpelledInSource;
+}
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.h b/clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.h
new file mode 100644
index 0000000000000..9f33b61c5a981
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.h
@@ -0,0 +1,44 @@
+//===--- IncorrectIteratorsCheck.h - clang-tidy -----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTITERATORSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTITERATORSCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace clang::tidy::bugprone {
+
+/// Detects calls to iterator algorithms where they are called with potentially
+/// invalid arguments.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/incorrect-iterators.html
+class IncorrectIteratorsCheck : public ClangTidyCheck {
+public:
+ IncorrectIteratorsCheck(StringRef Name, ClangTidyContext *Context);
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Options) override;
+ std::optional<TraversalKind> getCheckTraversalKind() const override;
+
+private:
+ std::vector<StringRef> BeginFree;
+ std::vector<StringRef> EndFree;
+ std::vector<StringRef> BeginMethod;
+ std::vector<StringRef> EndMethod;
+ std::vector<StringRef> RBeginFree;
+ std::vector<StringRef> REndFree;
+ std::vector<StringRef> RBeginMethod;
+ std::vector<StringRef> REndMethod;
+ std::vector<StringRef> MakeReverseIterator;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTITERATORSCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index a23483e6df6d2..4e15dc22e3a95 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -147,6 +147,12 @@ New checks
Detects error-prone Curiously Recurring Template Pattern usage, when the CRTP
can be constructed outside itself and the derived class.
+- New :doc:`bugprone-incorrect-iterators
+ <clang-tidy/checks/bugprone/incorrect-iterators>` check.
+
+ Detects calls to iterator algorithms where they are called with potentially
+ invalid arguments.
+
- New :doc:`bugprone-pointer-arithmetic-on-polymorphic-object
<clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/incorrect-iterators.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/incorrect-iterators.rst
new file mode 100644
index 0000000000000..f6f6540d2bdd1
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/incorrect-iterators.rst
@@ -0,0 +1,111 @@
+.. title:: clang-tidy - bugprone-incorrect-iterators
+
+bugprone-incorrect-iterators
+============================
+
+Detects calls to iterator algorithms where they are called with potentially
+invalid arguments.
+
+Different ranges
+================
+
+Looks for calls where the range for the `begin` argument is different to the
+`end` argument
+
+.. code-block:: c++
+
+ std::find(a.begin(), b.end(), 0);
+ std::find(std::begin(a), std::end(b));
+
+Mismatched Begin/end
+====================
+
+Looks for calls where the `begin` parameter is passed an `end` argument or vice
+versa
+
+.. code-block:: c++
+
+ std::find(a.begin(), a.begin(), 0); // Second argument should be a.end().
+ std::find(a.end(), a.end(), 0); // First argument should be a.begin().
+
+Reverse Iteration
+=================
+
+The check understands `rbegin` and `rend` and ensures they are in the correct places.
+
+.. code-block:: c++
+
+ std::find(a.rbegin(), a.rend(), 0); // OK.
+ std::find(a.rend(), a.rbegin(), 0); // Arguments are swapped.
+
+Manually creating a reverse iterator using the ``std::make_reverse_iterator`` is
+also supported, In this case the check looks for calls to `end` for the `begin`
+parameter and vice versa. The name of functions for creating reverse iterator
+can be configured with the option :option:`MakeReverseIterator`
+
+.. code-block:: c++
+
+ std::find(std::make_reverse_iterator(a.begin()),
+ std::make_reverse_iterator(a.end()), 0); // Arguments are swapped
+ std::find(std::make_reverse_iterator(a.end()),
+ std::make_reverse_iterator(a.begin()), 0); // OK
+ // Understands this spaghetti looking code is actually doing the correct thing
+ std::find(a.rbegin(), std::make_reverse_iterator(a.begin()), 0);
+
+Container Methods
+=================
+
+Looks for calls to methods on containers that expect an iterator inside the
+container but are given a different container
+
+.. code-block:: c++
+
+ vec.insert(other_vec.begin(), 5); // The iterator is invalid for this container
+
+Options
+-------
+
+.. option:: BeginFree
+
+ A semi-colon seperated list of free function names that return an iterator to
+ the start of a range. Default value is `::std::begin;std::cbegin`.
+
+.. option:: EndFree
+
+ A semi-colon seperated list of free function names that return an iterator to
+ the end of a range. Default value is `::std::end;std::cend`.
+
+.. option:: BeginMethod
+
+ A semi-colon seperated list of method names that return an iterator to
+ the start of a range. Default value is `begin;cbegin`.
+
+.. option:: EndMethod
+
+ A semi-colon seperated list of method names that return an iterator to
+ the end of a range. Default value is `end;cend`.
+
+.. option:: RBeginFree
+
+ A semi-colon seperated list of free function names that return a reverse
+ iterator to the start of a range. Default value is `::std::rbegin;std::crbegin`.
+
+.. option:: REndFree
+
+ A semi-colon seperated list of free function names that return a reverse
+ iterator to the end of a range. Default value is `::std::rend;std::crend`.
+
+.. option:: RBeginMethod
+
+ A semi-colon seperated list of method names that return a reverse
+ iterator to the start of a range. Default value is `rbegin;crbegin`.
+
+.. option:: REndMethod
+
+ A semi-colon seperated list of method names that return a reverse
+ iterator to the end of a range. Default value is `rend;crend`.
+
+.. option:: MakeReverseIterator
+
+ A semi-colon seperated list of free functions that convert an interator into a
+ reverse iterator. Default value is `::std::make_reverse_iterator`
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index dd2887edb0f8d..2e22dc5b6824f 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -100,6 +100,7 @@ Clang-Tidy Checks
:doc:`bugprone-inaccurate-erase <bugprone/inaccurate-erase>`, "Yes"
:doc:`bugprone-inc-dec-in-conditions <bugprone/inc-dec-in-conditions>`,
:doc:`bugprone-incorrect-enable-if <bugprone/incorrect-enable-if>`, "Yes"
+ :doc:`bugprone-incorrect-iterators <bugprone/incorrect-iterators>`, "Yes"
:doc:`bugprone-incorrect-roundings <bugprone/incorrect-roundings>`,
:doc:`bugprone-infinite-loop <bugprone/infinite-loop>`,
:doc:`bugprone-integer-division <bugprone/integer-division>`,
@@ -118,6 +119,7 @@ Clang-Tidy Checks
:doc:`bugprone-not-null-terminated-result <bugprone/not-null-terminated-result>`, "Yes"
:doc:`bugprone-optional-value-conversion <bugprone/optional-value-conversion>`, "Yes"
:doc:`bugprone-parent-virtual-call <bugprone/parent-virtual-call>`, "Yes"
+ :doc:`bugprone-pointer-arithmetic-on-polymorphic-object <bugprone/pointer-arithmetic-on-polymorphic-object>`,
:doc:`bugprone-posix-return <bugprone/posix-return>`, "Yes"
:doc:`bugprone-redundant-branch-condition <bugprone/redundant-branch-condition>`, "Yes"
:doc:`bugprone-reserved-identifier <bugprone/reserved-identifier>`, "Yes"
@@ -158,7 +160,6 @@ Clang-Tidy Checks
:doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
:doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
:doc:`bugprone-use-after-move <bugprone/use-after-move>`,
- :doc:`bugprone-pointer-arithmetic-on-polymorphic-object <bugprone/pointer-arithmetic-on-polymorphic-object>`,
:doc:`bugprone-virtual-near-miss <bugprone/virtual-near-miss>`, "Yes"
:doc:`cert-dcl50-cpp <cert/dcl50-cpp>`,
:doc:`cert-dcl58-cpp <cert/dcl58-cpp>`,
@@ -269,7 +270,7 @@ Clang-Tidy Checks
:doc:`misc-unused-parameters <misc/unused-parameters>`, "Yes"
:doc:`misc-unused-using-decls <misc/unused-using-decls>`, "Yes"
:doc:`misc-use-anonymous-namespace <misc/use-anonymous-namespace>`,
- :doc:`misc-use-internal-linkage <misc/use-internal-linkage>`,
+ :doc:`misc-use-internal-linkage <misc/use-internal-linkage>`, "Yes"
:doc:`modernize-avoid-bind <modernize/avoid-bind>`, "Yes"
:doc:`modernize-avoid-c-arrays <modernize/avoid-c-arrays>`,
:doc:`modernize-concat-nested-namespaces <modernize/concat-nested-namespaces>`, "Yes"
@@ -400,15 +401,14 @@ Clang-Tidy Checks
:doc:`readability-use-std-min-max <readability/use-std-min-max>`, "Yes"
:doc:`zircon-temporary-objects <zircon/temporary-objects>`,
-Check aliases
--------------
-.. csv-table::
+.. csv-table:: Aliases..
:header: "Name", "Redirect", "Offers fixes"
:doc:`bugprone-narrowing-conversions <bugprone/narrowing-conversions>`, :doc:`cppcoreguidelines-narrowing-conversions <cppcoreguidelines/narrowing-conversions>`,
:doc:`cert-con36-c <cert/con36-c>`, :doc:`bugprone-spuriously-wake-up-functions <bugprone/spuriously-wake-up-functions>`,
:doc:`cert-con54-cpp <cert/con54-cpp>`, :doc:`bugprone-spuriously-wake-up-functions <bugprone/spuriously-wake-up-functions>`,
+ :doc:`cert-ctr56-cpp <cert/ctr56-cpp>`, :doc:`bugprone-pointer-arithmetic-on-polymorphic-object <bugprone/pointer-arithmetic-on-polymorphic-object>`,
:doc:`cert-dcl03-c <cert/dcl03-c>`, :doc:`misc-static-assert <misc/static-assert>`, "Yes"
:doc:`cert-dcl16-c <cert/dcl16-c>`, :doc:`readability-uppercase-literal-suffix <readability/uppercase-literal-suffix>`, "Yes"
:doc:`cert-dcl37-c <cert/dcl37-c>`, :doc:`bugprone-reserved-identifier <bugprone/reserved-identifier>`, "Yes"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/incorrect-iterators.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/incorrect-iterators.cpp
new file mode 100644
index 0000000000000..6a0b3700ff127
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/incorrect-iterators.cpp
@@ -0,0 +1,156 @@
+// RUN: %check_clang_tidy -std=c++14 %s bugprone-incorrect-iterators %t
+
+namespace std {
+namespace execution {
+class parallel_policy {};
+constexpr parallel_policy par;
+} // namespace execution
+
+template <typename BiDirIter> class reverse_iterator {
+ constexpr explicit reverse_iterator(BiDirIter Iter);
+};
+
+template <typename BiDirIter>
+reverse_iterator<BiDirIter> make_reverse_iterator(BiDirIter Iter);
+
+template <typename T> class vector {
+public:
+ using iterator = T *;
+ using const_iterator = const T *;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using reverse_const_iterator = std::reverse_iterator<const_iterator>;
+
+ constexpr const_iterator begin() const;
+ constexpr const_iterator end() const;
+ constexpr const_iterator cbegin() const;
+ constexpr const_iterator cend() const;
+ constexpr iterator begin();
+ constexpr iterator end();
+ constexpr reverse_const_iterator rbegin() const;
+ constexpr reverse_const_iterator rend() const;
+ constexpr reverse_const_iterator crbegin() const;
+ constexpr reverse_const_iterator crend() const;
+ constexpr reverse_iterator rbegin();
+ constexpr reverse_iterator rend();
+
+ template <class InputIt>
+ iterator insert(const_iterator pos, InputIt first, InputIt last);
+};
+
+template <typename Container> constexpr auto begin(const Container &Cont) {
+ return Cont.begin();
+}
+
+template <typename Container> constexpr auto begin(Container &Cont) {
+ return Cont.begin();
+}
+
+template <typename Container> constexpr auto end(const Container &Cont) {
+ return Cont.end();
+}
+
+template <typename Container> constexpr auto end(Container &Cont) {
+ return Cont.end();
+}
+
+template <typename Container> constexpr auto cbegin(const Container &Cont) {
+ return Cont.cbegin();
+}
+
+template <typename Container> constexpr auto cend(const Container &Cont) {
+ return Cont.cend();
+}
+
+template <typename Container> constexpr auto rbegin(const Container &Cont) {
+ return Cont.rbegin();
+}
+
+template <typename Container> constexpr auto rbegin(Container &Cont) {
+ return Cont.rbegin();
+}
+
+template <typename Container> constexpr auto rend(const Container &Cont) {
+ return Cont.rend();
+}
+
+template <typename Container> constexpr auto rend(Container &Cont) {
+ return Cont.rend();
+}
+
+template <typename Container> constexpr auto crbegin(const Container &Cont) {
+ return Cont.crbegin();
+}
+
+template <typename Container> constexpr auto crend(const Container &Cont) {
+ return Cont.crend();
+}
+// Find
+template <class InputIt, class T>
+InputIt find(InputIt first, InputIt last, const T &value);
+
+template <class Policy, class InputIt, class T>
+InputIt find(Policy &&policy, InputIt first, InputIt last, const T &value);
+
+// Reverse
+template <typename Iter> void reverse(Iter begin, Iter end);
+
+// Includes
+template <class InputIt1, class InputIt2>
+bool includes(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2);
+
+// IsPermutation
+template <class ForwardIt1, class ForwardIt2>
+bool is_permutation(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2);
+template <class ForwardIt1, class ForwardIt2>
+bool is_permutation(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2,
+ ForwardIt2 last2);
+
+// Equal
+template <class InputIt1, class InputIt2>
+bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2);
+
+template <class InputIt1, class InputIt2>
+bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2);
+
+template <class InputIt1, class InputIt2, class BinaryPred>
+bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2,
+ BinaryPred p) {
+ // Need a definition to suppress undefined_internal_type when invoked with
+ // lambda
+ return true;
+}
+
+template <class ForwardIt, class T>
+void iota(ForwardIt first, ForwardIt last, T value);
+
+template <class ForwardIt>
+ForwardIt rotate(ForwardIt first, ForwardIt middle, ForwardIt last);
+
+} // namespace std
+
+void Test() {
+ std::vector<int> I;
+ std::vector<int> J;
+ std::find(I.end(), I.begin(), 0);
+ // CHECK-NOTES: [[@LINE-1]]:13: warning: 'end' iterator supplied where a 'begin' iterator is expected
+ // CHECK-NOTES: [[@LINE-2]]:22: warning: 'begin' iterator supplied where an 'end' iterator is expected
+ std::find(std::execution::par, I.begin(), J.end(), 0);
+ // CHECK-NOTES: [[@LINE-1]]:3: warning: mismatched ranges pass to function
+ // CHECK-NOTES: [[@LINE-2]]:34: note: first range passed here to begin
+ // CHECK-NOTES: [[@LINE-3]]:45: note: different range passed here to end
+ std::find(std::make_reverse_iterator(I.end()),
+ std::make_reverse_iterator(I.end()), 0);
+ // CHECK-NOTES: [[@LINE-1]]:40: warning: 'begin' iterator supplied where an 'end' iterator is expected
+ // CHECK-NOTES: [[@LINE-2]]:13: note: 'make_reverse_iterator<int *>' changes 'end' into a 'begin' iterator
+ std::find(I.rbegin(), std::make_reverse_iterator(J.begin()), 0);
+ // CHECK-NOTES: [[@LINE-1]]:3: warning: mismatched ranges pass to function
+ // CHECK-NOTES: [[@LINE-2]]:13: note: first range passed here to begin
+ // CHECK-NOTES: [[@LINE-3]]:52: note: different range passed here to end
+
+ I.insert(J.begin(), J.end(), J.begin());
+ // CHECK-NOTES: [[@LINE-1]]:12: warning: 'insert<int *>' called with an iterator for a different container
+ // CHECK-NOTES: [[@LINE-2]]:3: note: container is specified here
+ // CHECK-NOTES: [[@LINE-3]]:12: note: different container provided here
+ // CHECK-NOTES: [[@LINE-4]]:23: warning: 'end' iterator supplied where a 'begin' iterator is expected
+ // CHECK-NOTES: [[@LINE-5]]:32: warning: 'begin' iterator supplied where an 'end' iterator is expected
+}
More information about the cfe-commits
mailing list