[clang-tools-extra] [clang-tidy] Add new performance-expensive-flat-container-operation check (PR #112051)
Nicolas van Kempen via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 11 14:45:59 PDT 2024
https://github.com/nicovank created https://github.com/llvm/llvm-project/pull/112051
Summary:
Test Plan:
>From 9b2953abd81dab3349a656544c916fe101508ff2 Mon Sep 17 00:00:00 2001
From: Nicolas van Kempen <nvankemp at gmail.com>
Date: Fri, 11 Oct 2024 17:45:35 -0400
Subject: [PATCH] [clang-tidy] Add new
performance-expensive-flat-container-operation check
Summary:
Test Plan:
---
.../clang-tidy/performance/CMakeLists.txt | 1 +
.../ExpensiveFlatContainerOperationCheck.cpp | 104 ++++
.../ExpensiveFlatContainerOperationCheck.h | 37 ++
.../performance/PerformanceTidyModule.cpp | 3 +
clang-tools-extra/docs/ReleaseNotes.rst | 5 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../expensive-flat-container-operation.rst | 82 +++
clang-tools-extra/test/.clang-format | 1 -
...container-operation-warn-outside-loops.cpp | 478 ++++++++++++++++++
.../expensive-flat-container-operation.cpp | 91 ++++
10 files changed, 802 insertions(+), 1 deletion(-)
create mode 100644 clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/performance/expensive-flat-container-operation.rst
delete mode 100644 clang-tools-extra/test/.clang-format
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation-warn-outside-loops.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation.cpp
diff --git a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
index c6e547c5089fb0..2def785be0465b 100644
--- a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
@@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangTidyPerformanceModule STATIC
AvoidEndlCheck.cpp
EnumSizeCheck.cpp
+ ExpensiveFlatContainerOperationCheck.cpp
FasterStringFindCheck.cpp
ForRangeCopyCheck.cpp
ImplicitConversionInLoopCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.cpp b/clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.cpp
new file mode 100644
index 00000000000000..c06cbfd5d4271c
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.cpp
@@ -0,0 +1,104 @@
+//===--- ExpensiveFlatContainerOperationCheck.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 "ExpensiveFlatContainerOperationCheck.h"
+
+#include "../utils/OptionsUtils.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::performance {
+
+namespace {
+// TODO: folly::heap_vector_map?
+const auto DefaultFlatContainers =
+ "::std::flat_map; ::std::flat_multimap;"
+ "::std::flat_set; ::std::flat_multiset;"
+ "::boost::container::flat_map; ::boost::container::flat_multimap;"
+ "::boost::container::flat_set; ::boost::container::flat_multiset;"
+ "::folly::sorted_vector_map; ::folly::sorted_vector_set;";
+} // namespace
+
+ExpensiveFlatContainerOperationCheck::ExpensiveFlatContainerOperationCheck(
+ StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ WarnOutsideLoops(Options.get("WarnOutsideLoops", false)),
+ FlatContainers(utils::options::parseStringList(
+ Options.get("FlatContainers", DefaultFlatContainers))) {}
+
+void ExpensiveFlatContainerOperationCheck::registerMatchers(
+ MatchFinder *Finder) {
+ const auto OnSoughtFlatContainer =
+ callee(cxxMethodDecl(ofClass(cxxRecordDecl(hasAnyName(FlatContainers)))));
+
+ // Any emplace-style or insert_or_assign call is a single-element operation.
+ const auto HasEmplaceOrInsertorAssignCall = callee(cxxMethodDecl(hasAnyName(
+ "emplace", "emplace_hint", "try_emplace", "insert_or_assign")));
+
+ // Erase calls with a single argument are single-element operations.
+ const auto HasEraseCallWithOneArgument = cxxMemberCallExpr(
+ argumentCountIs(1), callee(cxxMethodDecl(hasName("erase"))));
+
+ // TODO: insert.
+
+ const auto SoughtFlatContainerOperation =
+ cxxMemberCallExpr(
+ OnSoughtFlatContainer,
+ anyOf(HasEmplaceOrInsertorAssignCall, HasEraseCallWithOneArgument))
+ .bind("call");
+
+ if (WarnOutsideLoops) {
+ Finder->addMatcher(SoughtFlatContainerOperation, this);
+ return;
+ }
+
+ Finder->addMatcher(
+ mapAnyOf(whileStmt, forStmt, cxxForRangeStmt, doStmt)
+ .with(stmt(
+ stmt().bind("loop"),
+ forEachDescendant(cxxMemberCallExpr(
+ SoughtFlatContainerOperation,
+ // Common false positive: variable is declared directly within
+ // the loop. Note that this won't catch cases where the
+ // container is a member of a class declared in the loop.
+ // More robust lifetime analysis would be required to catch
+ // those cases, but this should filter out the most common
+ // false positives.
+ unless(onImplicitObjectArgument(declRefExpr(hasDeclaration(
+ decl(hasAncestor(stmt(equalsBoundNode("loop")))))))))))),
+ this);
+}
+
+void ExpensiveFlatContainerOperationCheck::check(
+ const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
+
+ diag(Call->getExprLoc(),
+ "Single element operations are expensive for flat containers. "
+ "Consider using available bulk operations instead, aggregating values "
+ "beforehand if needed.");
+}
+
+void ExpensiveFlatContainerOperationCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "WarnOutsideLoops", WarnOutsideLoops);
+ Options.store(Opts, "FlatContainers",
+ utils::options::serializeStringList(FlatContainers));
+}
+
+bool ExpensiveFlatContainerOperationCheck::isLanguageVersionSupported(
+ const LangOptions &LangOpts) const {
+ return LangOpts.CPlusPlus;
+}
+
+std::optional<TraversalKind>
+ExpensiveFlatContainerOperationCheck::getCheckTraversalKind() const {
+ return TK_IgnoreUnlessSpelledInSource;
+}
+} // namespace clang::tidy::performance
diff --git a/clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.h b/clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.h
new file mode 100644
index 00000000000000..b64ea87b573ffd
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.h
@@ -0,0 +1,37 @@
+//===--- ExpensiveFlatContainerOperationCheck.h - clang-tidy ----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_EXPENSIVEFLATCONTAINEROPERATIONCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_EXPENSIVEFLATCONTAINEROPERATIONCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::performance {
+
+/// Warns when calling an O(N) operation on a flat container.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/performance/expensive-flat-container-operation.html
+class ExpensiveFlatContainerOperationCheck : public ClangTidyCheck {
+public:
+ ExpensiveFlatContainerOperationCheck(StringRef Name,
+ ClangTidyContext *Context);
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
+ std::optional<TraversalKind> getCheckTraversalKind() const override;
+
+private:
+ bool WarnOutsideLoops;
+ std::vector<StringRef> FlatContainers;
+};
+
+} // namespace clang::tidy::performance
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_EXPENSIVEFLATCONTAINEROPERATIONCHECK_H
diff --git a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
index 9e0fa6f88b36a0..f6b0ed02389e36 100644
--- a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
@@ -11,6 +11,7 @@
#include "../ClangTidyModuleRegistry.h"
#include "AvoidEndlCheck.h"
#include "EnumSizeCheck.h"
+#include "ExpensiveFlatContainerOperationCheck.h"
#include "FasterStringFindCheck.h"
#include "ForRangeCopyCheck.h"
#include "ImplicitConversionInLoopCheck.h"
@@ -37,6 +38,8 @@ class PerformanceModule : public ClangTidyModule {
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<AvoidEndlCheck>("performance-avoid-endl");
CheckFactories.registerCheck<EnumSizeCheck>("performance-enum-size");
+ CheckFactories.registerCheck<ExpensiveFlatContainerOperationCheck>(
+ "performance-expensive-flat-container-operation");
CheckFactories.registerCheck<FasterStringFindCheck>(
"performance-faster-string-find");
CheckFactories.registerCheck<ForRangeCopyCheck>(
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 3f7bcde1eb3014..db396fd61ea636 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -121,6 +121,11 @@ New checks
Gives warnings for tagged unions, where the number of tags is
different from the number of data members inside the union.
+- New :doc:`performance-expensive-flat-container-operation
+ <clang-tidy/checks/performance/expensive-flat-container-operation>` check.
+
+ Warns when calling an O(N) operation on a flat container.
+
- New :doc:`portability-template-virtual-member-function
<clang-tidy/checks/portability/template-virtual-member-function>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 0082234f5ed31b..eaaf23bd5c535b 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -328,6 +328,7 @@ Clang-Tidy Checks
:doc:`openmp-use-default-none <openmp/use-default-none>`,
:doc:`performance-avoid-endl <performance/avoid-endl>`, "Yes"
:doc:`performance-enum-size <performance/enum-size>`,
+ :doc:`performance-expensive-flat-container-operation <performance/expensive-flat-container-operation>`,
:doc:`performance-faster-string-find <performance/faster-string-find>`, "Yes"
:doc:`performance-for-range-copy <performance/for-range-copy>`, "Yes"
:doc:`performance-implicit-conversion-in-loop <performance/implicit-conversion-in-loop>`,
diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/expensive-flat-container-operation.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/expensive-flat-container-operation.rst
new file mode 100644
index 00000000000000..259c89fdcd7678
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/performance/expensive-flat-container-operation.rst
@@ -0,0 +1,82 @@
+.. title:: clang-tidy - performance-expensive-flat-container-operation
+
+performance-expensive-flat-container-operation
+==============================================
+
+Warns when calling an O(N) operation on a flat container.
+
+This check operates on vector-based flat containers such as
+``std::flat_(map|set)``, ``boost::container::flat_(map|set)``, or
+``folly::sorted_vector_(map|set)``. While these containers' behavior is
+identical to usual maps/sets, the insert and erase operations are O(N). This
+check flags such operations, which are a common bad pattern, notably in loops.
+
+Below is an example of a typical bad pattern: inserting some values one by one
+into a flat container. This is O(N^2), as the container will need to shift
+elements right after each insertion.
+
+.. code-block:: c++
+
+ std::random_device generator;
+ std::uniform_int_distribution<int> distribution;
+
+ std::flat_set<int> set;
+ for (auto i = 0; i < N; ++i) {
+ set.insert(distribution(generator));
+ }
+
+The code above can be improved using a temporary vector, later inserting all
+values at once into the ``flat_set``.
+
+.. code-block:: c++
+
+ std::vector<int> temporary;
+ for (auto i = 0; i < N; ++i) {
+ temporary.push_back(distribution(generator));
+ }
+ std::flat_set<int> set(temporary.begin(), temporary.end());
+
+ // Or even better when possible, moving the temporary container:
+ // std::flat_set<int> set(std::move(temporary));
+
+For expensive-to-copy objects, ``std::move_iterator`` should be used.
+When possible, the temporary container can be moved directly into the flat
+container. When it is known that the inserted keys are sorted and uniqued, such
+as cases when they come from another flat container, ``std::sorted_unique`` can
+be used when inserting to save more cycles. Finally, if order is not important,
+hash-based containers can provide better performance.
+
+Limitations
+-----------
+
+This check is not capable of flagging insertions into a map via ``operator[]``,
+as it is not possible at compile-time to know whether it will trigger an
+insertion or a simple lookup. These cases have to be detected using dynamic
+profiling.
+
+This check is also of course not able to detect single element operations in
+loops crossing function boundaries. A more robust static analysis would be
+necessary to detect these cases.
+
+Options
+-------
+
+.. option:: WarnOutsideLoops
+
+ When disabled, the check will only warn when the single element operation is
+ directly enclosed by a loop, hence directly actionable. At the very least,
+ these cases can be improved using some temporary container.
+
+ When enabled, all insert and erase operations will be flagged.
+
+ Default is `false`.
+
+.. option:: FlatContainers
+
+ A semicolon-separated list of flat containers, with ``insert``, ``emplace``
+ and/or ``erase`` operations.
+
+ Default includes ``std::flat_(map|set)``, ``flat_multi(map|set)``,
+ ``boost::container::flat_(map|set)``,
+ ``boost::container::flat_multi(map|set)``, and
+ ``folly::sorted_vector_(map|set)``.
diff --git a/clang-tools-extra/test/.clang-format b/clang-tools-extra/test/.clang-format
deleted file mode 100644
index e3845288a2aece..00000000000000
--- a/clang-tools-extra/test/.clang-format
+++ /dev/null
@@ -1 +0,0 @@
-DisableFormat: true
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation-warn-outside-loops.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation-warn-outside-loops.cpp
new file mode 100644
index 00000000000000..0a258a0e48da1f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation-warn-outside-loops.cpp
@@ -0,0 +1,478 @@
+// RUN: %check_clang_tidy %s performance-expensive-flat-container-operation %t -- \
+// RUN: -config="{CheckOptions: \
+// RUN: [{key: performance-expensive-flat-container-operation.WarnOutsideLoops, \
+// RUN: value: true}] \
+// RUN: }"
+
+#include <stddef.h>
+
+namespace std {
+template <class T1, class T2> struct pair { pair(T1, T2); };
+
+template <class T> struct initializer_list {};
+
+template <class T> struct remove_reference { typedef T type; };
+template <class T> struct remove_reference<T &> { typedef T type; };
+template <class T> struct remove_reference<T &&> { typedef T type; };
+
+template <class T>
+typename std::remove_reference<T>::type &&move(T &&) noexcept;
+
+struct sorted_unique_t {};
+inline constexpr sorted_unique_t sorted_unique{};
+struct sorted_equivalent_t {};
+inline constexpr sorted_equivalent_t sorted_equivalent{};
+
+template <class Key, class T> struct flat_map {
+ using key_type = Key;
+ using mapped_type = T;
+ using value_type = pair<key_type, mapped_type>;
+ using reference = pair<const key_type &, mapped_type &>;
+ using const_reference = pair<const key_type &, const mapped_type &>;
+ using size_type = size_t;
+ using iterator = struct {};
+ using const_iterator = struct {};
+
+ const_iterator begin() const noexcept;
+ const_iterator end() const noexcept;
+
+ template <class... Args> pair<iterator, bool> emplace(Args &&...args);
+ template <class... Args>
+ iterator emplace_hint(const_iterator position, Args &&...args);
+
+ pair<iterator, bool> insert(const value_type &x);
+ pair<iterator, bool> insert(value_type &&x);
+ iterator insert(const_iterator position, const value_type &x);
+ iterator insert(const_iterator position, value_type &&x);
+
+ // template <class P> pair<iterator, bool> insert(P &&x);
+ // template <class P> iterator insert(const_iterator position, P &&);
+ template <class InputIter> void insert(InputIter first, InputIter last);
+ template <class InputIter>
+ void insert(sorted_unique_t, InputIter first, InputIter last);
+ template <class R> void insert_range(R &&rg);
+
+ void insert(initializer_list<value_type> il);
+ void insert(sorted_unique_t s, initializer_list<value_type> il);
+
+ template <class... Args>
+ pair<iterator, bool> try_emplace(const key_type &k, Args &&...args);
+ template <class... Args>
+ pair<iterator, bool> try_emplace(key_type &&k, Args &&...args);
+ template <class K, class... Args>
+ pair<iterator, bool> try_emplace(K &&k, Args &&...args);
+ template <class... Args>
+ iterator try_emplace(const_iterator hint, const key_type &k, Args &&...args);
+ template <class... Args>
+ iterator try_emplace(const_iterator hint, key_type &&k, Args &&...args);
+ template <class K, class... Args>
+ iterator try_emplace(const_iterator hint, K &&k, Args &&...args);
+ template <class M>
+ pair<iterator, bool> insert_or_assign(const key_type &k, M &&obj);
+ template <class M>
+ pair<iterator, bool> insert_or_assign(key_type &&k, M &&obj);
+ template <class K, class M>
+ pair<iterator, bool> insert_or_assign(K &&k, M &&obj);
+ template <class M>
+ iterator insert_or_assign(const_iterator hint, const key_type &k, M &&obj);
+ template <class M>
+ iterator insert_or_assign(const_iterator hint, key_type &&k, M &&obj);
+ template <class K, class M>
+ iterator insert_or_assign(const_iterator hint, K &&k, M &&obj);
+
+ iterator erase(iterator position);
+ iterator erase(const_iterator position);
+ size_type erase(const key_type &x);
+ template <class K> size_type erase(K &&x);
+ iterator erase(const_iterator first, const_iterator last);
+};
+
+template <class Key, class T> struct flat_multimap {
+ using key_type = Key;
+ using mapped_type = T;
+ using value_type = pair<key_type, mapped_type>;
+ using reference = pair<const key_type &, mapped_type &>;
+ using const_reference = pair<const key_type &, const mapped_type &>;
+ using size_type = size_t;
+ using iterator = struct {};
+ using const_iterator = struct {};
+
+ const_iterator begin() const noexcept;
+ const_iterator end() const noexcept;
+
+ template <class... Args> iterator emplace(Args &&...args);
+ template <class... Args>
+ iterator emplace_hint(const_iterator position, Args &&...args);
+
+ iterator insert(const value_type &x);
+ iterator insert(value_type &&x);
+ iterator insert(const_iterator position, const value_type &x);
+ iterator insert(const_iterator position, value_type &&x);
+
+ // template <class P> iterator insert(P &&x);
+ // template <class P> iterator insert(const_iterator position, P &&);
+ template <class InputIter> void insert(InputIter first, InputIter last);
+ template <class InputIter>
+ void insert(sorted_equivalent_t, InputIter first, InputIter last);
+ template <class R> void insert_range(R &&rg);
+
+ void insert(initializer_list<value_type> il);
+ void insert(sorted_equivalent_t s, initializer_list<value_type> il);
+
+ iterator erase(iterator position);
+ iterator erase(const_iterator position);
+ size_type erase(const key_type &x);
+ template <class K> size_type erase(K &&x);
+ iterator erase(const_iterator first, const_iterator last);
+
+ void swap(flat_multimap &) noexcept;
+ void clear() noexcept;
+};
+
+template <class Key> struct flat_set {
+ using key_type = Key;
+ using value_type = Key;
+ using reference = value_type &;
+ using const_reference = const value_type &;
+ using size_type = size_t;
+ using iterator = struct {};
+ using const_iterator = struct {};
+
+ const_iterator begin() const noexcept;
+ const_iterator end() const noexcept;
+
+ template <class... Args> pair<iterator, bool> emplace(Args &&...args);
+ template <class... Args>
+ iterator emplace_hint(const_iterator position, Args &&...args);
+
+ pair<iterator, bool> insert(const value_type &x);
+ pair<iterator, bool> insert(value_type &&x);
+ template <class K> pair<iterator, bool> insert(K &&x);
+ iterator insert(const_iterator position, const value_type &x);
+ iterator insert(const_iterator position, value_type &&x);
+ template <class K> iterator insert(const_iterator hint, K &&x);
+
+ template <class InputIter> void insert(InputIter first, InputIter last);
+ template <class InputIter>
+ void insert(sorted_unique_t, InputIter first, InputIter last);
+ template <class R> void insert_range(R &&rg);
+
+ void insert(initializer_list<value_type> il);
+ void insert(sorted_unique_t s, initializer_list<value_type> il);
+
+ iterator erase(iterator position);
+ iterator erase(const_iterator position);
+ size_type erase(const key_type &x);
+ template <class K> size_type erase(K &&x);
+ iterator erase(const_iterator first, const_iterator last);
+};
+
+template <class Key> struct flat_multiset {
+ using key_type = Key;
+ using value_type = Key;
+ using reference = value_type &;
+ using const_reference = const value_type &;
+ using size_type = size_t;
+ using iterator = struct {};
+ using const_iterator = struct {};
+
+ const_iterator begin() const noexcept;
+ const_iterator end() const noexcept;
+
+ template <class... Args> iterator emplace(Args &&...args);
+ template <class... Args>
+ iterator emplace_hint(const_iterator position, Args &&...args);
+
+ iterator insert(const value_type &x);
+ iterator insert(value_type &&x);
+ iterator insert(const_iterator position, const value_type &x);
+ iterator insert(const_iterator position, value_type &&x);
+
+ template <class InputIter> void insert(InputIter first, InputIter last);
+ template <class InputIter>
+ void insert(sorted_equivalent_t, InputIter first, InputIter last);
+ template <class R> void insert_range(R &&rg);
+
+ void insert(initializer_list<value_type> il);
+ void insert(sorted_equivalent_t s, initializer_list<value_type> il);
+
+ iterator erase(iterator position);
+ iterator erase(const_iterator position);
+ size_type erase(const key_type &x);
+ template <class K> size_type erase(K &&x);
+ iterator erase(const_iterator first, const_iterator last);
+};
+} // namespace std
+
+namespace boost::container {
+struct ordered_unique_range_t {};
+inline constexpr ordered_unique_range_t ordered_unique_range{};
+struct ordered_range_t {};
+inline constexpr ordered_range_t ordered_range{};
+
+template <typename Key, typename T> struct flat_map {
+ using key_type = Key;
+ using mapped_type = T;
+ using value_type = std::pair<Key, T>;
+ using size_type = size_t;
+ using iterator = struct {};
+ using const_iterator = struct {};
+
+ const_iterator begin() const noexcept;
+ const_iterator end() const noexcept;
+
+ template <typename M>
+ std::pair<iterator, bool> insert_or_assign(const key_type &, M &&);
+ template <typename M>
+ std::pair<iterator, bool> insert_or_assign(key_type &&, M &&);
+ template <typename M>
+ iterator insert_or_assign(const_iterator, const key_type &, M &&);
+ template <typename M>
+ iterator insert_or_assign(const_iterator, key_type &&, M &&);
+ template <class... Args> std::pair<iterator, bool> emplace(Args &&...);
+ template <class... Args> iterator emplace_hint(const_iterator, Args &&...);
+ template <class... Args>
+ std::pair<iterator, bool> try_emplace(const key_type &, Args &&...);
+ template <class... Args>
+ iterator try_emplace(const_iterator, const key_type &, Args &&...);
+ template <class... Args>
+ std::pair<iterator, bool> try_emplace(key_type &&, Args &&...);
+ template <class... Args>
+ iterator try_emplace(const_iterator, key_type &&, Args &&...);
+ std::pair<iterator, bool> insert(const value_type &);
+ std::pair<iterator, bool> insert(value_type &&);
+ template <typename Pair> std::pair<iterator, bool> insert(Pair &&);
+ iterator insert(const_iterator, const value_type &);
+ iterator insert(const_iterator, value_type &&);
+ template <typename Pair> iterator insert(const_iterator, Pair &&);
+ template <typename InputIterator> void insert(InputIterator, InputIterator);
+ template <typename InputIterator>
+ void insert(ordered_unique_range_t, InputIterator, InputIterator);
+ void insert(std::initializer_list<value_type>);
+ void insert(ordered_unique_range_t, std::initializer_list<value_type>);
+ iterator erase(const_iterator);
+ size_type erase(const key_type &);
+ iterator erase(const_iterator, const_iterator);
+};
+
+template <typename Key, typename T> struct flat_multimap {
+ using key_type = Key;
+ using mapped_type = T;
+ using value_type = std::pair<Key, T>;
+ using size_type = size_t;
+ using iterator = struct {};
+ using const_iterator = struct {};
+
+ const_iterator begin() const noexcept;
+ const_iterator end() const noexcept;
+
+ template <class... Args> iterator emplace(Args &&...);
+ template <class... Args> iterator emplace_hint(const_iterator, Args &&...);
+ iterator insert(const value_type &);
+ template <typename Pair> iterator insert(Pair &&);
+ iterator insert(const_iterator, const value_type &);
+ template <typename Pair> iterator insert(const_iterator, Pair &&);
+ template <typename InputIterator> void insert(InputIterator, InputIterator);
+ template <typename InputIterator>
+ void insert(ordered_range_t, InputIterator, InputIterator);
+ void insert(std::initializer_list<value_type>);
+ void insert(ordered_range_t, std::initializer_list<value_type>);
+ iterator erase(const_iterator);
+ size_type erase(const key_type &);
+ iterator erase(const_iterator, const_iterator);
+};
+
+template <typename Key> struct flat_set {
+ using key_type = Key;
+ using value_type = Key;
+ using size_type = size_t;
+ using iterator = struct {};
+ using const_iterator = struct {};
+
+ const_iterator begin() const noexcept;
+ const_iterator end() const noexcept;
+ template <class... Args> std::pair<iterator, bool> emplace(Args &&...);
+ template <class... Args> iterator emplace_hint(const_iterator, Args &&...);
+ std::pair<iterator, bool> insert(const value_type &);
+ std::pair<iterator, bool> insert(value_type &&);
+ iterator insert(const_iterator, const value_type &);
+ iterator insert(const_iterator, value_type &&);
+ template <typename InputIterator> void insert(InputIterator, InputIterator);
+ template <typename InputIterator>
+ void insert(ordered_unique_range_t, InputIterator, InputIterator);
+ void insert(std::initializer_list<value_type>);
+ void insert(ordered_unique_range_t, std::initializer_list<value_type>);
+ size_type erase(const key_type &);
+ iterator erase(const_iterator);
+ iterator erase(const_iterator, const_iterator);
+};
+
+template <typename Key> struct flat_multiset {
+ using key_type = Key;
+ using value_type = Key;
+ using size_type = size_t;
+ using iterator = struct {};
+ using const_iterator = struct {};
+
+ const_iterator begin() const noexcept;
+ const_iterator end() const noexcept;
+ template <class... Args> iterator emplace(Args &&...);
+ template <class... Args> iterator emplace_hint(const_iterator, Args &&...);
+ iterator insert(const value_type &);
+ iterator insert(value_type &&);
+ iterator insert(const_iterator, const value_type &);
+ iterator insert(const_iterator, value_type &&);
+ template <typename InputIterator> void insert(InputIterator, InputIterator);
+ template <typename InputIterator>
+ void insert(ordered_range_t, InputIterator, InputIterator);
+ void insert(std::initializer_list<value_type>);
+ void insert(ordered_range_t, std::initializer_list<value_type>);
+ iterator erase(const_iterator);
+ size_type erase(const key_type &);
+ iterator erase(const_iterator, const_iterator);
+};
+} // namespace boost::container
+
+namespace folly {
+struct sorted_unique_t {};
+inline constexpr sorted_unique_t sorted_unique{};
+
+template <class T> struct sorted_vector_set {
+ using value_type = T;
+ using key_type = T;
+ using iterator = struct {};
+ using const_iterator = struct {};
+ using size_type = size_t;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ std::pair<iterator, bool> insert(const value_type &value);
+ std::pair<iterator, bool> insert(value_type &&value);
+ iterator insert(const_iterator hint, const value_type &value);
+ iterator insert(const_iterator hint, value_type &&value);
+ template <class InputIterator>
+ void insert(InputIterator first, InputIterator last);
+ template <class InputIterator>
+ void insert(sorted_unique_t, InputIterator first, InputIterator last);
+ void insert(std::initializer_list<value_type> ilist);
+
+ template <typename... Args> std::pair<iterator, bool> emplace(Args &&...args);
+ std::pair<iterator, bool> emplace(const value_type &value);
+ std::pair<iterator, bool> emplace(value_type &&value);
+
+ template <typename... Args>
+ iterator emplace_hint(const_iterator hint, Args &&...args);
+ iterator emplace_hint(const_iterator hint, const value_type &value);
+ iterator emplace_hint(const_iterator hint, value_type &&value);
+
+ size_type erase(const key_type &key);
+ iterator erase(const_iterator it);
+ iterator erase(const_iterator first, const_iterator last);
+};
+
+template <class Key, class Value> struct sorted_vector_map {
+ typedef Key key_type;
+ typedef Value mapped_type;
+ using value_type = std::pair<Key, Value>;
+ using iterator = struct {};
+ using const_iterator = struct {};
+ using size_type = size_t;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ std::pair<iterator, bool> insert(const value_type &value);
+ std::pair<iterator, bool> insert(value_type &&value);
+ iterator insert(const_iterator hint, const value_type &value);
+ iterator insert(const_iterator hint, value_type &&value);
+ template <class InputIterator>
+ void insert(InputIterator first, InputIterator last);
+ template <class InputIterator>
+ void insert(sorted_unique_t, InputIterator first, InputIterator last);
+ void insert(std::initializer_list<value_type> ilist);
+
+ template <typename... Args> std::pair<iterator, bool> emplace(Args &&...args);
+ std::pair<iterator, bool> emplace(const value_type &value);
+ std::pair<iterator, bool> emplace(value_type &&value);
+
+ template <typename... Args>
+ iterator emplace_hint(const_iterator hint, Args &&...args);
+ iterator emplace_hint(const_iterator hint, const value_type &value);
+ iterator emplace_hint(const_iterator hint, value_type &&value);
+
+ template <typename... Args>
+ std::pair<iterator, bool> try_emplace(key_type &&k, Args &&...args);
+ template <typename... Args>
+ std::pair<iterator, bool> try_emplace(const key_type &k, Args &&...args);
+
+ template <typename M>
+ std::pair<iterator, bool> insert_or_assign(const key_type &k, M &&obj);
+ template <typename M>
+ std::pair<iterator, bool> insert_or_assign(key_type &&k, M &&obj);
+ template <class M>
+ iterator insert_or_assign(const_iterator hint, const key_type &k, M &&obj);
+ template <class M>
+ iterator insert_or_assign(const_iterator hint, key_type &&k, M &&obj);
+
+ size_type erase(const key_type &key);
+ iterator erase(const_iterator it);
+ iterator erase(const_iterator first, const_iterator last);
+};
+} // namespace folly
+
+struct MyKey {};
+
+void testStdFlapMapErase(std::flat_map<MyKey, int> map) {
+ map.erase(MyKey());
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+
+ map.erase(map.begin());
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+
+ map.erase(map.begin(), map.end());
+}
+
+void testWithUsing() {
+ using MySpecialMap = std::flat_map<MyKey, int>;
+ MySpecialMap map;
+ map.erase(MyKey());
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+}
+
+void testWithArrow() {
+ auto *map = new std::flat_map<MyKey, int>();
+ map->erase(MyKey());
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+}
+
+void testWithSubclass() {
+ struct MyMap : std::flat_map<MyKey, int> {};
+ MyMap map;
+ map.erase(MyKey());
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+
+ using MySpecialMap = MyMap;
+ MySpecialMap map2;
+ map2.erase(MyKey());
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+
+ using MySpecialMap2 = std::flat_map<MyKey, int>;
+ struct MyMap2 : MySpecialMap2 {};
+ MyMap2 map3;
+ map3.erase(MyKey());
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+
+ using MySpecialMap3 = MyMap2;
+ auto *map4 = new MySpecialMap3();
+ map4->erase(MyKey());
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation.cpp
new file mode 100644
index 00000000000000..838f757fab8f2e
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation.cpp
@@ -0,0 +1,91 @@
+// RUN: %check_clang_tidy %s performance-expensive-flat-container-operation %t -- \
+// RUN: -config="{CheckOptions: \
+// RUN: [{key: performance-expensive-flat-container-operation.WarnOutsideLoops, \
+// RUN: value: false}, \
+// RUN: {key: performance-expensive-flat-container-operation.FlatContainers, \
+// RUN: value: "::MyFlatSet"}] \
+// RUN: }"
+
+#include <stddef.h>
+
+template <class Key> struct MyFlatSet {
+ using key_type = Key;
+ void erase(const key_type &x);
+};
+
+void testWhileLoop() {
+ MyFlatSet<int> set;
+ while (true) {
+ set.erase(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ }
+}
+
+void testOutsideLoop(MyFlatSet<int> &set) { set.erase(0); }
+
+void testForLoop() {
+ MyFlatSet<int> set;
+ for (;;) {
+ set.erase(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ }
+}
+
+template <class Iterable> void testRangeForLoop(const Iterable &v) {
+ MyFlatSet<int> set;
+ for (const auto &x : v) {
+ set.erase(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ }
+}
+
+void testDoWhileLoop() {
+ MyFlatSet<int> set;
+ do {
+ set.erase(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ } while (true);
+}
+
+void testMultipleCasesInLoop() {
+ MyFlatSet<int> set;
+ for (;;) {
+ set.erase(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ set.erase(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ }
+
+ MyFlatSet<int> set2;
+ MyFlatSet<int> set3;
+ for (;;) {
+ set2.erase(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ set3.erase(1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ }
+
+ MyFlatSet<int> set4;
+ for (;;) {
+ set4.erase(0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element
+ // operations are expensive for flat containers.
+ MyFlatSet<int> set5;
+ set5.erase(1);
+ }
+}
+
+void testOperationAndDeclarationInLoop() {
+ for (;;) {
+ MyFlatSet<int> set;
+ set.erase(0);
+ }
+}
More information about the cfe-commits
mailing list