mboehme created this revision.
mboehme added a reviewer: sammccall.
mboehme added a project: clang-tools-extra.
Herald added a subscriber: xazax.hun.
mboehme requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

We have no way to reason about the bool returned by try_emplace, so we
simply ignore any std::move()s that happen in a try_emplace argument.
A lot of the time in this situation, the code will be checking the
bool and doing something else if it turns out the value wasn't moved
into the map, and this has been causing false positives so far.

I don't currently have any intentions of handling "maybe move" functions
more generally.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D98034

Files:
  clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
  clang-tools-extra/docs/clang-tidy/checks/bugprone-use-after-move.rst
  clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp
+++ clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp
@@ -32,6 +32,31 @@
   bool expired() const;
 };
 
+template <typename T1, typename T2>
+struct pair {};
+
+template <typename Key, typename T>
+struct map {
+  struct iterator {};
+
+  map();
+  void clear();
+  bool empty();
+  template <class... Args>
+  pair<iterator, bool> try_emplace(const Key &key, Args &&...args);
+};
+
+template <typename Key, typename T>
+struct unordered_map {
+  struct iterator {};
+
+  unordered_map();
+  void clear();
+  bool empty();
+  template <class... Args>
+  pair<iterator, bool> try_emplace(const Key &key, Args &&...args);
+};
+
 #define DECLARE_STANDARD_CONTAINER(name) \
   template <typename T>                  \
   struct name {                          \
@@ -55,11 +80,9 @@
 DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(forward_list);
 DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(list);
 DECLARE_STANDARD_CONTAINER(set);
-DECLARE_STANDARD_CONTAINER(map);
 DECLARE_STANDARD_CONTAINER(multiset);
 DECLARE_STANDARD_CONTAINER(multimap);
 DECLARE_STANDARD_CONTAINER(unordered_set);
-DECLARE_STANDARD_CONTAINER(unordered_map);
 DECLARE_STANDARD_CONTAINER(unordered_multiset);
 DECLARE_STANDARD_CONTAINER(unordered_multimap);
 
@@ -483,6 +506,35 @@
   }
 };
 
+// Ignore moves that happen in a try_emplace.
+void try_emplace() {
+  {
+    std::map<int, A> amap;
+    A a;
+    amap.try_emplace(1, std::move(a));
+    a.foo();
+  }
+  {
+    std::unordered_map<int, A> amap;
+    A a;
+    amap.try_emplace(1, std::move(a));
+    a.foo();
+  }
+  {
+    // Don't make any assumptions about methods called try_emplace that don't
+    // belong to a standard container.
+    struct map {
+      void try_emplace(int, A &&);
+    };
+    map mymap;
+    A a;
+    mymap.try_emplace(1, std::move(a));
+    a.foo();
+    // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
+    // CHECK-NOTES: [[@LINE-3]]:11: note: move occurred here
+  }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Tests involving control flow.
 
@@ -776,7 +828,7 @@
     container.empty();
   }
   {
-    std::map<int> container;
+    std::map<int, int> container;
     std::move(container);
     container.clear();
     container.empty();
@@ -800,7 +852,7 @@
     container.empty();
   }
   {
-    std::unordered_map<int> container;
+    std::unordered_map<int, int> container;
     std::move(container);
     container.clear();
     container.empty();
Index: clang-tools-extra/docs/clang-tidy/checks/bugprone-use-after-move.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/bugprone-use-after-move.rst
+++ clang-tools-extra/docs/clang-tidy/checks/bugprone-use-after-move.rst
@@ -169,6 +169,10 @@
 The check will assume that the last line causes a move, even though, in this
 particular case, it does not. Again, this is intentional.
 
+There is one special case: A call to ``std::move`` inside a ``try_emplace`` call
+on a standard map type is ignored. This is to avoid spurious warnings, as the
+check has no way to reason about the ``bool`` returned by ``try_emplace``.
+
 When analyzing the order in which moves, uses and reinitializations happen (see
 section `Unsequenced moves, uses, and reinitializations`_), the move is assumed
 to occur in whichever function the result of the ``std::move`` is passed to.
Index: clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
===================================================================
--- clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
+++ clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
@@ -393,12 +393,22 @@
 }
 
 void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
+  auto StandardMapMatcher =
+      hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(
+          cxxRecordDecl(hasAnyName("::std::map", "::std::unordered_map"))))));
+
   auto CallMoveMatcher =
       callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1),
                hasArgument(0, declRefExpr().bind("arg")),
                anyOf(hasAncestor(lambdaExpr().bind("containing-lambda")),
                      hasAncestor(functionDecl().bind("containing-func"))),
-               unless(inDecltypeOrTemplateArg()))
+               unless(inDecltypeOrTemplateArg()),
+               // Ignore std::move()s inside a try_emplace() call to avoid false
+               // positives as we can't reason about the bool returned by
+               // try_emplace.
+               unless(hasParent(cxxMemberCallExpr(
+                   on(expr(declRefExpr(), StandardMapMatcher)),
+                   callee(cxxMethodDecl(hasName("try_emplace")))))))
           .bind("call-move");
 
   Finder->addMatcher(
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to