Skip to content

Conversation

@njames93
Copy link
Member

@njames93 njames93 commented Jul 22, 2024

Looks for various patterns of functions that take arguments calling begin/end or similar for common mistakes.

@llvmbot
Copy link
Member

llvmbot commented Jul 22, 2024

@llvm/pr-subscribers-clang-tools-extra

Author: Nathan James (njames93)

Changes

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


Patch is 35.31 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/99917.diff

8 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3)
  • (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.cpp (+362)
  • (added) clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.h (+44)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+6)
  • (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/incorrect-iterators.rst (+111)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+5-5)
  • (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/incorrect-iterators.cpp (+156)
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 alg... [truncated] 
@llvmbot
Copy link
Member

llvmbot commented Jul 22, 2024

@llvm/pr-subscribers-clang-tidy

Author: Nathan James (njames93)

Changes

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


Patch is 35.31 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/99917.diff

8 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3)
  • (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.cpp (+362)
  • (added) clang-tools-extra/clang-tidy/bugprone/IncorrectIteratorsCheck.h (+44)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+6)
  • (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/incorrect-iterators.rst (+111)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+5-5)
  • (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/incorrect-iterators.cpp (+156)
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 alg... [truncated] 
@github-actions
Copy link

github-actions bot commented Jul 22, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@njames93 njames93 force-pushed the ct-incorrect-iterators branch 2 times, most recently from e71d153 to 7be8e9d Compare July 23, 2024 10:02
@njames93 njames93 changed the title Draft: Create a new check to look for mis-use in calls that take iterators Create a new check to look for mis-use in calls that take iterators Jul 23, 2024
@njames93 njames93 force-pushed the ct-incorrect-iterators branch from 7be8e9d to 1b91c22 Compare July 23, 2024 10:08
@njames93 njames93 force-pushed the ct-incorrect-iterators branch 2 times, most recently from 4293365 to 0c85592 Compare July 23, 2024 14:45
Copy link
Contributor

@5chmidti 5chmidti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial thoughts, and at first glance, this looks nice. Because we are early in the release cycle, there will be enough time to address the fixme's.

(I haven't run the check against some code bases as I am not at my main machine for another week.)

Comment on lines 619 to 847
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some more container member that expect an iterator of the container, such as std::deque::erase. Can we add them now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is a little sore point, that there's not yet(#100349) a nice way to specify the available permutations

@njames93 njames93 force-pushed the ct-incorrect-iterators branch 2 times, most recently from bbf32f3 to e1c3086 Compare July 27, 2024 16:22
@njames93
Copy link
Member Author

Added some detection for trying to advance past the end or before the start of a range.

@njames93 njames93 force-pushed the ct-incorrect-iterators branch from e1c3086 to c79951f Compare July 27, 2024 18:12
@njames93
Copy link
Member Author

@5chmidti I decided to run this version of the check on llvm and clang
https://gist.github.com/njames93/6f8863573972cb07c388ac19e8a20467

Seems like most of the warnings detected are when we are deliberately exceeding the bounds of a range when we know its a slice of a bigger range. Some of them are potentially cases where code could be refactored to better express it's intent, other instances already have comments to explain why we are exceeding the bounds

There were also some cases where classes had begin/end pairs that didn't return iterators, instead just integral types.
These cases could be silenced by changing the check to ensure we aren't looking for integral types, but I feel that's such an unlikely case that there's no need to worry about it

@5chmidti
Copy link
Contributor

The ++ and -- operators could be mis-used as well, similar to + and -. And there are some control statements with a single statement that has a compound statement around it, please remove those.

Checking the log, it looks like the check works well.

I know that TBB has begin and end members that return integral types. But I'm not sure that we need to exclude checking for integral types. E.g., a.end() + 1 could still be bupgrone, even if end returns an int.

@njames93
Copy link
Member Author

The ++ and -- operators could be mis-used as well, similar to + and -. And there are some control statements with a single statement that has a compound statement around it, please remove those.

The ++ and -- operators work on lvalues only(std::end(Range)++ is usually a compiler error.), Therefore, in order to detect that, there would need to be some control flow logic in the check, Something along the lines of detecting setting a variable to Range.end() then looking for any calls to operator++ on that variable. This logic definitely could be done and the results could catch many more questionable cases, but for the first version of this check I think it can be left out.

Copy link
Contributor

@5chmidti 5chmidti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (- single statement compound statements), but wait for another review please

@njames93 njames93 force-pushed the ct-incorrect-iterators branch 2 times, most recently from 4762dc0 to f32add6 Compare July 30, 2024 07:00
@njames93
Copy link
Member Author

njames93 commented Jul 30, 2024

  • Cleaned up handling of functions that take an execution policy as first argument.
  • Handle std::trasnform and its overloads as a special case(Its overloads are a bit of a pain)
    TODO:
  • Handle functions that take 3 argument ranges (first, middle, last) like std::rotate
@njames93 njames93 force-pushed the ct-incorrect-iterators branch 2 times, most recently from e604ce2 to e4d6c8e Compare August 1, 2024 22:18
Looks for various patterns of functions that take arguments calling begin/end or similar for common mistakes
@njames93 njames93 force-pushed the ct-incorrect-iterators branch from e4d6c8e to d0cf6a5 Compare August 2, 2024 09:51
Comment on lines +32 to +40
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are usually const, as these correspond to the options obtained from the user's configuration.

@whisperity
Copy link
Member

whisperity commented Aug 2, 2024

What happens in the following case?

std::vector V; const auto& V2 = V; std::find(V.begin(), V2.end(), 42);

These are syntactically different symbols but still should be the same underlying memory area. Perhaps it could be mentioned in the documentation, that the Clang Static Analyser has (albeit only as alpha...) some iterator-related checkers as well, where range constraints are (attempted to be?) modelled path-sensitively.

Maybe @baloghadamsoftware or @gamesh411 still remembers the status of those checkers.

// CHECK-NOTES: :[[@LINE-3]]:61: warning: 'end' iterator supplied where a 'begin' iterator is expected
// CHECK-NOTES: :[[@LINE-3]]:18: warning: 'end' iterator supplied where an output iterator is expected

std::push_heap(std::begin(I), std::end(J));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add test case:

std::vector<int> get_data() { return std::vector<int>{1,2,3,4}; } void test() { std::push_heap(std::begin(get_data()), std::end(get_data())); } 

There should also be a warning here "mismatched ranges supplied to 'std::push_heap'"

@EugeneZelenko
Copy link
Contributor

Please rebase from main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment