[clang-tools-extra] [clang-tidy] Add `bugprone-unordered-equal-compare` check (PR #210555)
Lucas Ly Ba via cfe-commits
cfe-commits at lists.llvm.org
Sat Jul 18 15:20:35 PDT 2026
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 <hi at lucaslyba.com>
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());
+}
More information about the cfe-commits
mailing list