[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