https://github.com/lucasly-ba created 
https://github.com/llvm/llvm-project/pull/210555

The iteration order of an unordered container is unspecified, so comparing the 
ranges of two of them with std::equal is order-dependent and can report equal 
containers as different. Flag such comparisons; operator== should be used 
instead.

Fixes #173108

>From 0814a5ebca2e16162ba00a3f430298aff5f200f5 Mon Sep 17 00:00:00 2001
From: Lucas Ly Ba <[email protected]>
Date: Sat, 18 Jul 2026 23:48:58 +0200
Subject: [PATCH] [clang-tidy] Add `bugprone-unordered-equal-compare` check

The iteration order of an unordered container is unspecified, so comparing the
ranges of two of them with std::equal is order-dependent and can report equal
containers as different. Flag such comparisons; operator== should be used
instead.

Fixes #173108
---
 .../bugprone/BugproneTidyModule.cpp           |  3 ++
 .../clang-tidy/bugprone/CMakeLists.txt        |  1 +
 .../bugprone/UnorderedEqualCompareCheck.cpp   | 39 +++++++++++++++++++
 .../bugprone/UnorderedEqualCompareCheck.h     | 38 ++++++++++++++++++
 clang-tools-extra/docs/ReleaseNotes.rst       |  7 ++++
 .../bugprone/unordered-equal-compare.rst      | 21 ++++++++++
 .../docs/clang-tidy/checks/list.rst           |  1 +
 .../bugprone/unordered-equal-compare.cpp      | 38 ++++++++++++++++++
 8 files changed, 148 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/bugprone/unordered-equal-compare.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/bugprone/unordered-equal-compare.cpp

diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp 
b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 3aa39d10ceb5d..12307f7a83629 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -110,6 +110,7 @@
 #include "UnhandledSelfAssignmentCheck.h"
 #include "UnintendedCharOstreamOutputCheck.h"
 #include "UniquePtrArrayMismatchCheck.h"
+#include "UnorderedEqualCompareCheck.h"
 #include "UnsafeFunctionsCheck.h"
 #include "UnsafeToAllowExceptionsCheck.h"
 #include "UnusedLocalNonTrivialVariableCheck.h"
@@ -321,6 +322,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-unique-ptr-array-mismatch");
     CheckFactories.registerCheck<CrtpConstructorAccessibilityCheck>(
         "bugprone-crtp-constructor-accessibility");
+    CheckFactories.registerCheck<UnorderedEqualCompareCheck>(
+        "bugprone-unordered-equal-compare");
     CheckFactories.registerCheck<UnsafeFunctionsCheck>(
         "bugprone-unsafe-functions");
     CheckFactories.registerCheck<UnsafeToAllowExceptionsCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 43e85b1407f21..9996486596fcf 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -112,6 +112,7 @@ add_clang_library(clangTidyBugproneModule STATIC
   UnhandledExceptionAtNewCheck.cpp
   UnhandledSelfAssignmentCheck.cpp
   UniquePtrArrayMismatchCheck.cpp
+  UnorderedEqualCompareCheck.cpp
   UnsafeFunctionsCheck.cpp
   UnsafeToAllowExceptionsCheck.cpp
   UnusedLocalNonTrivialVariableCheck.cpp
diff --git 
a/clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.cpp
new file mode 100644
index 0000000000000..31c100e89400a
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UnorderedEqualCompareCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void UnorderedEqualCompareCheck::registerMatchers(MatchFinder *Finder) {
+  auto UnorderedContainer = hasType(hasUnqualifiedDesugaredType(recordType(
+      hasDeclaration(cxxRecordDecl(hasAnyName(
+          "::std::unordered_set", "::std::unordered_multiset",
+          "::std::unordered_map", "::std::unordered_multimap"))))));
+
+  // An iterator obtained from an unordered container, e.g. c.begin().
+  auto IteratorFromUnordered = cxxMemberCallExpr(
+      callee(cxxMethodDecl(hasAnyName("begin", "end", "cbegin", "cend"))),
+      on(expr(UnorderedContainer)));
+
+  Finder->addMatcher(callExpr(callee(functionDecl(hasName("::std::equal"))),
+                              hasAnyArgument(IteratorFromUnordered))
+                         .bind("call"),
+                     this);
+}
+
+void UnorderedEqualCompareCheck::check(const MatchFinder::MatchResult &Result) 
{
+  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
+  diag(Call->getBeginLoc(),
+       "comparing an unordered container with 'std::equal' is 
order-dependent");
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.h 
b/clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.h
new file mode 100644
index 0000000000000..52846c317c5bb
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.h
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_UNORDEREDEQUALCOMPARECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNORDEREDEQUALCOMPARECHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Flags uses of 'std::equal' to compare the ranges of two unordered
+/// containers. The iteration order of unordered containers is unspecified, so
+/// such a comparison is order-dependent; 'operator==' should be used instead.
+///
+/// For the user-facing documentation see:
+/// 
https://clang.llvm.org/extra/clang-tidy/checks/bugprone/unordered-equal-compare.html
+class UnorderedEqualCompareCheck : public ClangTidyCheck {
+public:
+  UnorderedEqualCompareCheck(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;
+  }
+  std::optional<TraversalKind> getCheckTraversalKind() const override {
+    return TK_IgnoreUnlessSpelledInSource;
+  }
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // 
LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNORDEREDEQUALCOMPARECHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 69c3bcf67b8db..de9ea2670c833 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -97,6 +97,13 @@ Improvements to clang-tidy
 New checks
 ^^^^^^^^^^
 
+- New :doc:`bugprone-unordered-equal-compare
+  <clang-tidy/checks/bugprone/unordered-equal-compare>` check.
+
+  Flags uses of ``std::equal`` to compare the ranges of two unordered
+  containers, which is order-dependent because their iteration order is
+  unspecified.
+
 New check aliases
 ^^^^^^^^^^^^^^^^^
 
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unordered-equal-compare.rst 
b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unordered-equal-compare.rst
new file mode 100644
index 0000000000000..3dd904d5bcb62
--- /dev/null
+++ 
b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unordered-equal-compare.rst
@@ -0,0 +1,21 @@
+.. title:: clang-tidy - bugprone-unordered-equal-compare
+
+bugprone-unordered-equal-compare
+================================
+
+Flags uses of ``std::equal`` to compare the ranges of two unordered containers
+(``std::unordered_set``, ``std::unordered_multiset``, ``std::unordered_map`` 
and
+``std::unordered_multimap``).
+
+The iteration order of an unordered container is unspecified, so two containers
+holding the same elements may iterate them in a different order. Comparing 
their
+ranges element by element with ``std::equal`` is therefore order-dependent and
+can report equal containers as different. Use ``operator==`` instead, which
+compares the containers as sets.
+
+.. code-block:: c++
+
+  std::unordered_set<int> a, b;
+
+  bool wrong = std::equal(a.begin(), a.end(), b.begin());  // warning
+  bool ok = (a == b);
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst 
b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 2a44dc78fbc89..0aec1bcf83de5 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -179,6 +179,7 @@ Clang-Tidy Checks
    :doc:`bugprone-unhandled-self-assignment 
<bugprone/unhandled-self-assignment>`,
    :doc:`bugprone-unintended-char-ostream-output 
<bugprone/unintended-char-ostream-output>`, "Yes"
    :doc:`bugprone-unique-ptr-array-mismatch 
<bugprone/unique-ptr-array-mismatch>`, "Yes"
+   :doc:`bugprone-unordered-equal-compare <bugprone/unordered-equal-compare>`,
    :doc:`bugprone-unsafe-functions <bugprone/unsafe-functions>`,
    :doc:`bugprone-unsafe-to-allow-exceptions 
<bugprone/unsafe-to-allow-exceptions>`,
    :doc:`bugprone-unused-local-non-trivial-variable 
<bugprone/unused-local-non-trivial-variable>`,
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unordered-equal-compare.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unordered-equal-compare.cpp
new file mode 100644
index 0000000000000..969184714b51f
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unordered-equal-compare.cpp
@@ -0,0 +1,38 @@
+// RUN: %check_clang_tidy %s bugprone-unordered-equal-compare %t
+
+namespace std {
+template <class InputIt1, class InputIt2>
+bool equal(InputIt1, InputIt1, InputIt2);
+template <class InputIt1, class InputIt2>
+bool equal(InputIt1, InputIt1, InputIt2, InputIt2);
+
+template <class T> struct set {
+  struct iterator {};
+  iterator begin() const;
+  iterator end() const;
+};
+template <class T> struct unordered_set {
+  struct iterator {};
+  iterator begin() const;
+  iterator end() const;
+};
+template <class K, class V> struct unordered_map {
+  struct iterator {};
+  iterator begin() const;
+  iterator end() const;
+};
+} // namespace std
+
+void bad_set(std::unordered_set<int> &a, std::unordered_set<int> &b) {
+  std::equal(a.begin(), a.end(), b.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: comparing an unordered container 
with 'std::equal' is order-dependent [bugprone-unordered-equal-compare]
+}
+
+void bad_map(std::unordered_map<int, int> &a, std::unordered_map<int, int> &b) 
{
+  std::equal(a.begin(), a.end(), b.begin(), b.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: comparing an unordered container 
with 'std::equal' is order-dependent [bugprone-unordered-equal-compare]
+}
+
+void ok_ordered(std::set<int> &a, std::set<int> &b) {
+  std::equal(a.begin(), a.end(), b.begin());
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to