[clang-tools-extra] [clang-tidy] Add `bugprone-unordered-equal-compare` check (PR #210555)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Jul 18 15:21:26 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tools-extra
Author: Lucas Ly Ba (lucasly-ba)
<details>
<summary>Changes</summary>
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
---
Full diff: https://github.com/llvm/llvm-project/pull/210555.diff
8 Files Affected:
- (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3)
- (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
- (added) clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.cpp (+39)
- (added) clang-tools-extra/clang-tidy/bugprone/UnorderedEqualCompareCheck.h (+38)
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+7)
- (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/unordered-equal-compare.rst (+21)
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
- (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/unordered-equal-compare.cpp (+38)
``````````diff
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());
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/210555
More information about the cfe-commits
mailing list