[clang-tools-extra] [clang-tidy] Add `bugprone-missing-end-comparison` check (PR #182543)
Zeyi Xu via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 26 03:39:13 PDT 2026
https://github.com/zeyi2 updated https://github.com/llvm/llvm-project/pull/182543
>From 4ae63e8eea272b9059ea77429988ebc7e0ef26db Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Tue, 3 Feb 2026 00:24:05 +0800
Subject: [PATCH 01/17] ~
---
.../bugprone/BugproneTidyModule.cpp | 3 +
.../clang-tidy/bugprone/CMakeLists.txt | 1 +
.../bugprone/MissingEndComparisonCheck.cpp | 107 ++++++++++++++++++
.../bugprone/MissingEndComparisonCheck.h | 34 ++++++
clang-tools-extra/docs/ReleaseNotes.rst | 5 +
.../bugprone/missing-end-comparison.rst | 6 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../bugprone/missing-end-comparison.cpp | 94 +++++++++++++++
8 files changed, 251 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 4150442c25d61..8ddc7f5ce8306 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -51,6 +51,7 @@
#include "MisplacedOperatorInStrlenInAllocCheck.h"
#include "MisplacedPointerArithmeticInAllocCheck.h"
#include "MisplacedWideningCastCheck.h"
+#include "MissingEndComparisonCheck.h"
#include "MoveForwardingReferenceCheck.h"
#include "MultiLevelImplicitPointerConversionCheck.h"
#include "MultipleNewInOneExpressionCheck.h"
@@ -176,6 +177,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-incorrect-enable-if");
CheckFactories.registerCheck<IncorrectEnableSharedFromThisCheck>(
"bugprone-incorrect-enable-shared-from-this");
+ CheckFactories.registerCheck<MissingEndComparisonCheck>(
+ "bugprone-missing-end-comparison");
CheckFactories.registerCheck<UnintendedCharOstreamOutputCheck>(
"bugprone-unintended-char-ostream-output");
CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index db1256d91d311..66f94c20c3a06 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -37,6 +37,7 @@ add_clang_library(clangTidyBugproneModule STATIC
IncorrectEnableIfCheck.cpp
IncorrectEnableSharedFromThisCheck.cpp
InvalidEnumDefaultInitializationCheck.cpp
+ MissingEndComparisonCheck.cpp
UnintendedCharOstreamOutputCheck.cpp
ReturnConstRefFromParameterCheck.cpp
SuspiciousStringviewDataUsageCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
new file mode 100644
index 0000000000000..e7071d94f0ee6
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -0,0 +1,107 @@
+//===--- MissingEndComparisonCheck.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 "MissingEndComparisonCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
+ // List of standard algorithms that return an iterator and should be compared
+ // to the end iterator.
+ // Note: Algorithms returning pairs (like equal_range, mismatch) are excluded
+ // because std::pair doesn't implicitly convert to bool, so they wouldn't
+ // match CK_PointerToBoolean anyway.
+ auto StandardIteratorAlgorithms = functionDecl(hasAnyName(
+ "::std::find", "::std::find_if", "::std::find_if_not", "::std::search",
+ "::std::search_n", "::std::find_end", "::std::find_first_of",
+ "::std::lower_bound", "::std::upper_bound", "::std::partition_point",
+ "::std::min_element", "::std::max_element", "::std::adjacent_find",
+ "::std::is_sorted_until"));
+
+ // Matcher 1: Implicit cast from pointer to boolean.
+ // This catches cases where the algorithm returns a raw pointer (e.g.,
+ // finding in a C-array) and it's used in a boolean context.
+ Finder->addMatcher(
+ implicitCastExpr(
+ hasCastKind(CK_PointerToBoolean),
+ hasSourceExpression(ignoringParenImpCasts(
+ callExpr(callee(StandardIteratorAlgorithms)).bind("call"))))
+ .bind("cast"),
+ this);
+
+ // Matcher 2: Explicit/Implicit conversion via operator bool.
+ // This catches cases where the returned iterator has an explicit or implicit
+ // conversion to bool (e.g., some custom iterators).
+ Finder->addMatcher(
+ cxxMemberCallExpr(
+ callee(cxxConversionDecl(returns(booleanType()))),
+ on(ignoringParenImpCasts(
+ callExpr(callee(StandardIteratorAlgorithms)).bind("call"))))
+ .bind("cast"),
+ this);
+}
+
+void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
+ const auto *Cast = Result.Nodes.getNodeAs<Expr>("cast");
+
+ if (!Call || !Cast)
+ return;
+
+ // Most standard algorithms take the end iterator as the second argument.
+ // Check if we have enough arguments.
+ if (Call->getNumArgs() < 2)
+ return;
+
+ const Expr *EndIterArg = Call->getArg(1);
+
+ // If the second argument is nullptr/NULL, the user might be intentionally
+ // checking against nullptr (though odd for std algorithms, it's possible
+ // for raw pointers).
+ if (EndIterArg->isNullPointerConstant(*Result.Context,
+ Expr::NPC_ValueDependentIsNull)) {
+ return;
+ }
+
+ auto Diag = diag(Cast->getBeginLoc(),
+ "result of standard algorithm used in boolean context; did "
+ "you mean to compare with the end iterator?");
+
+ // Try to generate a fix-it.
+ // We want to rewrite the expression 'E' to 'E != EndIter'.
+ // 'Cast' is the boolean expression (e.g. the implicit cast or the bool conversion call).
+ // However, simply appending '!= End' to the end of the Cast's range might be tricky
+ // if there are precedence issues, but usually != has lower precedence than function calls
+ // and higher than assignment/logic.
+
+ // Get the source text of the end iterator argument.
+ StringRef EndIterText = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(EndIterArg->getSourceRange()),
+ *Result.SourceManager, Result.Context->getLangOpts());
+
+ if (EndIterText.empty())
+ return;
+
+ // Check if the end iterator expression is safe to duplicate.
+ // (Simple variable, member access, call to .end()).
+ // For now, we'll be conservative. If it looks complex, skip the fix-it.
+ // A simple heuristic: if it contains side effects or is too long, skip.
+ // But we can just provide the hint.
+
+ Diag << FixItHint::CreateInsertion(
+ Lexer::getLocForEndOfToken(Cast->getEndLoc(), 0, *Result.SourceManager,
+ Result.Context->getLangOpts()),
+ (" != " + EndIterText).str());
+}
+
+} // namespace clang::tidy::bugprone
\ No newline at end of file
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.h b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.h
new file mode 100644
index 0000000000000..75a2afab5a3a9
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.h
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_MISSINGENDCOMPARISONCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MISSINGENDCOMPARISONCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Detects usage of the result of standard algorithms (like std::find) in a
+/// boolean context without comparing it with the end iterator.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/missing-end-comparison.html
+class MissingEndComparisonCheck : public ClangTidyCheck {
+public:
+ MissingEndComparisonCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus;
+ }
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MISSINGENDCOMPARISONCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 754880bd1a381..fbe0a4d99cdad 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -97,6 +97,11 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^
+- New :doc:`bugprone-missing-end-comparison
+ <clang-tidy/checks/bugprone/missing-end-comparison>` check.
+
+ FIXME: Write a short description.
+
- New :doc:`llvm-use-vector-utils
<clang-tidy/checks/llvm/use-vector-utils>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
new file mode 100644
index 0000000000000..90894dce48786
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
@@ -0,0 +1,6 @@
+.. title:: clang-tidy - bugprone-missing-end-comparison
+
+bugprone-missing-end-comparison
+===============================
+
+FIXME: Describe what patterns does the check detect and why. Give examples.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 25d1354fc4c20..8d3470340a248 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -121,6 +121,7 @@ Clang-Tidy Checks
:doc:`bugprone-misplaced-operator-in-strlen-in-alloc <bugprone/misplaced-operator-in-strlen-in-alloc>`, "Yes"
:doc:`bugprone-misplaced-pointer-arithmetic-in-alloc <bugprone/misplaced-pointer-arithmetic-in-alloc>`, "Yes"
:doc:`bugprone-misplaced-widening-cast <bugprone/misplaced-widening-cast>`,
+ :doc:`bugprone-missing-end-comparison <bugprone/missing-end-comparison>`, "Yes"
:doc:`bugprone-move-forwarding-reference <bugprone/move-forwarding-reference>`, "Yes"
:doc:`bugprone-multi-level-implicit-pointer-conversion <bugprone/multi-level-implicit-pointer-conversion>`,
:doc:`bugprone-multiple-new-in-one-expression <bugprone/multiple-new-in-one-expression>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
new file mode 100644
index 0000000000000..1632a04c3ab15
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -0,0 +1,94 @@
+// RUN: %check_clang_tidy %s bugprone-missing-end-comparison %t -- -- -std=c++17
+
+namespace std {
+ template<typename T> struct iterator_traits;
+ struct forward_iterator_tag {};
+
+ typedef long int ptrdiff_t;
+ typedef decltype(nullptr) nullptr_t;
+
+ template<typename T>
+ struct vector {
+ typedef T* iterator;
+ typedef const T* const_iterator;
+ iterator begin();
+ iterator end();
+ const_iterator begin() const;
+ const_iterator end() const;
+ };
+
+ template<class InputIt, class T>
+ InputIt find(InputIt first, InputIt last, const T& value);
+
+ template<class ForwardIt, class T>
+ ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);
+
+ template<class ForwardIt, class ForwardIt2>
+ ForwardIt search(ForwardIt first, ForwardIt last, ForwardIt first2, ForwardIt2 last2);
+
+ template<class ForwardIt>
+ ForwardIt min_element(ForwardIt first, ForwardIt last);
+}
+
+struct CustomIterator {
+ int* ptr;
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = int;
+ using difference_type = long;
+ using pointer = int*;
+ using reference = int&;
+
+ int& operator*() const { return *ptr; }
+ CustomIterator& operator++() { ++ptr; return *this; }
+ bool operator==(const CustomIterator& other) const { return ptr == other.ptr; }
+ bool operator!=(const CustomIterator& other) const { return ptr != other.ptr; }
+
+ explicit operator bool() const { return ptr != nullptr; }
+};
+
+void test_raw_pointers() {
+ int arr[] = {1, 2, 3};
+ int* begin = arr;
+ int* end = arr + 3;
+
+ if (std::find(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if (std::find(begin, end, 2) != end) {}
+
+ while (std::lower_bound(begin, end, 2)) { break; }
+ // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: result of standard algorithm used in boolean context
+ // CHECK-FIXES: while (std::lower_bound(begin, end, 2) != end) { break; }
+
+ if (std::find(begin, end, 2) != end) {}
+}
+
+void test_vector() {
+ std::vector<int> v;
+ if (std::find(v.begin(), v.end(), 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
+ // CHECK-FIXES: if (std::find(v.begin(), v.end(), 2) != v.end()) {}
+
+ if (std::min_element(v.begin(), v.end())) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
+ // CHECK-FIXES: if (std::min_element(v.begin(), v.end()) != v.end()) {}
+}
+
+void test_custom_iterator() {
+ CustomIterator begin{nullptr}, end{nullptr};
+ if (std::find(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
+ // CHECK-FIXES: if (std::find(begin, end, 2) != end) {}
+}
+
+void test_complex_end() {
+ int arr[] = {1, 2, 3};
+ if (std::find(arr, arr + 3, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
+ // CHECK-FIXES: if (std::find(arr, arr + 3, 2) != arr + 3) {}
+}
+
+void test_sentinel() {
+ int* ptr = nullptr;
+ if (std::find<int*>(ptr, nullptr, 10)) {}
+ // No warning expected for nullptr sentinel
+}
\ No newline at end of file
>From a279a89481a7ffedd4977f3b14e8353ec35ed39a Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 20 Feb 2026 21:08:43 +0800
Subject: [PATCH 02/17] ~~
---
.../bugprone/MissingEndComparisonCheck.cpp | 220 ++++++++++++------
clang-tools-extra/docs/ReleaseNotes.rst | 3 +-
.../bugprone/missing-end-comparison.rst | 65 +++++-
.../bugprone/missing-end-comparison.cpp | 142 +++++++++--
4 files changed, 332 insertions(+), 98 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index e7071d94f0ee6..42ff99b121f84 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -1,4 +1,4 @@
-//===--- MissingEndComparisonCheck.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.
@@ -10,98 +10,166 @@
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/FixIt.h"
using namespace clang::ast_matchers;
namespace clang::tidy::bugprone {
+namespace {
+
+constexpr llvm::StringRef IteratorAlgorithms[] = {
+ "::std::find", "::std::find_if",
+ "::std::find_if_not", "::std::search",
+ "::std::search_n", "::std::find_end",
+ "::std::find_first_of", "::std::lower_bound",
+ "::std::upper_bound", "::std::partition_point",
+ "::std::min_element", "::std::max_element",
+ "::std::adjacent_find", "::std::is_sorted_until"};
+
+constexpr llvm::StringRef RangeAlgorithms[] = {
+ "::std::ranges::find", "::std::ranges::find_if",
+ "::std::ranges::find_if_not", "::std::ranges::lower_bound",
+ "::std::ranges::upper_bound", "::std::ranges::min_element",
+ "::std::ranges::max_element"};
+
+} // namespace
+
void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
- // List of standard algorithms that return an iterator and should be compared
- // to the end iterator.
- // Note: Algorithms returning pairs (like equal_range, mismatch) are excluded
- // because std::pair doesn't implicitly convert to bool, so they wouldn't
- // match CK_PointerToBoolean anyway.
- auto StandardIteratorAlgorithms = functionDecl(hasAnyName(
- "::std::find", "::std::find_if", "::std::find_if_not", "::std::search",
- "::std::search_n", "::std::find_end", "::std::find_first_of",
- "::std::lower_bound", "::std::upper_bound", "::std::partition_point",
- "::std::min_element", "::std::max_element", "::std::adjacent_find",
- "::std::is_sorted_until"));
-
- // Matcher 1: Implicit cast from pointer to boolean.
- // This catches cases where the algorithm returns a raw pointer (e.g.,
- // finding in a C-array) and it's used in a boolean context.
- Finder->addMatcher(
- implicitCastExpr(
- hasCastKind(CK_PointerToBoolean),
- hasSourceExpression(ignoringParenImpCasts(
- callExpr(callee(StandardIteratorAlgorithms)).bind("call"))))
- .bind("cast"),
- this);
-
- // Matcher 2: Explicit/Implicit conversion via operator bool.
- // This catches cases where the returned iterator has an explicit or implicit
- // conversion to bool (e.g., some custom iterators).
+ const auto StdAlgoCall = callExpr(
+ callee(functionDecl(hasAnyName(IteratorAlgorithms), isInStdNamespace())));
+
+ const auto RangesCall = cxxOperatorCallExpr(
+ hasOverloadedOperatorName("()"),
+ hasArgument(0, declRefExpr(to(
+ varDecl(hasAnyName(RangeAlgorithms)).bind("cpo")))));
+
+ const auto AnyAlgoCall =
+ getLangOpts().CPlusPlus20
+ ? expr(anyOf(StdAlgoCall, RangesCall)).bind("algoCall")
+ : expr(StdAlgoCall).bind("algoCall");
+
+ // Captures implicit pointer-to-bool casts and operator bool() calls.
+ const auto IsBoolUsage = anyOf(
+ implicitCastExpr(hasCastKind(CK_PointerToBoolean),
+ hasSourceExpression(ignoringParenImpCasts(AnyAlgoCall))),
+ cxxMemberCallExpr(callee(cxxConversionDecl(returns(booleanType()))),
+ on(ignoringParenImpCasts(AnyAlgoCall))));
+
+ // Captures variable usage: `auto it = std::find(...); if (it)`
+ // FIXME: This only handles variables initialized directly by the algorithm.
+ // We may need to introduce more accurate dataflow analysis in the future.
+ const auto VarWithAlgoInit =
+ varDecl(hasInitializer(ignoringParenImpCasts(AnyAlgoCall)));
+
+ const auto IsVariableBoolUsage =
+ anyOf(implicitCastExpr(hasCastKind(CK_PointerToBoolean),
+ hasSourceExpression(ignoringParenImpCasts(
+ declRefExpr(to(VarWithAlgoInit))))),
+ cxxMemberCallExpr(
+ callee(cxxConversionDecl(returns(booleanType()))),
+ on(ignoringParenImpCasts(declRefExpr(to(VarWithAlgoInit))))));
+
Finder->addMatcher(
- cxxMemberCallExpr(
- callee(cxxConversionDecl(returns(booleanType()))),
- on(ignoringParenImpCasts(
- callExpr(callee(StandardIteratorAlgorithms)).bind("call"))))
- .bind("cast"),
- this);
+ expr(anyOf(IsBoolUsage, IsVariableBoolUsage)).bind("boolOp"), this);
}
void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
- const auto *Cast = Result.Nodes.getNodeAs<Expr>("cast");
+ const auto *Call = Result.Nodes.getNodeAs<CallExpr>("algoCall");
+ const auto *BoolOp = Result.Nodes.getNodeAs<Expr>("boolOp");
+ const auto *CPO = Result.Nodes.getNodeAs<VarDecl>("cpo");
- if (!Call || !Cast)
+ if (!Call || !BoolOp)
return;
- // Most standard algorithms take the end iterator as the second argument.
- // Check if we have enough arguments.
- if (Call->getNumArgs() < 2)
- return;
+ std::string EndExprText;
+
+ if (!CPO) {
+ if (Call->getNumArgs() < 2)
+ return;
- const Expr *EndIterArg = Call->getArg(1);
-
- // If the second argument is nullptr/NULL, the user might be intentionally
- // checking against nullptr (though odd for std algorithms, it's possible
- // for raw pointers).
- if (EndIterArg->isNullPointerConstant(*Result.Context,
- Expr::NPC_ValueDependentIsNull)) {
+ const Expr *EndArg = Call->getArg(1);
+ // Filters nullptr, we assume the intent might be a valid check against null
+ if (EndArg->IgnoreParenCasts()->isNullPointerConstant(
+ *Result.Context, Expr::NPC_ValueDependentIsNull))
return;
+
+ EndExprText = tooling::fixit::getText(*EndArg, *Result.Context).str();
+ } else {
+ const FunctionDecl *Callee = Call->getDirectCallee();
+ if (!Callee || Callee->getNumParams() == 0)
+ return;
+
+ // Range overloads take a reference (R&&), Iterator overloads pass by value.
+ const bool IsIterPair =
+ !Callee->getParamDecl(0)->getType()->isReferenceType();
+
+ if (IsIterPair) {
+ if (Call->getNumArgs() < 3)
+ return;
+ // find(CPO, Iter, Sent, Val...) -> Sent is Arg 2.
+ const Expr *EndArg = Call->getArg(2);
+ EndExprText = tooling::fixit::getText(*EndArg, *Result.Context).str();
+ } else {
+ if (Call->getNumArgs() < 2)
+ return;
+ // find(CPO, Range, Val, Proj) -> Range is Arg 1.
+ const Expr *RangeArg = Call->getArg(1);
+ // Avoid potential side-effects
+ const Expr *InnerRange = RangeArg->IgnoreParenImpCasts();
+ if (isa<DeclRefExpr>(InnerRange) || isa<MemberExpr>(InnerRange)) {
+ const StringRef RangeText =
+ tooling::fixit::getText(*RangeArg, *Result.Context);
+ if (!RangeText.empty())
+ EndExprText = ("std::ranges::end(" + RangeText + ")").str();
+ }
+ }
}
- auto Diag = diag(Cast->getBeginLoc(),
- "result of standard algorithm used in boolean context; did "
- "you mean to compare with the end iterator?");
-
- // Try to generate a fix-it.
- // We want to rewrite the expression 'E' to 'E != EndIter'.
- // 'Cast' is the boolean expression (e.g. the implicit cast or the bool conversion call).
- // However, simply appending '!= End' to the end of the Cast's range might be tricky
- // if there are precedence issues, but usually != has lower precedence than function calls
- // and higher than assignment/logic.
-
- // Get the source text of the end iterator argument.
- StringRef EndIterText = Lexer::getSourceText(
- CharSourceRange::getTokenRange(EndIterArg->getSourceRange()),
- *Result.SourceManager, Result.Context->getLangOpts());
-
- if (EndIterText.empty())
- return;
+ bool IsNegated = false;
+ const UnaryOperator *NotOp = nullptr;
+ const Expr *CurrentExpr = BoolOp;
+ while (true) {
+ auto Parents = Result.Context->getParents(*CurrentExpr);
+ if (Parents.empty())
+ break;
+ if (const auto *P = Parents[0].get<ParenExpr>()) {
+ CurrentExpr = P;
+ continue;
+ }
+ if (const auto *U = Parents[0].get<UnaryOperator>()) {
+ if (U->getOpcode() == UO_LNot) {
+ NotOp = U;
+ IsNegated = true;
+ }
+ }
+ break;
+ }
- // Check if the end iterator expression is safe to duplicate.
- // (Simple variable, member access, call to .end()).
- // For now, we'll be conservative. If it looks complex, skip the fix-it.
- // A simple heuristic: if it contains side effects or is too long, skip.
- // But we can just provide the hint.
-
- Diag << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(Cast->getEndLoc(), 0, *Result.SourceManager,
- Result.Context->getLangOpts()),
- (" != " + EndIterText).str());
+ const auto Diag =
+ diag(BoolOp->getBeginLoc(),
+ "result of standard algorithm used in boolean context; did "
+ "you mean to compare with the end iterator?");
+
+ if (!EndExprText.empty()) {
+ if (IsNegated) {
+ // !it -> (it == end)
+ Diag << FixItHint::CreateReplacement(NotOp->getOperatorLoc(), "(");
+ Diag << FixItHint::CreateInsertion(
+ Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
+ *Result.SourceManager,
+ Result.Context->getLangOpts()),
+ " == " + EndExprText + ")");
+ } else {
+ // it -> (it != end)
+ Diag << FixItHint::CreateInsertion(BoolOp->getBeginLoc(), "(");
+ Diag << FixItHint::CreateInsertion(
+ Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
+ *Result.SourceManager,
+ Result.Context->getLangOpts()),
+ " != " + EndExprText + ")");
+ }
+ }
}
-} // namespace clang::tidy::bugprone
\ No newline at end of file
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 1d4c581fc2ddf..b89e2c0c60bf3 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -105,7 +105,8 @@ New checks
- New :doc:`bugprone-missing-end-comparison
<clang-tidy/checks/bugprone/missing-end-comparison>` check.
- FIXME: Write a short description.
+ Finds instances where the result of a standard algorithm is used in a boolean
+ context without being compared to the end iterator.
- New :doc:`bugprone-unsafe-to-allow-exceptions
<clang-tidy/checks/bugprone/unsafe-to-allow-exceptions>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
index 90894dce48786..4f1ac0d7657ab 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
@@ -3,4 +3,67 @@
bugprone-missing-end-comparison
===============================
-FIXME: Describe what patterns does the check detect and why. Give examples.
+Finds instances where the result of a standard algorithm is used in a boolean
+context without being compared to the end iterator.
+
+Standard algorithms such as `std::find`, `std::search`, and `std::lower_bound`
+return an iterator to the element if found, or the end iterator otherwise.
+
+Using the result directly in a boolean context (like an `if` statement) is
+almost always a bug, as it only checks if the iterator itself evaluates to
+`true`, which may always be true for many iterator types (including pointers).
+
+Examples:
+
+.. code-block:: c++
+
+ int arr[] = {1, 2, 3};
+ int* begin = std::begin(arr);
+ int* end = std::end(arr);
+
+ // Problematic:
+ if (std::find(begin, end, 2)) {
+ // ...
+ }
+
+ // Fixed by the check:
+ if ((std::find(begin, end, 2) != end)) {
+ // ...
+ }
+
+ // C++20 ranges:
+ std::vector<int> v = {1, 2, 3};
+ if (std::ranges::find(v, 2)) { // Problematic
+ // ...
+ }
+
+ // Fixed by the check:
+ if ((std::ranges::find(v, 2) != std::ranges::end(v))) {
+ // ...
+ }
+
+The check also handles range-based algorithms introduced in C++20.
+
+Supported algorithms:
+
+- `std::find`
+- `std::find_if`
+- `std::find_if_not`
+- `std::search`
+- `std::search_n`
+- `std::find_end`
+- `std::find_first_of`
+- `std::lower_bound`
+- `std::upper_bound`
+- `std::partition_point`
+- `std::min_element`
+- `std::max_element`
+- `std::adjacent_find`
+- `std::is_sorted_until`
+- `std::ranges::find`
+- `std::ranges::find_if`
+- `std::ranges::find_if_not`
+- `std::ranges::lower_bound`
+- `std::ranges::upper_bound`
+- `std::ranges::min_element`
+- `std::ranges::max_element`
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
index 1632a04c3ab15..6bbeef5cbde6f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -1,9 +1,9 @@
-// RUN: %check_clang_tidy %s bugprone-missing-end-comparison %t -- -- -std=c++17
+// RUN: %check_clang_tidy -std=c++20 %s bugprone-missing-end-comparison %t
namespace std {
template<typename T> struct iterator_traits;
struct forward_iterator_tag {};
-
+
typedef long int ptrdiff_t;
typedef decltype(nullptr) nullptr_t;
@@ -28,6 +28,28 @@ namespace std {
template<class ForwardIt>
ForwardIt min_element(ForwardIt first, ForwardIt last);
+
+ template<class InputIt1, class InputIt2>
+ struct pair {
+ InputIt1 first;
+ InputIt2 second;
+ };
+
+ namespace ranges {
+ template<typename T>
+ void* begin(T& t);
+ template<typename T>
+ void* end(T& t);
+
+ struct FindFn {
+ template<typename Range, typename T>
+ void* operator()(Range&& r, const T& value) const;
+
+ template<typename I, typename S, typename T>
+ void* operator()(I first, S last, const T& value) const;
+ };
+ inline constexpr FindFn find;
+ }
}
struct CustomIterator {
@@ -42,7 +64,7 @@ struct CustomIterator {
CustomIterator& operator++() { ++ptr; return *this; }
bool operator==(const CustomIterator& other) const { return ptr == other.ptr; }
bool operator!=(const CustomIterator& other) const { return ptr != other.ptr; }
-
+
explicit operator bool() const { return ptr != nullptr; }
};
@@ -53,42 +75,122 @@ void test_raw_pointers() {
if (std::find(begin, end, 2)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if (std::find(begin, end, 2) != end) {}
+ // CHECK-FIXES: if ((std::find(begin, end, 2) != end)) {}
while (std::lower_bound(begin, end, 2)) { break; }
- // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: result of standard algorithm used in boolean context
- // CHECK-FIXES: while (std::lower_bound(begin, end, 2) != end) { break; }
+ // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: while ((std::lower_bound(begin, end, 2) != end)) { break; }
- if (std::find(begin, end, 2) != end) {}
+ if (!std::find(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(begin, end, 2) == end)) {}
}
void test_vector() {
std::vector<int> v;
if (std::find(v.begin(), v.end(), 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
- // CHECK-FIXES: if (std::find(v.begin(), v.end(), 2) != v.end()) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(v.begin(), v.end(), 2) != v.end())) {}
if (std::min_element(v.begin(), v.end())) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
- // CHECK-FIXES: if (std::min_element(v.begin(), v.end()) != v.end()) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::min_element(v.begin(), v.end()) != v.end())) {}
+}
+
+void test_variable_tracking() {
+ int arr[] = {1, 2, 3};
+ auto it = std::find(arr, arr + 3, 2);
+ if (it) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((it != arr + 3)) {}
+
+ if (!it) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((it == arr + 3)) {}
+}
+
+void test_ranges() {
+ std::vector<int> v;
+ if (std::ranges::find(v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find(v, 2) != std::ranges::end(v))) {}
+
+ auto it = std::ranges::find(v, 2);
+ if (it) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((it != std::ranges::end(v))) {}
+}
+
+void test_ranges_iterator_pair() {
+ int arr[] = {1, 2, 3};
+ int *begin = arr;
+ int *end = arr + 3;
+ if (std::ranges::find(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find(begin, end, 2) != end)) {}
+}
+
+void test_side_effects() {
+ std::vector<int> get_vec();
+ if (std::ranges::find(get_vec(), 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+}
+
+void test_negative() {
+ std::vector<int> v;
+ if (std::find(v.begin(), v.end(), 2) != v.end()) {}
+ if (std::ranges::find(v, 2) == std::ranges::end(v)) {}
+ auto it = std::find(v.begin(), v.end(), 2);
+}
+
+void test_nested_parens() {
+ int arr[] = {1, 2, 3};
+ if (!((std::find(arr, arr + 3, 2)))) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((((std::find(arr, arr + 3, 2))) == arr + 3)) {}
+}
+
+struct Data { std::vector<int> v; };
+void test_member_expr(Data& d) {
+ if (std::ranges::find(d.v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find(d.v, 2) != std::ranges::end(d.v))) {}
+}
+
+void test_nullptr_comparison() {
+ if (std::find((int*)nullptr, (int*)nullptr, 2)) {}
+}
+
+void test_double_negation() {
+ int arr[] = {1, 2, 3};
+ auto it = std::find(arr, arr + 3, 2);
+ if (!!it) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if (!(it == arr + 3)) {}
}
void test_custom_iterator() {
CustomIterator begin{nullptr}, end{nullptr};
if (std::find(begin, end, 2)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
- // CHECK-FIXES: if (std::find(begin, end, 2) != end) {}
+ // CHECK-FIXES: if ((std::find(begin, end, 2) != end)) {}
}
-void test_complex_end() {
+void test_search() {
int arr[] = {1, 2, 3};
- if (std::find(arr, arr + 3, 2)) {}
+ int* begin = arr;
+ int* end = arr + 3;
+
+ if (std::search(begin, end, begin, end)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
- // CHECK-FIXES: if (std::find(arr, arr + 3, 2) != arr + 3) {}
+ // CHECK-FIXES: if ((std::search(begin, end, begin, end) != end)) {}
}
-void test_sentinel() {
- int* ptr = nullptr;
- if (std::find<int*>(ptr, nullptr, 10)) {}
- // No warning expected for nullptr sentinel
-}
\ No newline at end of file
+namespace other {
+ bool find(int* b, int* e, int v);
+}
+
+void test_other_namespace() {
+ int arr[] = {1};
+ if (other::find(arr, arr + 1, 1)) {}
+}
>From 06b5a27bdc5fca2b63cd6d43cf6b9077d538581e Mon Sep 17 00:00:00 2001
From: mitchell <mitchell.xu2 at gmail.com>
Date: Sat, 21 Feb 2026 12:54:09 +0800
Subject: [PATCH 03/17] Apply suggestions from code review
Co-authored-by: EugeneZelenko <eugene.zelenko at gmail.com>
---
clang-tools-extra/docs/ReleaseNotes.rst | 2 +-
.../clang-tidy/checks/bugprone/missing-end-comparison.rst | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b89e2c0c60bf3..3beaef6b538fa 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -105,7 +105,7 @@ New checks
- New :doc:`bugprone-missing-end-comparison
<clang-tidy/checks/bugprone/missing-end-comparison>` check.
- Finds instances where the result of a standard algorithm is used in a boolean
+ Finds instances where the result of a standard algorithm is used in a Boolean
context without being compared to the end iterator.
- New :doc:`bugprone-unsafe-to-allow-exceptions
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
index 4f1ac0d7657ab..c2c93e8f52325 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
@@ -3,15 +3,15 @@
bugprone-missing-end-comparison
===============================
-Finds instances where the result of a standard algorithm is used in a boolean
+Finds instances where the result of a standard algorithm is used in a Boolean
context without being compared to the end iterator.
-Standard algorithms such as `std::find`, `std::search`, and `std::lower_bound`
+Standard algorithms such as ``std::find``, ``std::search``, and ``std::lower_bound``
return an iterator to the element if found, or the end iterator otherwise.
-Using the result directly in a boolean context (like an `if` statement) is
+Using the result directly in a Boolean context (like an ``if`` statement) is
almost always a bug, as it only checks if the iterator itself evaluates to
-`true`, which may always be true for many iterator types (including pointers).
+``true``, which may always be true for many iterator types (including pointers).
Examples:
>From 46e59626253789f837d0d5273da22786efe65c78 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Sat, 21 Feb 2026 13:48:54 +0800
Subject: [PATCH 04/17] fix docs
---
.../bugprone/missing-end-comparison.rst | 49 ++++++++++---------
1 file changed, 25 insertions(+), 24 deletions(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
index c2c93e8f52325..6353bc5b2a3b8 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
@@ -6,12 +6,13 @@ bugprone-missing-end-comparison
Finds instances where the result of a standard algorithm is used in a Boolean
context without being compared to the end iterator.
-Standard algorithms such as ``std::find``, ``std::search``, and ``std::lower_bound``
-return an iterator to the element if found, or the end iterator otherwise.
+Standard algorithms such as ``std::find``, ``std::search``, and
+``std::lower_bound`` return an iterator to the element if found, or the end
+iterator otherwise.
Using the result directly in a Boolean context (like an ``if`` statement) is
almost always a bug, as it only checks if the iterator itself evaluates to
-``true``, which may always be true for many iterator types (including pointers).
+``true``, which may always be true for many iterator types.
Examples:
@@ -46,24 +47,24 @@ The check also handles range-based algorithms introduced in C++20.
Supported algorithms:
-- `std::find`
-- `std::find_if`
-- `std::find_if_not`
-- `std::search`
-- `std::search_n`
-- `std::find_end`
-- `std::find_first_of`
-- `std::lower_bound`
-- `std::upper_bound`
-- `std::partition_point`
-- `std::min_element`
-- `std::max_element`
-- `std::adjacent_find`
-- `std::is_sorted_until`
-- `std::ranges::find`
-- `std::ranges::find_if`
-- `std::ranges::find_if_not`
-- `std::ranges::lower_bound`
-- `std::ranges::upper_bound`
-- `std::ranges::min_element`
-- `std::ranges::max_element`
+- ``std::find``
+- ``std::find_if``
+- ``std::find_if_not``
+- ``std::search``
+- ``std::search_n``
+- ``std::find_end``
+- ``std::find_first_of``
+- ``std::lower_bound``
+- ``std::upper_bound``
+- ``std::partition_point``
+- ``std::min_element``
+- ``std::max_element``
+- ``std::adjacent_find``
+- ``std::is_sorted_until``
+- ``std::ranges::find``
+- ``std::ranges::find_if``
+- ``std::ranges::find_if_not``
+- ``std::ranges::lower_bound``
+- ``std::ranges::upper_bound``
+- ``std::ranges::min_element``
+- ``std::ranges::max_element``
>From a22e8990be9b69be1f40b75f8f1bc75b466ba1b2 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Sat, 21 Feb 2026 14:26:46 +0800
Subject: [PATCH 05/17] Fix execution releated issues
---
.../bugprone/MissingEndComparisonCheck.cpp | 13 ++++++-
.../bugprone/missing-end-comparison.cpp | 36 +++++++++++++++++++
2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 42ff99b121f84..9e351f7c97a4c 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -88,7 +88,18 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
if (Call->getNumArgs() < 2)
return;
- const Expr *EndArg = Call->getArg(1);
+ unsigned EndIdx = 1;
+ const Expr *FirstArg = Call->getArg(0);
+ if (const auto *Record =
+ FirstArg->getType().getNonReferenceType()->getAsCXXRecordDecl()) {
+ if (Record->getName().ends_with("_policy"))
+ EndIdx = 2;
+ }
+
+ if (Call->getNumArgs() <= EndIdx)
+ return;
+
+ const Expr *EndArg = Call->getArg(EndIdx);
// Filters nullptr, we assume the intent might be a valid check against null
if (EndArg->IgnoreParenCasts()->isNullPointerConstant(
*Result.Context, Expr::NPC_ValueDependentIsNull))
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
index 6bbeef5cbde6f..c27013a7714ec 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -20,6 +20,19 @@ namespace std {
template<class InputIt, class T>
InputIt find(InputIt first, InputIt last, const T& value);
+ namespace execution {
+ struct sequenced_policy {};
+ struct parallel_policy {};
+ inline constexpr sequenced_policy seq;
+ inline constexpr parallel_policy par;
+ }
+
+ template<class ExecutionPolicy, class InputIt, class T>
+ InputIt find(ExecutionPolicy&& policy, InputIt first, InputIt last, const T& value);
+
+ template<class ExecutionPolicy, class ForwardIt, class T>
+ ForwardIt lower_bound(ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, const T& value);
+
template<class ForwardIt, class T>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);
@@ -194,3 +207,26 @@ void test_other_namespace() {
int arr[] = {1};
if (other::find(arr, arr + 1, 1)) {}
}
+
+void test_execution_policy() {
+ int arr[] = {1, 2, 3};
+ int* begin = arr;
+ int* end = arr + 3;
+
+ if (std::find(std::execution::seq, begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(std::execution::seq, begin, end, 2) != end)) {}
+
+ if (std::find(std::execution::par, begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(std::execution::par, begin, end, 2) != end)) {}
+
+ auto it = std::find(std::execution::seq, begin, end, 2);
+ if (it) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((it != end)) {}
+
+ if (std::lower_bound(std::execution::seq, begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::lower_bound(std::execution::seq, begin, end, 2) != end)) {}
+}
>From 3905ac32c21e6ef39e86e262091a2d11c08d4ae1 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Sun, 22 Feb 2026 00:55:12 +0800
Subject: [PATCH 06/17] more algorithms support, better example, more testcases
---
.../bugprone/MissingEndComparisonCheck.cpp | 9 ++-
.../bugprone/missing-end-comparison.rst | 42 +++++-----
.../bugprone/missing-end-comparison.cpp | 76 +++++++++++++++++++
3 files changed, 104 insertions(+), 23 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 9e351f7c97a4c..9699ab48faf84 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -28,10 +28,11 @@ constexpr llvm::StringRef IteratorAlgorithms[] = {
"::std::adjacent_find", "::std::is_sorted_until"};
constexpr llvm::StringRef RangeAlgorithms[] = {
- "::std::ranges::find", "::std::ranges::find_if",
- "::std::ranges::find_if_not", "::std::ranges::lower_bound",
- "::std::ranges::upper_bound", "::std::ranges::min_element",
- "::std::ranges::max_element"};
+ "::std::ranges::find", "::std::ranges::find_if",
+ "::std::ranges::find_if_not", "::std::ranges::lower_bound",
+ "::std::ranges::upper_bound", "::std::ranges::min_element",
+ "::std::ranges::max_element", "::std::ranges::find_first_of",
+ "::std::ranges::adjacent_find", "::std::ranges::is_sorted_until"};
} // namespace
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
index 6353bc5b2a3b8..e4381382a136a 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
@@ -18,29 +18,30 @@ Examples:
.. code-block:: c++
- int arr[] = {1, 2, 3};
- int* begin = std::begin(arr);
- int* end = std::end(arr);
+ void example() {
+ int arr[] = {1, 2, 3};
+ int* begin = std::begin(arr);
+ int* end = std::end(arr);
- // Problematic:
- if (std::find(begin, end, 2)) {
- // ...
- }
+ if (std::find(begin, end, 2)) {
+ // ...
+ }
- // Fixed by the check:
- if ((std::find(begin, end, 2) != end)) {
- // ...
- }
+ // Fixed by the check:
+ if ((std::find(begin, end, 2) != end)) {
+ // ...
+ }
- // C++20 ranges:
- std::vector<int> v = {1, 2, 3};
- if (std::ranges::find(v, 2)) { // Problematic
- // ...
- }
+ // C++20 ranges:
+ int v[] = {1, 2, 3};
+ if (std::ranges::find(v, 2)) {
+ // ...
+ }
- // Fixed by the check:
- if ((std::ranges::find(v, 2) != std::ranges::end(v))) {
- // ...
+ // Fixed by the check:
+ if ((std::ranges::find(v, 2) != std::ranges::end(v))) {
+ // ...
+ }
}
The check also handles range-based algorithms introduced in C++20.
@@ -68,3 +69,6 @@ Supported algorithms:
- ``std::ranges::upper_bound``
- ``std::ranges::min_element``
- ``std::ranges::max_element``
+- ``std::ranges::find_first_of``
+- ``std::ranges::adjacent_find``
+- ``std::ranges::is_sorted_until``
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
index c27013a7714ec..8e581a88772bb 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -62,6 +62,30 @@ namespace std {
void* operator()(I first, S last, const T& value) const;
};
inline constexpr FindFn find;
+
+ struct FindFirstOfFn {
+ template<typename R1, typename R2>
+ void* operator()(R1&& r1, R2&& r2) const;
+ template<typename I1, typename S1, typename I2, typename S2>
+ void* operator()(I1 f1, S1 l1, I2 f2, S2 l2) const;
+ };
+ inline constexpr FindFirstOfFn find_first_of;
+
+ struct AdjacentFindFn {
+ template<typename R>
+ void* operator()(R&& r) const;
+ template<typename I, typename S>
+ void* operator()(I f, S l) const;
+ };
+ inline constexpr AdjacentFindFn adjacent_find;
+
+ struct IsSortedUntilFn {
+ template<typename R>
+ void* operator()(R&& r) const;
+ template<typename I, typename S>
+ void* operator()(I f, S l) const;
+ };
+ inline constexpr IsSortedUntilFn is_sorted_until;
}
}
@@ -132,6 +156,35 @@ void test_ranges() {
if (it) {}
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((it != std::ranges::end(v))) {}
+
+ std::vector<int> v1, v2;
+ int arr[] = {1, 2, 3};
+ int *begin = arr;
+ int *end = arr + 3;
+
+ if (std::ranges::find_first_of(v1, v2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find_first_of(v1, v2) != std::ranges::end(v1))) {}
+
+ if (std::ranges::find_first_of(begin, end, begin, end)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find_first_of(begin, end, begin, end) != end)) {}
+
+ if (std::ranges::adjacent_find(v1)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::adjacent_find(v1) != std::ranges::end(v1))) {}
+
+ if (std::ranges::adjacent_find(begin, end)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::adjacent_find(begin, end) != end)) {}
+
+ if (std::ranges::is_sorted_until(v1)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::is_sorted_until(v1) != std::ranges::end(v1))) {}
+
+ if (std::ranges::is_sorted_until(begin, end)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::is_sorted_until(begin, end) != end)) {}
}
void test_ranges_iterator_pair() {
@@ -230,3 +283,26 @@ void test_execution_policy() {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::lower_bound(std::execution::seq, begin, end, 2) != end)) {}
}
+
+void test_loops() {
+ int arr[] = {1, 2, 3};
+ int *begin = arr;
+ int *end = arr + 3;
+
+ while (std::find(begin, end, 2)) { break; }
+ // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: while ((std::find(begin, end, 2) != end)) { break; }
+
+ do { } while (std::find(begin, end, 2));
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: do { } while ((std::find(begin, end, 2) != end));
+
+ for (auto it = std::find(begin, end, 2); it; ) { break; }
+ // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: for (auto it = std::find(begin, end, 2); (it != end); ) { break; }
+
+ std::vector<int> v;
+ for (auto it = std::ranges::find(v, 2); !it; ) { break; }
+ // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: for (auto it = std::ranges::find(v, 2); (it == std::ranges::end(v)); ) { break; }
+}
>From 9cac8bf7a310e4631eb6eb24d6aa981384b096e0 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Sun, 22 Feb 2026 01:57:03 +0800
Subject: [PATCH 07/17] ~~
---
.../bugprone/MissingEndComparisonCheck.cpp | 70 ++++++++++++++-----
.../bugprone/missing-end-comparison.cpp | 6 ++
2 files changed, 58 insertions(+), 18 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 9699ab48faf84..2957e6920be09 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -34,6 +34,16 @@ constexpr llvm::StringRef RangeAlgorithms[] = {
"::std::ranges::max_element", "::std::ranges::find_first_of",
"::std::ranges::adjacent_find", "::std::ranges::is_sorted_until"};
+AST_MATCHER(DeclStmt, isConditionVariableStatement) {
+ return !match(
+ declStmt(hasAncestor(stmt(anyOf(
+ ifStmt(hasConditionVariableStatement(equalsNode(&Node))),
+ whileStmt(hasConditionVariableStatement(equalsNode(&Node))),
+ forStmt(hasConditionVariableStatement(equalsNode(&Node))))))),
+ Node, Finder->getASTContext())
+ .empty();
+}
+
} // namespace
void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
@@ -61,7 +71,8 @@ void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
// FIXME: This only handles variables initialized directly by the algorithm.
// We may need to introduce more accurate dataflow analysis in the future.
const auto VarWithAlgoInit =
- varDecl(hasInitializer(ignoringParenImpCasts(AnyAlgoCall)));
+ varDecl(hasInitializer(ignoringParenImpCasts(AnyAlgoCall)))
+ .bind("initVar");
const auto IsVariableBoolUsage =
anyOf(implicitCastExpr(hasCastKind(CK_PointerToBoolean),
@@ -163,25 +174,48 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
"result of standard algorithm used in boolean context; did "
"you mean to compare with the end iterator?");
- if (!EndExprText.empty()) {
- if (IsNegated) {
- // !it -> (it == end)
- Diag << FixItHint::CreateReplacement(NotOp->getOperatorLoc(), "(");
- Diag << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
- *Result.SourceManager,
- Result.Context->getLangOpts()),
- " == " + EndExprText + ")");
- } else {
- // it -> (it != end)
- Diag << FixItHint::CreateInsertion(BoolOp->getBeginLoc(), "(");
- Diag << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
- *Result.SourceManager,
- Result.Context->getLangOpts()),
- " != " + EndExprText + ")");
+ if (EndExprText.empty())
+ return;
+
+ // Suppress fix-it if the expression is part of a variable declaration or a
+ // condition variable declaration.
+ if (const auto *InitVar = Result.Nodes.getNodeAs<VarDecl>("initVar")) {
+ if (InitVar->getType()->isBooleanType())
+ return;
+
+ const auto &Parents = Result.Context->getParents(*InitVar);
+ if (!Parents.empty()) {
+ if (const auto *ParentDecl = Parents[0].get<DeclStmt>()) {
+ if (!match(declStmt(isConditionVariableStatement()), *ParentDecl,
+ *Result.Context)
+ .empty()) {
+ return;
+ }
+ }
}
}
+
+ const auto &Parents = Result.Context->getParents(*BoolOp);
+ if (!Parents.empty() && Parents[0].get<VarDecl>())
+ return;
+
+ if (IsNegated) {
+ // !it -> (it == end)
+ Diag << FixItHint::CreateReplacement(NotOp->getOperatorLoc(), "(");
+ Diag << FixItHint::CreateInsertion(
+ Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
+ *Result.SourceManager,
+ Result.Context->getLangOpts()),
+ " == " + EndExprText + ")");
+ } else {
+ // it -> (it != end)
+ Diag << FixItHint::CreateInsertion(BoolOp->getBeginLoc(), "(");
+ Diag << FixItHint::CreateInsertion(
+ Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
+ *Result.SourceManager,
+ Result.Context->getLangOpts()),
+ " != " + EndExprText + ")");
+ }
}
} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
index 8e581a88772bb..1bf9be4700779 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -306,3 +306,9 @@ void test_loops() {
// CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: for (auto it = std::ranges::find(v, 2); (it == std::ranges::end(v)); ) { break; }
}
+
+void test_invalid_fixit() {
+ int arr[] = {1, 2, 3};
+ if (int* it2 = std::find(arr, arr + 3, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+}
>From 22f65eca46fd4f9761e0dcf7f444b2fc24c43da2 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Sun, 22 Feb 2026 23:37:42 +0800
Subject: [PATCH 08/17] better?
---
.../bugprone/MissingEndComparisonCheck.cpp | 189 ++++++++++--------
.../Inputs/missing-end-comparison/fake_std.h | 93 +++++++++
.../bugprone/missing-end-comparison.cpp | 157 ++++-----------
3 files changed, 231 insertions(+), 208 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/missing-end-comparison/fake_std.h
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 2957e6920be09..2e1c898ecdcdc 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -44,14 +44,95 @@ AST_MATCHER(DeclStmt, isConditionVariableStatement) {
.empty();
}
+static std::optional<std::string>
+getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
+ const FunctionDecl *Callee = Call->getDirectCallee();
+ assert(Callee && Callee->getNumParams() > 0 &&
+ "Matcher should ensure Callee has parameters");
+
+ // Range overloads take a reference (R&&), Iterator overloads pass by value.
+ const bool IsIterPair =
+ !Callee->getParamDecl(0)->getType()->isReferenceType();
+
+ if (IsIterPair) {
+ if (Call->getNumArgs() < 3)
+ return std::nullopt;
+ // find(CPO, Iter, Sent, Val...) -> Sent is Arg 2.
+ const Expr *EndArg = Call->getArg(2);
+ return tooling::fixit::getText(*EndArg, *Result.Context).str();
+ }
+
+ if (Call->getNumArgs() < 2)
+ return std::nullopt;
+ // find(CPO, Range, Val, Proj) -> Range is Arg 1.
+ const Expr *RangeArg = Call->getArg(1);
+ // Avoid potential side-effects
+ const Expr *InnerRange = RangeArg->IgnoreParenImpCasts();
+ if (isa<DeclRefExpr>(InnerRange) || isa<MemberExpr>(InnerRange)) {
+ const StringRef RangeText =
+ tooling::fixit::getText(*RangeArg, *Result.Context);
+ if (!RangeText.empty())
+ return ("std::ranges::end(" + RangeText + ")").str();
+ }
+ return "";
+}
+
+static std::optional<std::string>
+getStandardEndText(const MatchFinder::MatchResult &Result,
+ const CallExpr *Call) {
+ if (Call->getNumArgs() < 2)
+ return std::nullopt;
+
+ unsigned EndIdx = 1;
+ const Expr *FirstArg = Call->getArg(0);
+ if (const auto *Record =
+ FirstArg->getType().getNonReferenceType()->getAsCXXRecordDecl()) {
+ if (Record->getName().ends_with("_policy"))
+ EndIdx = 2;
+ }
+
+ if (Call->getNumArgs() <= EndIdx)
+ return std::nullopt;
+
+ const Expr *EndArg = Call->getArg(EndIdx);
+ // Filters nullptr, we assume the intent might be a valid check against null
+ if (EndArg->IgnoreParenCasts()->isNullPointerConstant(
+ *Result.Context, Expr::NPC_ValueDependentIsNull))
+ return std::nullopt;
+
+ return tooling::fixit::getText(*EndArg, *Result.Context).str();
+}
+
+static const UnaryOperator *getLNotAncestor(const Expr *E,
+ ASTContext &Context) {
+ const Expr *CurrentExpr = E;
+ while (true) {
+ auto Parents = Context.getParents(*CurrentExpr);
+ if (Parents.empty())
+ break;
+ if (const auto *P = Parents[0].get<ParenExpr>()) {
+ CurrentExpr = P;
+ continue;
+ }
+ if (const auto *U = Parents[0].get<UnaryOperator>()) {
+ if (U->getOpcode() == UO_LNot)
+ return U;
+ }
+ break;
+ }
+ return nullptr;
+}
+
} // namespace
void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
- const auto StdAlgoCall = callExpr(
- callee(functionDecl(hasAnyName(IteratorAlgorithms), isInStdNamespace())));
+ const auto StdAlgoCall = callExpr(callee(functionDecl(
+ hasAnyName(IteratorAlgorithms), unless(parameterCountIs(0)))));
+ // Captures customization point object
const auto RangesCall = cxxOperatorCallExpr(
hasOverloadedOperatorName("()"),
+ callee(cxxMethodDecl(unless(parameterCountIs(0)))),
hasArgument(0, declRefExpr(to(
varDecl(hasAnyName(RangeAlgorithms)).bind("cpo")))));
@@ -71,8 +152,7 @@ void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
// FIXME: This only handles variables initialized directly by the algorithm.
// We may need to introduce more accurate dataflow analysis in the future.
const auto VarWithAlgoInit =
- varDecl(hasInitializer(ignoringParenImpCasts(AnyAlgoCall)))
- .bind("initVar");
+ varDecl(hasInitializer(expr(hasDescendant(AnyAlgoCall)))).bind("initVar");
const auto IsVariableBoolUsage =
anyOf(implicitCastExpr(hasCastKind(CK_PointerToBoolean),
@@ -87,94 +167,31 @@ void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
}
void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *Call = Result.Nodes.getNodeAs<CallExpr>("algoCall");
const auto *BoolOp = Result.Nodes.getNodeAs<Expr>("boolOp");
- const auto *CPO = Result.Nodes.getNodeAs<VarDecl>("cpo");
-
- if (!Call || !BoolOp)
- return;
+ assert(BoolOp);
- std::string EndExprText;
+ std::optional<std::string> EndExprText;
- if (!CPO) {
- if (Call->getNumArgs() < 2)
- return;
-
- unsigned EndIdx = 1;
- const Expr *FirstArg = Call->getArg(0);
- if (const auto *Record =
- FirstArg->getType().getNonReferenceType()->getAsCXXRecordDecl()) {
- if (Record->getName().ends_with("_policy"))
- EndIdx = 2;
- }
-
- if (Call->getNumArgs() <= EndIdx)
- return;
-
- const Expr *EndArg = Call->getArg(EndIdx);
- // Filters nullptr, we assume the intent might be a valid check against null
- if (EndArg->IgnoreParenCasts()->isNullPointerConstant(
- *Result.Context, Expr::NPC_ValueDependentIsNull))
- return;
-
- EndExprText = tooling::fixit::getText(*EndArg, *Result.Context).str();
+ if (Result.Nodes.getNodeAs<VarDecl>("cpo")) {
+ const auto *Call = Result.Nodes.getNodeAs<CallExpr>("algoCall");
+ EndExprText = getRangesEndText(Result, Call);
+ } else if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("algoCall")) {
+ EndExprText = getStandardEndText(Result, Call);
} else {
- const FunctionDecl *Callee = Call->getDirectCallee();
- if (!Callee || Callee->getNumParams() == 0)
- return;
-
- // Range overloads take a reference (R&&), Iterator overloads pass by value.
- const bool IsIterPair =
- !Callee->getParamDecl(0)->getType()->isReferenceType();
-
- if (IsIterPair) {
- if (Call->getNumArgs() < 3)
- return;
- // find(CPO, Iter, Sent, Val...) -> Sent is Arg 2.
- const Expr *EndArg = Call->getArg(2);
- EndExprText = tooling::fixit::getText(*EndArg, *Result.Context).str();
- } else {
- if (Call->getNumArgs() < 2)
- return;
- // find(CPO, Range, Val, Proj) -> Range is Arg 1.
- const Expr *RangeArg = Call->getArg(1);
- // Avoid potential side-effects
- const Expr *InnerRange = RangeArg->IgnoreParenImpCasts();
- if (isa<DeclRefExpr>(InnerRange) || isa<MemberExpr>(InnerRange)) {
- const StringRef RangeText =
- tooling::fixit::getText(*RangeArg, *Result.Context);
- if (!RangeText.empty())
- EndExprText = ("std::ranges::end(" + RangeText + ")").str();
- }
- }
+ llvm_unreachable("Matcher should bind 'algoCall' or 'cpo'");
}
- bool IsNegated = false;
- const UnaryOperator *NotOp = nullptr;
- const Expr *CurrentExpr = BoolOp;
- while (true) {
- auto Parents = Result.Context->getParents(*CurrentExpr);
- if (Parents.empty())
- break;
- if (const auto *P = Parents[0].get<ParenExpr>()) {
- CurrentExpr = P;
- continue;
- }
- if (const auto *U = Parents[0].get<UnaryOperator>()) {
- if (U->getOpcode() == UO_LNot) {
- NotOp = U;
- IsNegated = true;
- }
- }
- break;
- }
+ if (!EndExprText)
+ return;
+
+ const UnaryOperator *NotOp = getLNotAncestor(BoolOp, *Result.Context);
+ const bool IsNegated = NotOp != nullptr;
- const auto Diag =
- diag(BoolOp->getBeginLoc(),
- "result of standard algorithm used in boolean context; did "
- "you mean to compare with the end iterator?");
+ const auto Diag = diag(BoolOp->getBeginLoc(),
+ "result of standard algorithm used as 'bool'; did you "
+ "mean to compare with the end iterator?");
- if (EndExprText.empty())
+ if (EndExprText->empty())
return;
// Suppress fix-it if the expression is part of a variable declaration or a
@@ -206,7 +223,7 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
*Result.SourceManager,
Result.Context->getLangOpts()),
- " == " + EndExprText + ")");
+ " == " + *EndExprText + ")");
} else {
// it -> (it != end)
Diag << FixItHint::CreateInsertion(BoolOp->getBeginLoc(), "(");
@@ -214,7 +231,7 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
*Result.SourceManager,
Result.Context->getLangOpts()),
- " != " + EndExprText + ")");
+ " != " + *EndExprText + ")");
}
}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/missing-end-comparison/fake_std.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/missing-end-comparison/fake_std.h
new file mode 100644
index 0000000000000..89330e4015089
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/missing-end-comparison/fake_std.h
@@ -0,0 +1,93 @@
+#ifndef MISSING_END_COMPARISON_FAKE_STD_H
+#define MISSING_END_COMPARISON_FAKE_STD_H
+
+namespace std {
+ template<typename T> struct iterator_traits;
+ struct forward_iterator_tag {};
+
+ typedef long int ptrdiff_t;
+ typedef decltype(nullptr) nullptr_t;
+
+ template<typename T>
+ struct vector {
+ typedef T* iterator;
+ typedef const T* const_iterator;
+ iterator begin();
+ iterator end();
+ const_iterator begin() const;
+ const_iterator end() const;
+ };
+
+ template<class InputIt, class T>
+ InputIt find(InputIt first, InputIt last, const T& value);
+
+ namespace execution {
+ struct sequenced_policy {};
+ struct parallel_policy {};
+ inline constexpr sequenced_policy seq;
+ inline constexpr parallel_policy par;
+ }
+
+ template<class ExecutionPolicy, class InputIt, class T>
+ InputIt find(ExecutionPolicy&& policy, InputIt first, InputIt last, const T& value);
+
+ template<class ExecutionPolicy, class ForwardIt, class T>
+ ForwardIt lower_bound(ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, const T& value);
+
+ template<class ForwardIt, class T>
+ ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);
+
+ template<class ForwardIt, class ForwardIt2>
+ ForwardIt search(ForwardIt first, ForwardIt last, ForwardIt first2, ForwardIt2 last2);
+
+ template<class ForwardIt>
+ ForwardIt min_element(ForwardIt first, ForwardIt last);
+
+ template<class InputIt1, class InputIt2>
+ struct pair {
+ InputIt1 first;
+ InputIt2 second;
+ };
+
+ namespace ranges {
+ template<typename T>
+ void* begin(T& t);
+ template<typename T>
+ void* end(T& t);
+
+ struct FindFn {
+ template<typename Range, typename T>
+ void* operator()(Range&& r, const T& value) const;
+
+ template<typename I, typename S, typename T>
+ void* operator()(I first, S last, const T& value) const;
+ };
+ inline constexpr FindFn find;
+
+ struct FindFirstOfFn {
+ template<typename R1, typename R2>
+ void* operator()(R1&& r1, R2&& r2) const;
+ template<typename I1, typename S1, typename I2, typename S2>
+ void* operator()(I1 f1, S1 l1, I2 f2, S2 l2) const;
+ };
+ inline constexpr FindFirstOfFn find_first_of;
+
+ struct AdjacentFindFn {
+ template<typename R>
+ void* operator()(R&& r) const;
+ template<typename I, typename S>
+ void* operator()(I f, S l) const;
+ };
+ inline constexpr AdjacentFindFn adjacent_find;
+
+ struct IsSortedUntilFn {
+ template<typename R>
+ void* operator()(R&& r) const;
+ template<typename I, typename S>
+ void* operator()(I f, S l) const;
+ };
+ inline constexpr IsSortedUntilFn is_sorted_until;
+ }
+} // namespace std
+
+#endif // MISSING_END_COMPARISON_FAKE_STD_H
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
index 1bf9be4700779..f58f441bd05aa 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -1,99 +1,12 @@
-// RUN: %check_clang_tidy -std=c++20 %s bugprone-missing-end-comparison %t
-
-namespace std {
- template<typename T> struct iterator_traits;
- struct forward_iterator_tag {};
-
- typedef long int ptrdiff_t;
- typedef decltype(nullptr) nullptr_t;
-
- template<typename T>
- struct vector {
- typedef T* iterator;
- typedef const T* const_iterator;
- iterator begin();
- iterator end();
- const_iterator begin() const;
- const_iterator end() const;
- };
-
- template<class InputIt, class T>
- InputIt find(InputIt first, InputIt last, const T& value);
-
- namespace execution {
- struct sequenced_policy {};
- struct parallel_policy {};
- inline constexpr sequenced_policy seq;
- inline constexpr parallel_policy par;
- }
-
- template<class ExecutionPolicy, class InputIt, class T>
- InputIt find(ExecutionPolicy&& policy, InputIt first, InputIt last, const T& value);
-
- template<class ExecutionPolicy, class ForwardIt, class T>
- ForwardIt lower_bound(ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, const T& value);
-
- template<class ForwardIt, class T>
- ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);
-
- template<class ForwardIt, class ForwardIt2>
- ForwardIt search(ForwardIt first, ForwardIt last, ForwardIt first2, ForwardIt2 last2);
-
- template<class ForwardIt>
- ForwardIt min_element(ForwardIt first, ForwardIt last);
-
- template<class InputIt1, class InputIt2>
- struct pair {
- InputIt1 first;
- InputIt2 second;
- };
-
- namespace ranges {
- template<typename T>
- void* begin(T& t);
- template<typename T>
- void* end(T& t);
-
- struct FindFn {
- template<typename Range, typename T>
- void* operator()(Range&& r, const T& value) const;
-
- template<typename I, typename S, typename T>
- void* operator()(I first, S last, const T& value) const;
- };
- inline constexpr FindFn find;
-
- struct FindFirstOfFn {
- template<typename R1, typename R2>
- void* operator()(R1&& r1, R2&& r2) const;
- template<typename I1, typename S1, typename I2, typename S2>
- void* operator()(I1 f1, S1 l1, I2 f2, S2 l2) const;
- };
- inline constexpr FindFirstOfFn find_first_of;
-
- struct AdjacentFindFn {
- template<typename R>
- void* operator()(R&& r) const;
- template<typename I, typename S>
- void* operator()(I f, S l) const;
- };
- inline constexpr AdjacentFindFn adjacent_find;
-
- struct IsSortedUntilFn {
- template<typename R>
- void* operator()(R&& r) const;
- template<typename I, typename S>
- void* operator()(I f, S l) const;
- };
- inline constexpr IsSortedUntilFn is_sorted_until;
- }
-}
+// RUN: %check_clang_tidy -std=c++20 %s bugprone-missing-end-comparison %t -- -- -I %S/Inputs/missing-end-comparison
+
+#include "fake_std.h"
struct CustomIterator {
int* ptr;
using iterator_category = std::forward_iterator_tag;
using value_type = int;
- using difference_type = long;
+ using difference_type = std::ptrdiff_t;
using pointer = int*;
using reference = int&;
@@ -111,26 +24,26 @@ void test_raw_pointers() {
int* end = arr + 3;
if (std::find(begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::find(begin, end, 2) != end)) {}
while (std::lower_bound(begin, end, 2)) { break; }
- // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: while ((std::lower_bound(begin, end, 2) != end)) { break; }
if (!std::find(begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::find(begin, end, 2) == end)) {}
}
void test_vector() {
std::vector<int> v;
if (std::find(v.begin(), v.end(), 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::find(v.begin(), v.end(), 2) != v.end())) {}
if (std::min_element(v.begin(), v.end())) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::min_element(v.begin(), v.end()) != v.end())) {}
}
@@ -138,23 +51,23 @@ void test_variable_tracking() {
int arr[] = {1, 2, 3};
auto it = std::find(arr, arr + 3, 2);
if (it) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((it != arr + 3)) {}
if (!it) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((it == arr + 3)) {}
}
void test_ranges() {
std::vector<int> v;
if (std::ranges::find(v, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::find(v, 2) != std::ranges::end(v))) {}
auto it = std::ranges::find(v, 2);
if (it) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((it != std::ranges::end(v))) {}
std::vector<int> v1, v2;
@@ -163,27 +76,27 @@ void test_ranges() {
int *end = arr + 3;
if (std::ranges::find_first_of(v1, v2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::find_first_of(v1, v2) != std::ranges::end(v1))) {}
if (std::ranges::find_first_of(begin, end, begin, end)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::find_first_of(begin, end, begin, end) != end)) {}
if (std::ranges::adjacent_find(v1)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::adjacent_find(v1) != std::ranges::end(v1))) {}
if (std::ranges::adjacent_find(begin, end)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::adjacent_find(begin, end) != end)) {}
if (std::ranges::is_sorted_until(v1)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::is_sorted_until(v1) != std::ranges::end(v1))) {}
if (std::ranges::is_sorted_until(begin, end)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::is_sorted_until(begin, end) != end)) {}
}
@@ -192,14 +105,14 @@ void test_ranges_iterator_pair() {
int *begin = arr;
int *end = arr + 3;
if (std::ranges::find(begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::find(begin, end, 2) != end)) {}
}
void test_side_effects() {
std::vector<int> get_vec();
if (std::ranges::find(get_vec(), 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
}
void test_negative() {
@@ -212,14 +125,14 @@ void test_negative() {
void test_nested_parens() {
int arr[] = {1, 2, 3};
if (!((std::find(arr, arr + 3, 2)))) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((((std::find(arr, arr + 3, 2))) == arr + 3)) {}
}
struct Data { std::vector<int> v; };
void test_member_expr(Data& d) {
if (std::ranges::find(d.v, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::ranges::find(d.v, 2) != std::ranges::end(d.v))) {}
}
@@ -231,14 +144,14 @@ void test_double_negation() {
int arr[] = {1, 2, 3};
auto it = std::find(arr, arr + 3, 2);
if (!!it) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if (!(it == arr + 3)) {}
}
void test_custom_iterator() {
CustomIterator begin{nullptr}, end{nullptr};
if (std::find(begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'
// CHECK-FIXES: if ((std::find(begin, end, 2) != end)) {}
}
@@ -248,7 +161,7 @@ void test_search() {
int* end = arr + 3;
if (std::search(begin, end, begin, end)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'
// CHECK-FIXES: if ((std::search(begin, end, begin, end) != end)) {}
}
@@ -267,20 +180,20 @@ void test_execution_policy() {
int* end = arr + 3;
if (std::find(std::execution::seq, begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::find(std::execution::seq, begin, end, 2) != end)) {}
if (std::find(std::execution::par, begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::find(std::execution::par, begin, end, 2) != end)) {}
auto it = std::find(std::execution::seq, begin, end, 2);
if (it) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((it != end)) {}
if (std::lower_bound(std::execution::seq, begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: if ((std::lower_bound(std::execution::seq, begin, end, 2) != end)) {}
}
@@ -290,25 +203,25 @@ void test_loops() {
int *end = arr + 3;
while (std::find(begin, end, 2)) { break; }
- // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: while ((std::find(begin, end, 2) != end)) { break; }
do { } while (std::find(begin, end, 2));
- // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: do { } while ((std::find(begin, end, 2) != end));
for (auto it = std::find(begin, end, 2); it; ) { break; }
- // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: for (auto it = std::find(begin, end, 2); (it != end); ) { break; }
std::vector<int> v;
for (auto it = std::ranges::find(v, 2); !it; ) { break; }
- // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: for (auto it = std::ranges::find(v, 2); (it == std::ranges::end(v)); ) { break; }
}
void test_invalid_fixit() {
int arr[] = {1, 2, 3};
if (int* it2 = std::find(arr, arr + 3, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: result of standard algorithm used in boolean context; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
}
>From 22d860b572850689258e721776703d1d52ebb368 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Mon, 23 Feb 2026 00:28:07 +0800
Subject: [PATCH 09/17] fixup fixup fixup
---
.../clang-tidy/bugprone/MissingEndComparisonCheck.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 2e1c898ecdcdc..086d2459fd857 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -44,6 +44,8 @@ AST_MATCHER(DeclStmt, isConditionVariableStatement) {
.empty();
}
+} // namespace
+
static std::optional<std::string>
getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
const FunctionDecl *Callee = Call->getDirectCallee();
@@ -123,8 +125,6 @@ static const UnaryOperator *getLNotAncestor(const Expr *E,
return nullptr;
}
-} // namespace
-
void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
const auto StdAlgoCall = callExpr(callee(functionDecl(
hasAnyName(IteratorAlgorithms), unless(parameterCountIs(0)))));
>From 0ad8a89e44658d38d09304d4ff880657b33356b2 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Mon, 23 Feb 2026 22:09:38 +0800
Subject: [PATCH 10/17] Add option
---
.../bugprone/MissingEndComparisonCheck.cpp | 75 +++++++++++++++----
.../bugprone/MissingEndComparisonCheck.h | 7 +-
.../bugprone/missing-end-comparison.rst | 14 ++++
.../missing-end-comparison-custom.cpp | 45 +++++++++++
.../bugprone/missing-end-comparison.cpp | 2 +-
5 files changed, 124 insertions(+), 19 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-custom.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 086d2459fd857..cc8f70445445e 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "MissingEndComparisonCheck.h"
+#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
@@ -34,18 +35,40 @@ constexpr llvm::StringRef RangeAlgorithms[] = {
"::std::ranges::max_element", "::std::ranges::find_first_of",
"::std::ranges::adjacent_find", "::std::ranges::is_sorted_until"};
-AST_MATCHER(DeclStmt, isConditionVariableStatement) {
- return !match(
- declStmt(hasAncestor(stmt(anyOf(
- ifStmt(hasConditionVariableStatement(equalsNode(&Node))),
- whileStmt(hasConditionVariableStatement(equalsNode(&Node))),
- forStmt(hasConditionVariableStatement(equalsNode(&Node))))))),
- Node, Finder->getASTContext())
- .empty();
+static bool isConditionVar(const DeclStmt *S, ASTContext &Ctx) {
+ const auto &Parents = Ctx.getParents(*S);
+ if (Parents.empty())
+ return false;
+
+ const auto *ParentStmt = Parents[0].get<Stmt>();
+ if (!ParentStmt)
+ return false;
+
+ if (const auto *If = dyn_cast<IfStmt>(ParentStmt))
+ return If->getConditionVariableDeclStmt() == S;
+ if (const auto *While = dyn_cast<WhileStmt>(ParentStmt))
+ return While->getConditionVariableDeclStmt() == S;
+ if (const auto *For = dyn_cast<ForStmt>(ParentStmt))
+ return For->getConditionVariableDeclStmt() == S;
+
+ return false;
}
} // namespace
+MissingEndComparisonCheck::MissingEndComparisonCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ ExtraAlgorithms(
+ utils::options::parseStringList(Options.get("ExtraAlgorithms", ""))) {
+}
+
+void MissingEndComparisonCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "ExtraAlgorithms",
+ utils::options::serializeStringList(ExtraAlgorithms));
+}
+
static std::optional<std::string>
getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
const FunctionDecl *Callee = Call->getDirectCallee();
@@ -85,6 +108,23 @@ getStandardEndText(const MatchFinder::MatchResult &Result,
if (Call->getNumArgs() < 2)
return std::nullopt;
+ // Heuristic: if the first argument is a record type and the types of the
+ // first two arguments are distinct, we assume it's a range algorithm.
+ if (Call->getNumArgs() == 2) {
+ const Expr *Arg0 = Call->getArg(0);
+ const Expr *Arg1 = Call->getArg(1);
+ QualType T0 = Arg0->getType().getCanonicalType();
+ QualType T1 = Arg1->getType().getCanonicalType();
+
+ if (T0.getNonReferenceType() != T1.getNonReferenceType() &&
+ T0.getNonReferenceType()->isRecordType()) {
+ const StringRef ContainerText =
+ tooling::fixit::getText(*Arg0, *Result.Context);
+ if (!ContainerText.empty())
+ return ("std::end(" + ContainerText + ")").str();
+ }
+ }
+
unsigned EndIdx = 1;
const Expr *FirstArg = Call->getArg(0);
if (const auto *Record =
@@ -105,8 +145,8 @@ getStandardEndText(const MatchFinder::MatchResult &Result,
return tooling::fixit::getText(*EndArg, *Result.Context).str();
}
-static const UnaryOperator *getLNotAncestor(const Expr *E,
- ASTContext &Context) {
+static const UnaryOperator *getParentLogicalNot(const Expr *E,
+ ASTContext &Context) {
const Expr *CurrentExpr = E;
while (true) {
auto Parents = Context.getParents(*CurrentExpr);
@@ -126,8 +166,14 @@ static const UnaryOperator *getLNotAncestor(const Expr *E,
}
void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
+ llvm::SmallVector<StringRef, 32> ExpandedIteratorAlgorithms;
+ ExpandedIteratorAlgorithms.append(std::begin(IteratorAlgorithms),
+ std::end(IteratorAlgorithms));
+ ExpandedIteratorAlgorithms.append(ExtraAlgorithms.begin(),
+ ExtraAlgorithms.end());
+
const auto StdAlgoCall = callExpr(callee(functionDecl(
- hasAnyName(IteratorAlgorithms), unless(parameterCountIs(0)))));
+ hasAnyName(ExpandedIteratorAlgorithms), unless(parameterCountIs(0)))));
// Captures customization point object
const auto RangesCall = cxxOperatorCallExpr(
@@ -184,7 +230,7 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
if (!EndExprText)
return;
- const UnaryOperator *NotOp = getLNotAncestor(BoolOp, *Result.Context);
+ const UnaryOperator *NotOp = getParentLogicalNot(BoolOp, *Result.Context);
const bool IsNegated = NotOp != nullptr;
const auto Diag = diag(BoolOp->getBeginLoc(),
@@ -203,11 +249,8 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
const auto &Parents = Result.Context->getParents(*InitVar);
if (!Parents.empty()) {
if (const auto *ParentDecl = Parents[0].get<DeclStmt>()) {
- if (!match(declStmt(isConditionVariableStatement()), *ParentDecl,
- *Result.Context)
- .empty()) {
+ if (isConditionVar(ParentDecl, *Result.Context))
return;
- }
}
}
}
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.h b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.h
index 75a2afab5a3a9..816f1d848430b 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.h
@@ -20,13 +20,16 @@ namespace clang::tidy::bugprone {
/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/missing-end-comparison.html
class MissingEndComparisonCheck : public ClangTidyCheck {
public:
- MissingEndComparisonCheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context) {}
+ MissingEndComparisonCheck(StringRef Name, ClangTidyContext *Context);
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
+
+private:
+ std::vector<StringRef> ExtraAlgorithms;
};
} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
index e4381382a136a..389e8635b983e 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
@@ -72,3 +72,17 @@ Supported algorithms:
- ``std::ranges::find_first_of``
- ``std::ranges::adjacent_find``
- ``std::ranges::is_sorted_until``
+
+Options
+-------
+
+.. option:: ExtraAlgorithms
+
+ A semicolon-separated list of extra algorithms to check.
+ The list can contain:
+
+ - Iterator-based algorithms. These should follow the standard iterator
+ pattern: ``func(Iter, Iter, ...)``.
+ - Range-based algorithms. These are heuristically detected if they take
+ exactly two arguments and the first argument is a container or range.
+ The fix will insert ``std::end(Container)``.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-custom.cpp
new file mode 100644
index 0000000000000..6e0916a689605
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-custom.cpp
@@ -0,0 +1,45 @@
+// RUN: %check_clang_tidy -std=c++20 %s bugprone-missing-end-comparison %t \
+// RUN: -config="{CheckOptions: {bugprone-missing-end-comparison.ExtraAlgorithms: '::my_lib::find;::my_lib::find_range'}}" \
+// RUN: -- -I %S/Inputs/missing-end-comparison
+
+#include "fake_std.h"
+
+namespace std {
+ template<typename T>
+ auto end(T& t) -> decltype(t.end()) { return t.end(); }
+}
+
+namespace my_lib {
+ template<typename Iter, typename T>
+ Iter find(Iter first, Iter last, const T& value) {
+ return first;
+ }
+
+ template<typename Range, typename T>
+ auto find_range(Range&& range, const T& value) {
+ return range.begin();
+ }
+}
+
+void test_custom_algorithms() {
+ std::vector<int> v;
+ if (my_lib::find(v.begin(), v.end(), 42)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((my_lib::find(v.begin(), v.end(), 42) != v.end())) {}
+
+ if (my_lib::find_range(v, 42)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((my_lib::find_range(v, 42) != std::end(v))) {}
+}
+
+// Make sure we still check the original algorithms list.
+void test_vector() {
+ std::vector<int> v;
+ if (std::find(v.begin(), v.end(), 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(v.begin(), v.end(), 2) != v.end())) {}
+
+ if (std::min_element(v.begin(), v.end())) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::min_element(v.begin(), v.end()) != v.end())) {}
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
index f58f441bd05aa..95f8f279c9027 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -220,7 +220,7 @@ void test_loops() {
// CHECK-FIXES: for (auto it = std::ranges::find(v, 2); (it == std::ranges::end(v)); ) { break; }
}
-void test_invalid_fixit() {
+void test_condition_variable_suppression() {
int arr[] = {1, 2, 3};
if (int* it2 = std::find(arr, arr + 3, 2)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
>From cf2f25d303d7eaecdb8357779450532c67237245 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Mon, 23 Feb 2026 22:41:45 +0800
Subject: [PATCH 11/17] fixup fixup fixup fixup fixup
---
.../bugprone/MissingEndComparisonCheck.cpp | 33 +++++++++----------
1 file changed, 16 insertions(+), 17 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index cc8f70445445e..c075b97d454b6 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -34,6 +34,20 @@ constexpr llvm::StringRef RangeAlgorithms[] = {
"::std::ranges::upper_bound", "::std::ranges::min_element",
"::std::ranges::max_element", "::std::ranges::find_first_of",
"::std::ranges::adjacent_find", "::std::ranges::is_sorted_until"};
+} // namespace
+
+MissingEndComparisonCheck::MissingEndComparisonCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ ExtraAlgorithms(
+ utils::options::parseStringList(Options.get("ExtraAlgorithms", ""))) {
+}
+
+void MissingEndComparisonCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "ExtraAlgorithms",
+ utils::options::serializeStringList(ExtraAlgorithms));
+}
static bool isConditionVar(const DeclStmt *S, ASTContext &Ctx) {
const auto &Parents = Ctx.getParents(*S);
@@ -54,21 +68,6 @@ static bool isConditionVar(const DeclStmt *S, ASTContext &Ctx) {
return false;
}
-} // namespace
-
-MissingEndComparisonCheck::MissingEndComparisonCheck(StringRef Name,
- ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context),
- ExtraAlgorithms(
- utils::options::parseStringList(Options.get("ExtraAlgorithms", ""))) {
-}
-
-void MissingEndComparisonCheck::storeOptions(
- ClangTidyOptions::OptionMap &Opts) {
- Options.store(Opts, "ExtraAlgorithms",
- utils::options::serializeStringList(ExtraAlgorithms));
-}
-
static std::optional<std::string>
getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
const FunctionDecl *Callee = Call->getDirectCallee();
@@ -113,8 +112,8 @@ getStandardEndText(const MatchFinder::MatchResult &Result,
if (Call->getNumArgs() == 2) {
const Expr *Arg0 = Call->getArg(0);
const Expr *Arg1 = Call->getArg(1);
- QualType T0 = Arg0->getType().getCanonicalType();
- QualType T1 = Arg1->getType().getCanonicalType();
+ const QualType T0 = Arg0->getType().getCanonicalType();
+ const QualType T1 = Arg1->getType().getCanonicalType();
if (T0.getNonReferenceType() != T1.getNonReferenceType() &&
T0.getNonReferenceType()->isRecordType()) {
>From cb814401e3e58d867074cc83bd8b861824abaf3d Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Tue, 24 Feb 2026 00:49:25 +0800
Subject: [PATCH 12/17] better???
---
.../checks/bugprone/missing-end-comparison.rst | 3 +++
.../checkers/bugprone/missing-end-comparison.cpp | 10 ++++++++++
2 files changed, 13 insertions(+)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
index 389e8635b983e..fb68016a0259e 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/missing-end-comparison.rst
@@ -83,6 +83,9 @@ Options
- Iterator-based algorithms. These should follow the standard iterator
pattern: ``func(Iter, Iter, ...)``.
+
- Range-based algorithms. These are heuristically detected if they take
exactly two arguments and the first argument is a container or range.
The fix will insert ``std::end(Container)``.
+
+ Default is an empty string.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
index 95f8f279c9027..189ff972db2bd 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -224,4 +224,14 @@ void test_condition_variable_suppression() {
int arr[] = {1, 2, 3};
if (int* it2 = std::find(arr, arr + 3, 2)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+
+ while (int* it3 = std::find(arr, arr + 3, 2)) { break; }
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+
+ for (; int* it4 = std::find(arr, arr + 3, 2); ) { break; }
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+
+ std::vector<int> v;
+ if (auto it5 = std::ranges::find(v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
}
>From a217b5859d950d98cddc88bde0d21c7144caf82d Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Wed, 25 Feb 2026 22:50:00 +0800
Subject: [PATCH 13/17] nit
---
.../clang-tidy/bugprone/MissingEndComparisonCheck.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index c075b97d454b6..3ce28a615d315 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -197,7 +197,8 @@ void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
// FIXME: This only handles variables initialized directly by the algorithm.
// We may need to introduce more accurate dataflow analysis in the future.
const auto VarWithAlgoInit =
- varDecl(hasInitializer(expr(hasDescendant(AnyAlgoCall)))).bind("initVar");
+ varDecl(hasInitializer(expr(ignoringParenImpCasts(AnyAlgoCall))))
+ .bind("initVar");
const auto IsVariableBoolUsage =
anyOf(implicitCastExpr(hasCastKind(CK_PointerToBoolean),
>From aa10d75a26417ddc53990e35427e12f1d61febda Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 27 Feb 2026 00:19:32 +0800
Subject: [PATCH 14/17] fixup fixup fixup fixup
---
.../bugprone/MissingEndComparisonCheck.cpp | 39 +++++--------------
1 file changed, 10 insertions(+), 29 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 3ce28a615d315..cffd2ea3238fd 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -49,25 +49,6 @@ void MissingEndComparisonCheck::storeOptions(
utils::options::serializeStringList(ExtraAlgorithms));
}
-static bool isConditionVar(const DeclStmt *S, ASTContext &Ctx) {
- const auto &Parents = Ctx.getParents(*S);
- if (Parents.empty())
- return false;
-
- const auto *ParentStmt = Parents[0].get<Stmt>();
- if (!ParentStmt)
- return false;
-
- if (const auto *If = dyn_cast<IfStmt>(ParentStmt))
- return If->getConditionVariableDeclStmt() == S;
- if (const auto *While = dyn_cast<WhileStmt>(ParentStmt))
- return While->getConditionVariableDeclStmt() == S;
- if (const auto *For = dyn_cast<ForStmt>(ParentStmt))
- return For->getConditionVariableDeclStmt() == S;
-
- return false;
-}
-
static std::optional<std::string>
getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
const FunctionDecl *Callee = Call->getDirectCallee();
@@ -92,7 +73,7 @@ getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
const Expr *RangeArg = Call->getArg(1);
// Avoid potential side-effects
const Expr *InnerRange = RangeArg->IgnoreParenImpCasts();
- if (isa<DeclRefExpr>(InnerRange) || isa<MemberExpr>(InnerRange)) {
+ if (isa<DeclRefExpr, MemberExpr>(InnerRange)) {
const StringRef RangeText =
tooling::fixit::getText(*RangeArg, *Result.Context);
if (!RangeText.empty())
@@ -197,8 +178,13 @@ void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
// FIXME: This only handles variables initialized directly by the algorithm.
// We may need to introduce more accurate dataflow analysis in the future.
const auto VarWithAlgoInit =
- varDecl(hasInitializer(expr(ignoringParenImpCasts(AnyAlgoCall))))
- .bind("initVar");
+ varDecl(decl().bind("initVar"),
+ hasInitializer(expr(ignoringParenImpCasts(AnyAlgoCall))),
+ optionally(hasParent(declStmt(
+ hasParent(mapAnyOf(ifStmt, whileStmt, forStmt)
+ .with(hasConditionVariableStatement(declStmt(
+ has(varDecl(equalsBoundNode("initVar"))))))
+ .bind("condVarParent"))))));
const auto IsVariableBoolUsage =
anyOf(implicitCastExpr(hasCastKind(CK_PointerToBoolean),
@@ -246,13 +232,8 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
if (InitVar->getType()->isBooleanType())
return;
- const auto &Parents = Result.Context->getParents(*InitVar);
- if (!Parents.empty()) {
- if (const auto *ParentDecl = Parents[0].get<DeclStmt>()) {
- if (isConditionVar(ParentDecl, *Result.Context))
- return;
- }
- }
+ if (Result.Nodes.getNodeAs<Stmt>("condVarParent"))
+ return;
}
const auto &Parents = Result.Context->getParents(*BoolOp);
>From 1921a106d179eeedbb155218076fad728b3b4659 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 27 Feb 2026 17:24:07 +0800
Subject: [PATCH 15/17] better?
---
.../bugprone/MissingEndComparisonCheck.cpp | 30 ++++++++-----------
1 file changed, 13 insertions(+), 17 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index cffd2ea3238fd..1a4373d78e232 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -49,8 +49,8 @@ void MissingEndComparisonCheck::storeOptions(
utils::options::serializeStringList(ExtraAlgorithms));
}
-static std::optional<std::string>
-getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
+static std::optional<std::string> getRangesEndText(const ASTContext &Context,
+ const CallExpr *Call) {
const FunctionDecl *Callee = Call->getDirectCallee();
assert(Callee && Callee->getNumParams() > 0 &&
"Matcher should ensure Callee has parameters");
@@ -64,7 +64,7 @@ getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
return std::nullopt;
// find(CPO, Iter, Sent, Val...) -> Sent is Arg 2.
const Expr *EndArg = Call->getArg(2);
- return tooling::fixit::getText(*EndArg, *Result.Context).str();
+ return tooling::fixit::getText(*EndArg, Context).str();
}
if (Call->getNumArgs() < 2)
@@ -74,17 +74,15 @@ getRangesEndText(const MatchFinder::MatchResult &Result, const CallExpr *Call) {
// Avoid potential side-effects
const Expr *InnerRange = RangeArg->IgnoreParenImpCasts();
if (isa<DeclRefExpr, MemberExpr>(InnerRange)) {
- const StringRef RangeText =
- tooling::fixit::getText(*RangeArg, *Result.Context);
+ const StringRef RangeText = tooling::fixit::getText(*RangeArg, Context);
if (!RangeText.empty())
return ("std::ranges::end(" + RangeText + ")").str();
}
return "";
}
-static std::optional<std::string>
-getStandardEndText(const MatchFinder::MatchResult &Result,
- const CallExpr *Call) {
+static std::optional<std::string> getStandardEndText(ASTContext &Context,
+ const CallExpr *Call) {
if (Call->getNumArgs() < 2)
return std::nullopt;
@@ -96,10 +94,8 @@ getStandardEndText(const MatchFinder::MatchResult &Result,
const QualType T0 = Arg0->getType().getCanonicalType();
const QualType T1 = Arg1->getType().getCanonicalType();
- if (T0.getNonReferenceType() != T1.getNonReferenceType() &&
- T0.getNonReferenceType()->isRecordType()) {
- const StringRef ContainerText =
- tooling::fixit::getText(*Arg0, *Result.Context);
+ if (T0 != T1 && T0.getNonReferenceType()->isRecordType()) {
+ const StringRef ContainerText = tooling::fixit::getText(*Arg0, Context);
if (!ContainerText.empty())
return ("std::end(" + ContainerText + ")").str();
}
@@ -109,7 +105,7 @@ getStandardEndText(const MatchFinder::MatchResult &Result,
const Expr *FirstArg = Call->getArg(0);
if (const auto *Record =
FirstArg->getType().getNonReferenceType()->getAsCXXRecordDecl()) {
- if (Record->getName().ends_with("_policy"))
+ if (Record->getIdentifier() && Record->getName().ends_with("_policy"))
EndIdx = 2;
}
@@ -119,10 +115,10 @@ getStandardEndText(const MatchFinder::MatchResult &Result,
const Expr *EndArg = Call->getArg(EndIdx);
// Filters nullptr, we assume the intent might be a valid check against null
if (EndArg->IgnoreParenCasts()->isNullPointerConstant(
- *Result.Context, Expr::NPC_ValueDependentIsNull))
+ Context, Expr::NPC_ValueDependentIsNull))
return std::nullopt;
- return tooling::fixit::getText(*EndArg, *Result.Context).str();
+ return tooling::fixit::getText(*EndArg, Context).str();
}
static const UnaryOperator *getParentLogicalNot(const Expr *E,
@@ -206,9 +202,9 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
if (Result.Nodes.getNodeAs<VarDecl>("cpo")) {
const auto *Call = Result.Nodes.getNodeAs<CallExpr>("algoCall");
- EndExprText = getRangesEndText(Result, Call);
+ EndExprText = getRangesEndText(*Result.Context, Call);
} else if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("algoCall")) {
- EndExprText = getStandardEndText(Result, Call);
+ EndExprText = getStandardEndText(*Result.Context, Call);
} else {
llvm_unreachable("Matcher should bind 'algoCall' or 'cpo'");
}
>From 839b0f4281b244da4ac595733261c3a1fb456532 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Tue, 3 Mar 2026 10:13:07 +0800
Subject: [PATCH 16/17] fixup fixup fixup fixup fixup fixup
---
.../clang-tidy/bugprone/MissingEndComparisonCheck.cpp | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 1a4373d78e232..47666ca135772 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -17,9 +17,7 @@ using namespace clang::ast_matchers;
namespace clang::tidy::bugprone {
-namespace {
-
-constexpr llvm::StringRef IteratorAlgorithms[] = {
+static constexpr llvm::StringRef IteratorAlgorithms[] = {
"::std::find", "::std::find_if",
"::std::find_if_not", "::std::search",
"::std::search_n", "::std::find_end",
@@ -28,13 +26,12 @@ constexpr llvm::StringRef IteratorAlgorithms[] = {
"::std::min_element", "::std::max_element",
"::std::adjacent_find", "::std::is_sorted_until"};
-constexpr llvm::StringRef RangeAlgorithms[] = {
+static constexpr llvm::StringRef RangeAlgorithms[] = {
"::std::ranges::find", "::std::ranges::find_if",
"::std::ranges::find_if_not", "::std::ranges::lower_bound",
"::std::ranges::upper_bound", "::std::ranges::min_element",
"::std::ranges::max_element", "::std::ranges::find_first_of",
"::std::ranges::adjacent_find", "::std::ranges::is_sorted_until"};
-} // namespace
MissingEndComparisonCheck::MissingEndComparisonCheck(StringRef Name,
ClangTidyContext *Context)
@@ -62,14 +59,14 @@ static std::optional<std::string> getRangesEndText(const ASTContext &Context,
if (IsIterPair) {
if (Call->getNumArgs() < 3)
return std::nullopt;
- // find(CPO, Iter, Sent, Val...) -> Sent is Arg 2.
+ // CPO(Iter, Sent, Val...) -> Sent is Arg 2.
const Expr *EndArg = Call->getArg(2);
return tooling::fixit::getText(*EndArg, Context).str();
}
if (Call->getNumArgs() < 2)
return std::nullopt;
- // find(CPO, Range, Val, Proj) -> Range is Arg 1.
+ // CPO(Range, Val, Proj) -> Range is Arg 1.
const Expr *RangeArg = Call->getArg(1);
// Avoid potential side-effects
const Expr *InnerRange = RangeArg->IgnoreParenImpCasts();
>From 1d99cb6bdf57b8609fb54d8dad548a8d24036602 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Thu, 26 Mar 2026 18:35:43 +0800
Subject: [PATCH 17/17] better implementation
---
.../bugprone/BugproneTidyModule.cpp | 4 +-
.../bugprone/MissingEndComparisonCheck.cpp | 69 +++----
.../checkers/Inputs/Headers/std/algorithm | 175 ++++++++++++++++++
.../checkers/Inputs/Headers/std/iterator | 48 +++++
.../Inputs/missing-end-comparison/fake_std.h | 93 ----------
.../missing-end-comparison-custom.cpp | 19 +-
.../bugprone/missing-end-comparison-cxx17.cpp | 50 +++++
.../bugprone/missing-end-comparison-cxx20.cpp | 123 ++++++++++++
.../bugprone/missing-end-comparison-cxx98.cpp | 36 ++++
.../bugprone/missing-end-comparison.cpp | 159 +++++-----------
10 files changed, 513 insertions(+), 263 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/algorithm
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/iterator
delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/missing-end-comparison/fake_std.h
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx17.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx20.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx98.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index b98d6396a12c0..e2f943a338aaa 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -179,8 +179,6 @@ class BugproneModule : public ClangTidyModule {
"bugprone-incorrect-enable-if");
CheckFactories.registerCheck<IncorrectEnableSharedFromThisCheck>(
"bugprone-incorrect-enable-shared-from-this");
- CheckFactories.registerCheck<MissingEndComparisonCheck>(
- "bugprone-missing-end-comparison");
CheckFactories.registerCheck<UnintendedCharOstreamOutputCheck>(
"bugprone-unintended-char-ostream-output");
CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
@@ -212,6 +210,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-misplaced-pointer-arithmetic-in-alloc");
CheckFactories.registerCheck<MisplacedWideningCastCheck>(
"bugprone-misplaced-widening-cast");
+ CheckFactories.registerCheck<MissingEndComparisonCheck>(
+ "bugprone-missing-end-comparison");
CheckFactories.registerCheck<MoveForwardingReferenceCheck>(
"bugprone-move-forwarding-reference");
CheckFactories.registerCheck<MultiLevelImplicitPointerConversionCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
index 47666ca135772..e2daa7eefb99d 100644
--- a/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/MissingEndComparisonCheck.cpp
@@ -118,26 +118,6 @@ static std::optional<std::string> getStandardEndText(ASTContext &Context,
return tooling::fixit::getText(*EndArg, Context).str();
}
-static const UnaryOperator *getParentLogicalNot(const Expr *E,
- ASTContext &Context) {
- const Expr *CurrentExpr = E;
- while (true) {
- auto Parents = Context.getParents(*CurrentExpr);
- if (Parents.empty())
- break;
- if (const auto *P = Parents[0].get<ParenExpr>()) {
- CurrentExpr = P;
- continue;
- }
- if (const auto *U = Parents[0].get<UnaryOperator>()) {
- if (U->getOpcode() == UO_LNot)
- return U;
- }
- break;
- }
- return nullptr;
-}
-
void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
llvm::SmallVector<StringRef, 32> ExpandedIteratorAlgorithms;
ExpandedIteratorAlgorithms.append(std::begin(IteratorAlgorithms),
@@ -187,8 +167,21 @@ void MissingEndComparisonCheck::registerMatchers(MatchFinder *Finder) {
callee(cxxConversionDecl(returns(booleanType()))),
on(ignoringParenImpCasts(declRefExpr(to(VarWithAlgoInit))))));
+ const auto BoolUsage = expr(anyOf(IsBoolUsage, IsVariableBoolUsage));
+
Finder->addMatcher(
- expr(anyOf(IsBoolUsage, IsVariableBoolUsage)).bind("boolOp"), this);
+ unaryOperator(hasOperatorName("!"),
+ hasUnaryOperand(ignoringParens(BoolUsage.bind("boolOp"))))
+ .bind("Neg"),
+ this);
+
+ Finder->addMatcher(
+ expr(BoolUsage,
+ unless(hasAncestor(unaryOperator(
+ hasOperatorName("!"), hasUnaryOperand(ignoringParens(
+ expr(equalsBoundNode("boolOp"))))))))
+ .bind("boolOp"),
+ this);
}
void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
@@ -209,12 +202,11 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
if (!EndExprText)
return;
- const UnaryOperator *NotOp = getParentLogicalNot(BoolOp, *Result.Context);
- const bool IsNegated = NotOp != nullptr;
+ const auto *NotOp = Result.Nodes.getNodeAs<UnaryOperator>("Neg");
- const auto Diag = diag(BoolOp->getBeginLoc(),
- "result of standard algorithm used as 'bool'; did you "
- "mean to compare with the end iterator?");
+ auto Diag = diag(BoolOp->getBeginLoc(),
+ "result of standard algorithm used as 'bool'; did you "
+ "mean to compare with the end iterator?");
if (EndExprText->empty())
return;
@@ -233,23 +225,18 @@ void MissingEndComparisonCheck::check(const MatchFinder::MatchResult &Result) {
if (!Parents.empty() && Parents[0].get<VarDecl>())
return;
- if (IsNegated) {
- // !it -> (it == end)
+ const SourceLocation AfterBoolOp =
+ Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0, *Result.SourceManager,
+ Result.Context->getLangOpts());
+
+ if (NotOp)
Diag << FixItHint::CreateReplacement(NotOp->getOperatorLoc(), "(");
- Diag << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
- *Result.SourceManager,
- Result.Context->getLangOpts()),
- " == " + *EndExprText + ")");
- } else {
- // it -> (it != end)
+ else
Diag << FixItHint::CreateInsertion(BoolOp->getBeginLoc(), "(");
- Diag << FixItHint::CreateInsertion(
- Lexer::getLocForEndOfToken(BoolOp->getEndLoc(), 0,
- *Result.SourceManager,
- Result.Context->getLangOpts()),
- " != " + *EndExprText + ")");
- }
+
+ Diag << FixItHint::CreateInsertion(
+ AfterBoolOp,
+ (llvm::Twine(NotOp ? " == " : " != ") + *EndExprText + ")").str());
}
} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/algorithm b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/algorithm
new file mode 100644
index 0000000000000..07be71c999af9
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/algorithm
@@ -0,0 +1,175 @@
+#ifndef _ALGORITHM_
+#define _ALGORITHM_
+
+#include "iterator"
+
+namespace std {
+
+template <class InputIt, class T>
+InputIt find(InputIt first, InputIt last, const T &value);
+
+template <class InputIt, class UnaryPred>
+InputIt find_if(InputIt first, InputIt last, UnaryPred pred);
+
+template <class InputIt, class UnaryPred>
+InputIt find_if_not(InputIt first, InputIt last, UnaryPred pred);
+
+template <class InputIt, class ForwardIt>
+InputIt find_first_of(InputIt first, InputIt last, ForwardIt s_first,
+ ForwardIt s_last);
+
+template <class ForwardIt1, class ForwardIt2>
+ForwardIt1 find_end(ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first,
+ ForwardIt2 s_last);
+
+template <class ForwardIt>
+ForwardIt adjacent_find(ForwardIt first, ForwardIt last);
+
+template <class ForwardIt1, class ForwardIt2>
+ForwardIt1 search(ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first,
+ ForwardIt2 s_last);
+
+template <class ForwardIt, class Size, class T>
+ForwardIt search_n(ForwardIt first, ForwardIt last, Size count,
+ const T &value);
+
+template <class ForwardIt, class T>
+ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T &value);
+
+template <class ForwardIt, class T>
+ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T &value);
+
+template <class ForwardIt, class T>
+ForwardIt partition_point(ForwardIt first, ForwardIt last, const T &value);
+
+template <class ForwardIt>
+ForwardIt min_element(ForwardIt first, ForwardIt last);
+
+template <class ForwardIt>
+ForwardIt max_element(ForwardIt first, ForwardIt last);
+
+template <class ForwardIt>
+ForwardIt is_sorted_until(ForwardIt first, ForwardIt last);
+
+#if __cplusplus >= 201703L
+
+namespace execution {
+struct sequenced_policy {};
+struct parallel_policy {};
+inline constexpr sequenced_policy seq{};
+inline constexpr parallel_policy par{};
+} // namespace execution
+
+// Overloads with execution policies.
+template <class ExecutionPolicy, class InputIt, class T>
+InputIt find(ExecutionPolicy &&policy, InputIt first, InputIt last,
+ const T &value);
+
+template <class ExecutionPolicy, class ForwardIt, class T>
+ForwardIt lower_bound(ExecutionPolicy &&policy, ForwardIt first,
+ ForwardIt last, const T &value);
+
+#endif // __cplusplus >= 201703L
+
+#if __cplusplus >= 202002L
+
+namespace ranges {
+
+template <typename T>
+auto begin(T &t) -> decltype(t.begin()) { return t.begin(); }
+
+template <typename T>
+auto end(T &t) -> decltype(t.end()) { return t.end(); }
+
+struct __find_fn {
+ template <typename Range, typename T>
+ auto operator()(Range &&r, const T &value) const
+ -> decltype(r.begin());
+ template <typename I, typename S, typename T>
+ I operator()(I first, S last, const T &value) const;
+};
+inline constexpr __find_fn find{};
+
+struct __find_if_fn {
+ template <typename Range, typename Pred>
+ auto operator()(Range &&r, Pred pred) const
+ -> decltype(r.begin());
+ template <typename I, typename S, typename Pred>
+ I operator()(I first, S last, Pred pred) const;
+};
+inline constexpr __find_if_fn find_if{};
+
+struct __find_if_not_fn {
+ template <typename Range, typename Pred>
+ auto operator()(Range &&r, Pred pred) const
+ -> decltype(r.begin());
+ template <typename I, typename S, typename Pred>
+ I operator()(I first, S last, Pred pred) const;
+};
+inline constexpr __find_if_not_fn find_if_not{};
+
+struct __find_first_of_fn {
+ template <typename R1, typename R2>
+ auto operator()(R1 &&r1, R2 &&r2) const -> decltype(r1.begin());
+ template <typename I1, typename S1, typename I2, typename S2>
+ I1 operator()(I1 f1, S1 l1, I2 f2, S2 l2) const;
+};
+inline constexpr __find_first_of_fn find_first_of{};
+
+struct __lower_bound_fn {
+ template <typename Range, typename T>
+ auto operator()(Range &&r, const T &value) const
+ -> decltype(r.begin());
+ template <typename I, typename S, typename T>
+ I operator()(I first, S last, const T &value) const;
+};
+inline constexpr __lower_bound_fn lower_bound{};
+
+struct __upper_bound_fn {
+ template <typename Range, typename T>
+ auto operator()(Range &&r, const T &value) const
+ -> decltype(r.begin());
+ template <typename I, typename S, typename T>
+ I operator()(I first, S last, const T &value) const;
+};
+inline constexpr __upper_bound_fn upper_bound{};
+
+struct __min_element_fn {
+ template <typename Range>
+ auto operator()(Range &&r) const -> decltype(r.begin());
+ template <typename I, typename S>
+ I operator()(I first, S last) const;
+};
+inline constexpr __min_element_fn min_element{};
+
+struct __max_element_fn {
+ template <typename Range>
+ auto operator()(Range &&r) const -> decltype(r.begin());
+ template <typename I, typename S>
+ I operator()(I first, S last) const;
+};
+inline constexpr __max_element_fn max_element{};
+
+struct __adjacent_find_fn {
+ template <typename Range>
+ auto operator()(Range &&r) const -> decltype(r.begin());
+ template <typename I, typename S>
+ I operator()(I first, S last) const;
+};
+inline constexpr __adjacent_find_fn adjacent_find{};
+
+struct __is_sorted_until_fn {
+ template <typename Range>
+ auto operator()(Range &&r) const -> decltype(r.begin());
+ template <typename I, typename S>
+ I operator()(I first, S last) const;
+};
+inline constexpr __is_sorted_until_fn is_sorted_until{};
+
+} // namespace ranges
+
+#endif // __cplusplus >= 202002L
+
+} // namespace std
+
+#endif // _ALGORITHM_
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/iterator b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/iterator
new file mode 100644
index 0000000000000..7c0138f68ce37
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/iterator
@@ -0,0 +1,48 @@
+#ifndef _ITERATOR_
+#define _ITERATOR_
+
+#include "cstddef"
+
+namespace std {
+
+struct input_iterator_tag {};
+struct output_iterator_tag {};
+struct forward_iterator_tag : input_iterator_tag {};
+struct bidirectional_iterator_tag : forward_iterator_tag {};
+struct random_access_iterator_tag : bidirectional_iterator_tag {};
+
+template <typename Iterator>
+struct iterator_traits {
+ typedef typename Iterator::difference_type difference_type;
+ typedef typename Iterator::value_type value_type;
+ typedef typename Iterator::pointer pointer;
+ typedef typename Iterator::reference reference;
+ typedef typename Iterator::iterator_category iterator_category;
+};
+
+template <typename T>
+struct iterator_traits<T *> {
+ typedef ptrdiff_t difference_type;
+ typedef T value_type;
+ typedef T *pointer;
+ typedef T &reference;
+ typedef random_access_iterator_tag iterator_category;
+};
+
+#if __cplusplus >= 201103L
+template <typename Container>
+auto begin(Container &c) -> decltype(c.begin()) { return c.begin(); }
+
+template <typename Container>
+auto end(Container &c) -> decltype(c.end()) { return c.end(); }
+#endif
+
+template <typename T, size_t N>
+T *begin(T (&arr)[N]) { return arr; }
+
+template <typename T, size_t N>
+T *end(T (&arr)[N]) { return arr + N; }
+
+} // namespace std
+
+#endif // _ITERATOR_
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/missing-end-comparison/fake_std.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/missing-end-comparison/fake_std.h
deleted file mode 100644
index 89330e4015089..0000000000000
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/missing-end-comparison/fake_std.h
+++ /dev/null
@@ -1,93 +0,0 @@
-#ifndef MISSING_END_COMPARISON_FAKE_STD_H
-#define MISSING_END_COMPARISON_FAKE_STD_H
-
-namespace std {
- template<typename T> struct iterator_traits;
- struct forward_iterator_tag {};
-
- typedef long int ptrdiff_t;
- typedef decltype(nullptr) nullptr_t;
-
- template<typename T>
- struct vector {
- typedef T* iterator;
- typedef const T* const_iterator;
- iterator begin();
- iterator end();
- const_iterator begin() const;
- const_iterator end() const;
- };
-
- template<class InputIt, class T>
- InputIt find(InputIt first, InputIt last, const T& value);
-
- namespace execution {
- struct sequenced_policy {};
- struct parallel_policy {};
- inline constexpr sequenced_policy seq;
- inline constexpr parallel_policy par;
- }
-
- template<class ExecutionPolicy, class InputIt, class T>
- InputIt find(ExecutionPolicy&& policy, InputIt first, InputIt last, const T& value);
-
- template<class ExecutionPolicy, class ForwardIt, class T>
- ForwardIt lower_bound(ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, const T& value);
-
- template<class ForwardIt, class T>
- ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);
-
- template<class ForwardIt, class ForwardIt2>
- ForwardIt search(ForwardIt first, ForwardIt last, ForwardIt first2, ForwardIt2 last2);
-
- template<class ForwardIt>
- ForwardIt min_element(ForwardIt first, ForwardIt last);
-
- template<class InputIt1, class InputIt2>
- struct pair {
- InputIt1 first;
- InputIt2 second;
- };
-
- namespace ranges {
- template<typename T>
- void* begin(T& t);
- template<typename T>
- void* end(T& t);
-
- struct FindFn {
- template<typename Range, typename T>
- void* operator()(Range&& r, const T& value) const;
-
- template<typename I, typename S, typename T>
- void* operator()(I first, S last, const T& value) const;
- };
- inline constexpr FindFn find;
-
- struct FindFirstOfFn {
- template<typename R1, typename R2>
- void* operator()(R1&& r1, R2&& r2) const;
- template<typename I1, typename S1, typename I2, typename S2>
- void* operator()(I1 f1, S1 l1, I2 f2, S2 l2) const;
- };
- inline constexpr FindFirstOfFn find_first_of;
-
- struct AdjacentFindFn {
- template<typename R>
- void* operator()(R&& r) const;
- template<typename I, typename S>
- void* operator()(I f, S l) const;
- };
- inline constexpr AdjacentFindFn adjacent_find;
-
- struct IsSortedUntilFn {
- template<typename R>
- void* operator()(R&& r) const;
- template<typename I, typename S>
- void* operator()(I f, S l) const;
- };
- inline constexpr IsSortedUntilFn is_sorted_until;
- }
-} // namespace std
-
-#endif // MISSING_END_COMPARISON_FAKE_STD_H
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-custom.cpp
index 6e0916a689605..74147830705d3 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-custom.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-custom.cpp
@@ -1,13 +1,8 @@
-// RUN: %check_clang_tidy -std=c++20 %s bugprone-missing-end-comparison %t \
-// RUN: -config="{CheckOptions: {bugprone-missing-end-comparison.ExtraAlgorithms: '::my_lib::find;::my_lib::find_range'}}" \
-// RUN: -- -I %S/Inputs/missing-end-comparison
+// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-missing-end-comparison %t \
+// RUN: -config="{CheckOptions: {bugprone-missing-end-comparison.ExtraAlgorithms: '::my_lib::find;::my_lib::find_range'}}"
-#include "fake_std.h"
-
-namespace std {
- template<typename T>
- auto end(T& t) -> decltype(t.end()) { return t.end(); }
-}
+#include <algorithm>
+#include <vector>
namespace my_lib {
template<typename Iter, typename T>
@@ -16,7 +11,8 @@ namespace my_lib {
}
template<typename Range, typename T>
- auto find_range(Range&& range, const T& value) {
+ auto find_range(Range&& range, const T& value)
+ -> decltype(range.begin()) {
return range.begin();
}
}
@@ -32,8 +28,7 @@ void test_custom_algorithms() {
// CHECK-FIXES: if ((my_lib::find_range(v, 42) != std::end(v))) {}
}
-// Make sure we still check the original algorithms list.
-void test_vector() {
+void test_still_checks_standard() {
std::vector<int> v;
if (std::find(v.begin(), v.end(), 2)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx17.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx17.cpp
new file mode 100644
index 0000000000000..473ed94614720
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx17.cpp
@@ -0,0 +1,50 @@
+// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-missing-end-comparison %t
+
+#include <algorithm>
+
+void test_execution_policy() {
+ int arr[] = {1, 2, 3};
+ int* begin = arr;
+ int* end = arr + 3;
+
+ if (std::find(std::execution::seq, begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(std::execution::seq, begin, end, 2) != end)) {}
+
+ if (std::find(std::execution::par, begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(std::execution::par, begin, end, 2) != end)) {}
+
+ auto it = std::find(std::execution::seq, begin, end, 2);
+ if (it) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((it != end)) {}
+
+ if (std::lower_bound(std::execution::seq, begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::lower_bound(std::execution::seq, begin, end, 2) != end)) {}
+}
+
+#define FIND_WITH_POLICY(begin, end, val) \
+ std::find(std::execution::seq, begin, end, val)
+
+void test_execution_policy_macro() {
+ int arr[] = {1, 2, 3};
+ int* begin = arr;
+ int* end = arr + 3;
+
+ if (FIND_WITH_POLICY(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+}
+
+template <typename T>
+void test_execution_policy_template_impl(T* begin, T* end, T val) {
+ if (std::find(std::execution::seq, begin, end, val)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(std::execution::seq, begin, end, val) != end)) {}
+}
+
+void test_execution_policy_template() {
+ int arr[] = {1, 2, 3};
+ test_execution_policy_template_impl(arr, arr + 3, 2);
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx20.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx20.cpp
new file mode 100644
index 0000000000000..dc0770bce797d
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx20.cpp
@@ -0,0 +1,123 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s bugprone-missing-end-comparison %t
+
+#include <algorithm>
+#include <vector>
+
+void test_ranges_range_overload() {
+ std::vector<int> v;
+ if (std::ranges::find(v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find(v, 2) != std::ranges::end(v))) {}
+
+ auto it = std::ranges::find(v, 2);
+ if (it) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((it != std::ranges::end(v))) {}
+
+ if (!std::ranges::find(v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find(v, 2) == std::ranges::end(v))) {}
+}
+
+void test_ranges_iterator_pair() {
+ int arr[] = {1, 2, 3};
+ int *begin = arr;
+ int *end = arr + 3;
+
+ if (std::ranges::find(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find(begin, end, 2) != end)) {}
+}
+
+void test_ranges_multi_range() {
+ std::vector<int> v1, v2;
+ int arr[] = {1, 2, 3};
+ int *begin = arr;
+ int *end = arr + 3;
+
+ if (std::ranges::find_first_of(v1, v2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find_first_of(v1, v2) != std::ranges::end(v1))) {}
+
+ if (std::ranges::find_first_of(begin, end, begin, end)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find_first_of(begin, end, begin, end) != end)) {}
+
+ if (std::ranges::adjacent_find(v1)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::adjacent_find(v1) != std::ranges::end(v1))) {}
+
+ if (std::ranges::adjacent_find(begin, end)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::adjacent_find(begin, end) != end)) {}
+
+ if (std::ranges::is_sorted_until(v1)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::is_sorted_until(v1) != std::ranges::end(v1))) {}
+
+ if (std::ranges::is_sorted_until(begin, end)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::is_sorted_until(begin, end) != end)) {}
+}
+
+void test_ranges_loops() {
+ std::vector<int> v;
+ int arr[] = {1, 2, 3};
+ int *begin = arr;
+ int *end = arr + 3;
+
+ for (auto it = std::ranges::find(v, 2); !it; ) { break; }
+ // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: for (auto it = std::ranges::find(v, 2); (it == std::ranges::end(v)); ) { break; }
+}
+
+struct Data { std::vector<int> v; };
+
+void test_member_expr(Data& d) {
+ if (std::ranges::find(d.v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::ranges::find(d.v, 2) != std::ranges::end(d.v))) {}
+}
+
+void test_side_effects() {
+ std::vector<int> get_vec();
+ if (std::ranges::find(get_vec(), 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+}
+
+void test_ranges_condition_variable_suppression() {
+ std::vector<int> v;
+ if (auto it = std::ranges::find(v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+}
+
+template <typename T>
+void test_ranges_templates_impl(const std::vector<T>& v, T val) {
+ auto it = std::ranges::find(v, val);
+ if (it) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((it != std::ranges::end(v))) {}
+}
+
+void test_ranges_templates() {
+ std::vector<int> v;
+ test_ranges_templates_impl(v, 2);
+}
+
+#define RANGES_FIND_IN(v, val) std::ranges::find(v, val)
+#define IS_FOUND(it) (it)
+
+void test_ranges_macros() {
+ std::vector<int> v;
+ if (RANGES_FIND_IN(v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+
+ auto it = RANGES_FIND_IN(v, 2);
+ if (IS_FOUND(it)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+}
+
+void test_ranges_negative() {
+ std::vector<int> v;
+ if (std::ranges::find(v, 2) == std::ranges::end(v)) {}
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx98.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx98.cpp
new file mode 100644
index 0000000000000..397d12a3b16ff
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison-cxx98.cpp
@@ -0,0 +1,36 @@
+// RUN: %check_clang_tidy -std=c++98 %s bugprone-missing-end-comparison %t
+
+#include <algorithm>
+
+void test_raw_pointers() {
+ int arr[] = {1, 2, 3};
+ int *begin = arr;
+ int *end = arr + 3;
+
+ if (std::find(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(begin, end, 2) != end)) {}
+
+ if (!std::find(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(begin, end, 2) == end)) {}
+
+ if (std::lower_bound(begin, end, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::lower_bound(begin, end, 2) != end)) {}
+
+ if (std::search(begin, end, begin, end)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'
+ // CHECK-FIXES: if ((std::search(begin, end, begin, end) != end)) {}
+
+ if (std::min_element(begin, end)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'
+ // CHECK-FIXES: if ((std::min_element(begin, end) != end)) {}
+}
+
+void test_negative() {
+ int arr[] = {1, 2, 3};
+ int *begin = arr;
+ int *end = arr + 3;
+ if (std::find(begin, end, 2) != end) {}
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
index 189ff972db2bd..1c8f2c9495fc0 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/missing-end-comparison.cpp
@@ -1,6 +1,7 @@
-// RUN: %check_clang_tidy -std=c++20 %s bugprone-missing-end-comparison %t -- -- -I %S/Inputs/missing-end-comparison
+// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-missing-end-comparison %t
-#include "fake_std.h"
+#include <algorithm>
+#include <vector>
struct CustomIterator {
int* ptr;
@@ -59,69 +60,6 @@ void test_variable_tracking() {
// CHECK-FIXES: if ((it == arr + 3)) {}
}
-void test_ranges() {
- std::vector<int> v;
- if (std::ranges::find(v, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::find(v, 2) != std::ranges::end(v))) {}
-
- auto it = std::ranges::find(v, 2);
- if (it) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((it != std::ranges::end(v))) {}
-
- std::vector<int> v1, v2;
- int arr[] = {1, 2, 3};
- int *begin = arr;
- int *end = arr + 3;
-
- if (std::ranges::find_first_of(v1, v2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::find_first_of(v1, v2) != std::ranges::end(v1))) {}
-
- if (std::ranges::find_first_of(begin, end, begin, end)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::find_first_of(begin, end, begin, end) != end)) {}
-
- if (std::ranges::adjacent_find(v1)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::adjacent_find(v1) != std::ranges::end(v1))) {}
-
- if (std::ranges::adjacent_find(begin, end)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::adjacent_find(begin, end) != end)) {}
-
- if (std::ranges::is_sorted_until(v1)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::is_sorted_until(v1) != std::ranges::end(v1))) {}
-
- if (std::ranges::is_sorted_until(begin, end)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::is_sorted_until(begin, end) != end)) {}
-}
-
-void test_ranges_iterator_pair() {
- int arr[] = {1, 2, 3};
- int *begin = arr;
- int *end = arr + 3;
- if (std::ranges::find(begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::find(begin, end, 2) != end)) {}
-}
-
-void test_side_effects() {
- std::vector<int> get_vec();
- if (std::ranges::find(get_vec(), 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
-}
-
-void test_negative() {
- std::vector<int> v;
- if (std::find(v.begin(), v.end(), 2) != v.end()) {}
- if (std::ranges::find(v, 2) == std::ranges::end(v)) {}
- auto it = std::find(v.begin(), v.end(), 2);
-}
-
void test_nested_parens() {
int arr[] = {1, 2, 3};
if (!((std::find(arr, arr + 3, 2)))) {}
@@ -129,17 +67,6 @@ void test_nested_parens() {
// CHECK-FIXES: if ((((std::find(arr, arr + 3, 2))) == arr + 3)) {}
}
-struct Data { std::vector<int> v; };
-void test_member_expr(Data& d) {
- if (std::ranges::find(d.v, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::ranges::find(d.v, 2) != std::ranges::end(d.v))) {}
-}
-
-void test_nullptr_comparison() {
- if (std::find((int*)nullptr, (int*)nullptr, 2)) {}
-}
-
void test_double_negation() {
int arr[] = {1, 2, 3};
auto it = std::find(arr, arr + 3, 2);
@@ -165,38 +92,6 @@ void test_search() {
// CHECK-FIXES: if ((std::search(begin, end, begin, end) != end)) {}
}
-namespace other {
- bool find(int* b, int* e, int v);
-}
-
-void test_other_namespace() {
- int arr[] = {1};
- if (other::find(arr, arr + 1, 1)) {}
-}
-
-void test_execution_policy() {
- int arr[] = {1, 2, 3};
- int* begin = arr;
- int* end = arr + 3;
-
- if (std::find(std::execution::seq, begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::find(std::execution::seq, begin, end, 2) != end)) {}
-
- if (std::find(std::execution::par, begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::find(std::execution::par, begin, end, 2) != end)) {}
-
- auto it = std::find(std::execution::seq, begin, end, 2);
- if (it) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((it != end)) {}
-
- if (std::lower_bound(std::execution::seq, begin, end, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: if ((std::lower_bound(std::execution::seq, begin, end, 2) != end)) {}
-}
-
void test_loops() {
int arr[] = {1, 2, 3};
int *begin = arr;
@@ -213,11 +108,6 @@ void test_loops() {
for (auto it = std::find(begin, end, 2); it; ) { break; }
// CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
// CHECK-FIXES: for (auto it = std::find(begin, end, 2); (it != end); ) { break; }
-
- std::vector<int> v;
- for (auto it = std::ranges::find(v, 2); !it; ) { break; }
- // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
- // CHECK-FIXES: for (auto it = std::ranges::find(v, 2); (it == std::ranges::end(v)); ) { break; }
}
void test_condition_variable_suppression() {
@@ -230,8 +120,47 @@ void test_condition_variable_suppression() {
for (; int* it4 = std::find(arr, arr + 3, 2); ) { break; }
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+}
+
+#define FIND_IN(v, val) std::find(v.begin(), v.end(), val)
+#define IS_FOUND(it) (it)
+void test_macros() {
std::vector<int> v;
- if (auto it5 = std::ranges::find(v, 2)) {}
- // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ if (FIND_IN(v, 2)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+
+ auto it = FIND_IN(v, 2);
+ if (IS_FOUND(it)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+}
+
+template <typename T>
+void test_templates_impl(const std::vector<T>& v, T val) {
+ if (std::find(v.begin(), v.end(), val)) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: result of standard algorithm used as 'bool'; did you mean to compare with the end iterator? [bugprone-missing-end-comparison]
+ // CHECK-FIXES: if ((std::find(v.begin(), v.end(), val) != v.end())) {}
+}
+
+void test_templates() {
+ std::vector<int> v;
+ test_templates_impl(v, 2);
+}
+
+namespace other {
+ bool find(int* b, int* e, int v);
+}
+
+void test_negative() {
+ std::vector<int> v;
+ if (std::find(v.begin(), v.end(), 2) != v.end()) {}
+
+ auto it = std::find(v.begin(), v.end(), 2);
+
+ int arr[] = {1};
+ if (other::find(arr, arr + 1, 1)) {}
+}
+
+void test_nullptr_comparison() {
+ if (std::find((int*)nullptr, (int*)nullptr, 2)) {}
}
More information about the cfe-commits
mailing list