// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t -- -- -fno-delayed-template-parsing typedef decltype(nullptr) nullptr_t; namespace std { typedef unsigned size_t; template struct unique_ptr { unique_ptr(); T *get() const; explicit operator bool() const; void reset(T *ptr); T &operator*() const; T *operator->() const; T& operator[](size_t i) const; }; template struct shared_ptr { shared_ptr(); T *get() const; explicit operator bool() const; void reset(T *ptr); T &operator*() const; T *operator->() const; }; template struct weak_ptr { weak_ptr(); bool expired() const; }; template struct pair {}; template struct map { struct iterator {}; map(); void clear(); bool empty(); template pair try_emplace(const Key &key, Args &&...args); }; template struct unordered_map { struct iterator {}; unordered_map(); void clear(); bool empty(); template pair try_emplace(const Key &key, Args &&...args); }; #define DECLARE_STANDARD_CONTAINER(name) \ template \ struct name { \ name(); \ void clear(); \ bool empty(); \ } #define DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(name) \ template \ struct name { \ name(); \ void clear(); \ bool empty(); \ void assign(size_t, const T &); \ } DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(basic_string); DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(vector); DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(deque); DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(forward_list); DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(list); DECLARE_STANDARD_CONTAINER(set); DECLARE_STANDARD_CONTAINER(multiset); DECLARE_STANDARD_CONTAINER(multimap); DECLARE_STANDARD_CONTAINER(unordered_set); DECLARE_STANDARD_CONTAINER(unordered_multiset); DECLARE_STANDARD_CONTAINER(unordered_multimap); typedef basic_string string; template struct remove_reference; template struct remove_reference { typedef _Tp type; }; template struct remove_reference<_Tp &> { typedef _Tp type; }; template struct remove_reference<_Tp &&> { typedef _Tp type; }; template constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) noexcept { return static_cast::type &&>(__t); } } // namespace std class A { public: A(); A(const A &); A(A &&); A &operator=(const A &); A &operator=(A &&); void foo() const; int getInt() const; operator bool() const; int i; }; template class AnnotatedContainer { public: AnnotatedContainer(); void foo() const; [[clang::reinitializes]] void clear(); }; //////////////////////////////////////////////////////////////////////////////// // General tests. // Simple case. void simple() { A a; a.foo(); A other_a = std::move(a); a.foo(); // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:15: note: move occurred here } // Don't flag a move-to-self. void selfMove() { A a; a = std::move(a); a.foo(); } // A warning should only be emitted for one use-after-move. void onlyFlagOneUseAfterMove() { A a; a.foo(); A other_a = std::move(a); a.foo(); // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:15: note: move occurred here a.foo(); } void moveAfterMove() { // Move-after-move also counts as a use. { A a; std::move(a); std::move(a); // CHECK-NOTES: [[@LINE-1]]:15: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } // This is also true if the move itself turns into the use on the second loop // iteration. { A a; for (int i = 0; i < 10; ++i) { std::move(a); // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:7: note: move occurred here // CHECK-NOTES: [[@LINE-3]]:17: note: the use happens in a later loop } } } // Checks also works on function parameters that have a use-after move. void parameters(A a) { std::move(a); a.foo(); // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here } void standardSmartPtr() { // std::unique_ptr<>, std::shared_ptr<> and std::weak_ptr<> are guaranteed to // be null after a std::move. So the check only flags accesses that would // dereference the pointer. { std::unique_ptr ptr; std::move(ptr); ptr.get(); static_cast(ptr); *ptr; // CHECK-NOTES: [[@LINE-1]]:6: warning: 'ptr' used after it was moved // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here } { std::unique_ptr ptr; std::move(ptr); ptr->foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } { std::unique_ptr ptr; std::move(ptr); ptr[0]; // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } { std::shared_ptr ptr; std::move(ptr); ptr.get(); static_cast(ptr); *ptr; // CHECK-NOTES: [[@LINE-1]]:6: warning: 'ptr' used after it was moved // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here } { std::shared_ptr ptr; std::move(ptr); ptr->foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } { // std::weak_ptr<> cannot be dereferenced directly, so we only check that // member functions may be called on it after a move. std::weak_ptr ptr; std::move(ptr); ptr.expired(); } // Make sure we recognize std::unique_ptr<> or std::shared_ptr<> if they're // wrapped in a typedef. { typedef std::unique_ptr PtrToA; PtrToA ptr; std::move(ptr); ptr.get(); } { typedef std::shared_ptr PtrToA; PtrToA ptr; std::move(ptr); ptr.get(); } // And we don't get confused if the template argument is a little more // involved. { struct B { typedef A AnotherNameForA; }; std::unique_ptr ptr; std::move(ptr); ptr.get(); } // Make sure we treat references to smart pointers correctly. { std::unique_ptr ptr; std::unique_ptr& ref_to_ptr = ptr; std::move(ref_to_ptr); ref_to_ptr.get(); } { std::unique_ptr ptr; std::unique_ptr&& rvalue_ref_to_ptr = std::move(ptr); std::move(rvalue_ref_to_ptr); rvalue_ref_to_ptr.get(); } // We don't give any special treatment to types that are called "unique_ptr" // or "shared_ptr" but are not in the "::std" namespace. { struct unique_ptr { void get(); } ptr; std::move(ptr); ptr.get(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } } // The check also works in member functions. class Container { void useAfterMoveInMemberFunction() { A a; std::move(a); a.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } }; // We see the std::move() if it's inside a declaration. void moveInDeclaration() { A a; A another_a(std::move(a)); a.foo(); // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } // We see the std::move if it's inside an initializer list. Initializer lists // are a special case because they cause ASTContext::getParents() to return // multiple parents for certain nodes in their subtree. This is because // RecursiveASTVisitor visits both the syntactic and semantic forms of // InitListExpr, and the parent-child relationships are different between the // two forms. void moveInInitList() { struct S { A a; }; A a; S s{std::move(a)}; a.foo(); // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here } void lambdas() { // Use-after-moves inside a lambda should be detected. { A a; auto lambda = [a] { std::move(a); a.foo(); // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here }; } // This is just as true if the variable was declared inside the lambda. { auto lambda = [] { A a; std::move(a); a.foo(); // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:7: note: move occurred here }; } // But don't warn if the move happened inside the lambda but the use happened // outside -- because // - the 'a' inside the lambda is a copy, and // - we don't know when the lambda will get called anyway { A a; auto lambda = [a] { std::move(a); }; a.foo(); } // Don't warn if 'a' is a copy inside a synchronous lambda { A a; A copied{[a] mutable { return std::move(a); }()}; a.foo(); } // False negative (should warn if 'a' is a ref inside a synchronous lambda) { A a; A moved{[&a] mutable { return std::move(a); }()}; a.foo(); } // Warn if the use consists of a capture that happens after a move. { A a; std::move(a); auto lambda = [a]() { a.foo(); }; // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } // ...even if the capture was implicit. { A a; std::move(a); auto lambda = [=]() { a.foo(); }; // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } // Same tests but for capture by reference. { A a; std::move(a); auto lambda = [&a]() { a.foo(); }; // CHECK-NOTES: [[@LINE-1]]:21: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } { A a; std::move(a); auto lambda = [&]() { a.foo(); }; // CHECK-NOTES: [[@LINE-1]]:20: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } // But don't warn if the move happened after the capture. { A a; auto lambda = [a]() { a.foo(); }; std::move(a); } // ...and again, same thing with an implicit move. { A a; auto lambda = [=]() { a.foo(); }; std::move(a); } // Same tests but for capture by reference. { A a; auto lambda = [&a]() { a.foo(); }; std::move(a); } { A a; auto lambda = [&]() { a.foo(); }; std::move(a); } { A a; auto lambda = [a = std::move(a)] { a.foo(); }; a.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:24: note: move occurred here } } // Use-after-moves are detected in uninstantiated templates if the moved type // is not a dependent type. template void movedTypeIsNotDependentType() { T t; A a; std::move(a); a.foo(); // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here } // And if the moved type is a dependent type, the use-after-move is detected if // the template is instantiated. template void movedTypeIsDependentType() { T t; std::move(t); t.foo(); // CHECK-NOTES: [[@LINE-1]]:3: warning: 't' used after it was moved // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here } template void movedTypeIsDependentType(); // We handle the case correctly where the move consists of an implicit call // to a conversion operator. void implicitConversionOperator() { struct Convertible { operator A() && { return A(); } }; void takeA(A a); Convertible convertible; takeA(std::move(convertible)); convertible; // CHECK-NOTES: [[@LINE-1]]:3: warning: 'convertible' used after it was moved // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here } // Using decltype on an expression is not a use. void decltypeIsNotUse() { A a; std::move(a); decltype(a) other_a; } // Ignore moves or uses that occur as part of template arguments. template class ClassTemplate { public: void foo(A a); }; template void functionTemplate(A a); void templateArgIsNotUse() { { // A pattern like this occurs in the EXPECT_EQ and ASSERT_EQ macros in // Google Test. A a; ClassTemplate().foo(std::move(a)); } { A a; functionTemplate(std::move(a)); } } // Ignore moves of global variables. A global_a; void ignoreGlobalVariables() { std::move(global_a); global_a.foo(); } // Ignore moves of member variables. class IgnoreMemberVariables { A a; static A static_a; void f() { std::move(a); a.foo(); std::move(static_a); static_a.foo(); } }; // Ignore moves that happen in a try_emplace. void ignoreMoveInTryEmplace() { { std::map amap; A a; amap.try_emplace(1, std::move(a)); a.foo(); } { std::unordered_map amap; A a; amap.try_emplace(1, std::move(a)); a.foo(); } } //////////////////////////////////////////////////////////////////////////////// // Tests involving control flow. void useAndMoveInLoop() { // Warn about use-after-moves if they happen in a later loop iteration than // the std::move(). { A a; for (int i = 0; i < 10; ++i) { a.foo(); // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE+2]]:7: note: move occurred here // CHECK-NOTES: [[@LINE-3]]:7: note: the use happens in a later loop std::move(a); } } // However, this case shouldn't be flagged -- the scope of the declaration of // 'a' is important. { for (int i = 0; i < 10; ++i) { A a; a.foo(); std::move(a); } } // Same as above, except that we have an unrelated variable being declared in // the same declaration as 'a'. This case is interesting because it tests that // the synthetic DeclStmts generated by the CFG are sequenced correctly // relative to the other statements. { for (int i = 0; i < 10; ++i) { A a, other; a.foo(); std::move(a); } } // Don't warn if we return after the move. { A a; for (int i = 0; i < 10; ++i) { a.foo(); if (a.getInt() > 0) { std::move(a); return; } } } } void differentBranches(int i) { // Don't warn if the use is in a different branch from the move. { A a; if (i > 0) { std::move(a); } else { a.foo(); } } // Same thing, but with a ternary operator. { A a; i > 0 ? (void)std::move(a) : a.foo(); } // A variation on the theme above. { A a; a.getInt() > 0 ? a.getInt() : A(std::move(a)).getInt(); } // Same thing, but with a switch statement. { A a; switch (i) { case 1: std::move(a); break; case 2: a.foo(); break; } } // However, if there's a fallthrough, we do warn. { A a; switch (i) { case 1: std::move(a); case 2: a.foo(); // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-4]]:7: note: move occurred here break; } } } // False positive: A use-after-move is flagged even though the "if (b)" and // "if (!b)" are mutually exclusive. void mutuallyExclusiveBranchesFalsePositive(bool b) { A a; if (b) { std::move(a); } if (!b) { a.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-5]]:5: note: move occurred here } } // Destructors marked [[noreturn]] are handled correctly in the control flow // analysis. (These are used in some styles of assertion macros.) class FailureLogger { public: FailureLogger(); [[noreturn]] ~FailureLogger(); void log(const char *); }; #define ASSERT(x) \ while (x) \ FailureLogger().log(#x) bool operationOnA(A); void noreturnDestructor() { A a; // The while loop in the ASSERT() would ordinarily have the potential to cause // a use-after-move because the second iteration of the loop would be using a // variable that had been moved from in the first iteration. Check that the // CFG knows that the second iteration of the loop is never reached because // the FailureLogger destructor is marked [[noreturn]]. ASSERT(operationOnA(std::move(a))); } #undef ASSERT //////////////////////////////////////////////////////////////////////////////// // Tests for reinitializations template void swap(T &a, T &b) { T tmp = std::move(a); a = std::move(b); b = std::move(tmp); } void assignments(int i) { // Don't report a use-after-move if the variable was assigned to in the // meantime. { A a; std::move(a); a = A(); a.foo(); } // The assignment should also be recognized if move, assignment and use don't // all happen in the same block (but the assignment is still guaranteed to // prevent a use-after-move). { A a; if (i == 1) { std::move(a); a = A(); } if (i == 2) { a.foo(); } } { A a; if (i == 1) { std::move(a); } if (i == 2) { a = A(); a.foo(); } } // The built-in assignment operator should also be recognized as a // reinitialization. (std::move() may be called on built-in types in template // code.) { int a1 = 1, a2 = 2; swap(a1, a2); } // A std::move() after the assignment makes the variable invalid again. { A a; std::move(a); a = A(); std::move(a); a.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } // Report a use-after-move if we can't be sure that the variable was assigned // to. { A a; std::move(a); if (i < 10) { a = A(); } if (i > 5) { a.foo(); // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-7]]:5: note: move occurred here } } } // Passing the object to a function through a non-const pointer or reference // counts as a re-initialization. void passByNonConstPointer(A *); void passByNonConstReference(A &); void passByNonConstPointerIsReinit() { { A a; std::move(a); passByNonConstPointer(&a); a.foo(); } { A a; std::move(a); passByNonConstReference(a); a.foo(); } } // Passing the object through a const pointer or reference counts as a use -- // since the called function cannot reinitialize the object. void passByConstPointer(const A *); void passByConstReference(const A &); void passByConstPointerIsUse() { { // Declaring 'a' as const so that no ImplicitCastExpr is inserted into the // AST -- we wouldn't want the check to rely solely on that to detect a // const pointer argument. const A a; std::move(a); passByConstPointer(&a); // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } const A a; std::move(a); passByConstReference(a); // CHECK-NOTES: [[@LINE-1]]:24: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here } // Clearing a standard container using clear() is treated as a // re-initialization. void standardContainerClearIsReinit() { { std::string container; std::move(container); container.clear(); container.empty(); } { std::vector container; std::move(container); container.clear(); container.empty(); auto container2 = container; std::move(container2); container2.clear(); container2.empty(); } { std::deque container; std::move(container); container.clear(); container.empty(); } { std::forward_list container; std::move(container); container.clear(); container.empty(); } { std::list container; std::move(container); container.clear(); container.empty(); } { std::set container; std::move(container); container.clear(); container.empty(); } { std::map container; std::move(container); container.clear(); container.empty(); } { std::multiset container; std::move(container); container.clear(); container.empty(); } { std::multimap container; std::move(container); container.clear(); container.empty(); } { std::unordered_set container; std::move(container); container.clear(); container.empty(); } { std::unordered_map container; std::move(container); container.clear(); container.empty(); } { std::unordered_multiset container; std::move(container); container.clear(); container.empty(); } { std::unordered_multimap container; std::move(container); container.clear(); container.empty(); } // This should also work for typedefs of standard containers. { typedef std::vector IntVector; IntVector container; std::move(container); container.clear(); container.empty(); } // But it shouldn't work for non-standard containers. { // This might be called "vector", but it's not in namespace "std". struct vector { void clear() {} } container; std::move(container); container.clear(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container' used after it was // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } // An intervening clear() on a different container does not reinitialize. { std::vector container1, container2; std::move(container1); container2.clear(); container1.empty(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container1' used after it was // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here } } // Clearing a standard container using assign() is treated as a // re-initialization. void standardContainerAssignIsReinit() { { std::string container; std::move(container); container.assign(0, ' '); container.empty(); } { std::vector container; std::move(container); container.assign(0, 0); container.empty(); } { std::deque container; std::move(container); container.assign(0, 0); container.empty(); } { std::forward_list container; std::move(container); container.assign(0, 0); container.empty(); } { std::list container; std::move(container); container.clear(); container.empty(); } // But it doesn't work for non-standard containers. { // This might be called "vector", but it's not in namespace "std". struct vector { void assign(std::size_t, int) {} } container; std::move(container); container.assign(0, 0); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container' used after it was // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } // An intervening assign() on a different container does not reinitialize. { std::vector container1, container2; std::move(container1); container2.assign(0, 0); container1.empty(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'container1' used after it was // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here } } // Resetting the standard smart pointer types using reset() is treated as a // re-initialization. (We don't test std::weak_ptr<> because it can't be // dereferenced directly.) void standardSmartPointerResetIsReinit() { { std::unique_ptr ptr; std::move(ptr); ptr.reset(new A); *ptr; } { std::shared_ptr ptr; std::move(ptr); ptr.reset(new A); *ptr; } } void reinitAnnotation() { { AnnotatedContainer obj; std::move(obj); obj.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'obj' used after it was // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } { AnnotatedContainer obj; std::move(obj); obj.clear(); obj.foo(); } { // Calling clear() on a different object to the one that was moved is not // considered a reinitialization. AnnotatedContainer obj1, obj2; std::move(obj1); obj2.clear(); obj1.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'obj1' used after it was // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here } } //////////////////////////////////////////////////////////////////////////////// // Tests related to order of evaluation within expressions // Relative sequencing of move and use. void passByRvalueReference(int i, A &&a); void passByValue(int i, A a); void passByValue(A a, int i); A g(A, A &&); int intFromA(A &&); int intFromInt(int); void sequencingOfMoveAndUse() { // This case is fine because the move only happens inside // passByRvalueReference(). At this point, a.getInt() is guaranteed to have // been evaluated. { A a; passByRvalueReference(a.getInt(), std::move(a)); } // However, if we pass by value, the move happens when the move constructor is // called to create a temporary, and this happens before the call to // passByValue(). Because the order in which arguments are evaluated isn't // defined, the move may happen before the call to a.getInt(). // // Check that we warn about a potential use-after move for both orderings of // a.getInt() and std::move(a), independent of the order in which the // arguments happen to get evaluated by the compiler. { A a; passByValue(a.getInt(), std::move(a)); // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:29: note: move occurred here // CHECK-NOTES: [[@LINE-3]]:17: note: the use and move are unsequenced } { A a; passByValue(std::move(a), a.getInt()); // CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:17: note: move occurred here // CHECK-NOTES: [[@LINE-3]]:31: note: the use and move are unsequenced } // An even more convoluted example. { A a; g(g(a, std::move(a)), g(a, std::move(a))); // CHECK-NOTES: [[@LINE-1]]:9: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:27: note: move occurred here // CHECK-NOTES: [[@LINE-3]]:9: note: the use and move are unsequenced // CHECK-NOTES: [[@LINE-4]]:29: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-5]]:7: note: move occurred here // CHECK-NOTES: [[@LINE-6]]:29: note: the use and move are unsequenced } // This case is fine because the actual move only happens inside the call to // operator=(). a.getInt(), by necessity, is evaluated before that call. { A a; A vec[1]; vec[a.getInt()] = std::move(a); } // However, in the following case, the move happens before the assignment, and // so the order of evaluation is not guaranteed. { A a; int v[3]; v[a.getInt()] = intFromA(std::move(a)); // CHECK-NOTES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:21: note: move occurred here // CHECK-NOTES: [[@LINE-3]]:7: note: the use and move are unsequenced } { A a; int v[3]; v[intFromA(std::move(a))] = intFromInt(a.i); // CHECK-NOTES: [[@LINE-1]]:44: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:7: note: move occurred here // CHECK-NOTES: [[@LINE-3]]:44: note: the use and move are unsequenced } } // Relative sequencing of move and reinitialization. If the two are unsequenced, // we conservatively assume that the move happens after the reinitialization, // i.e. the that object does not get reinitialized after the move. A MutateA(A a); void passByValue(A a1, A a2); void sequencingOfMoveAndReinit() { // Move and reinitialization as function arguments (which are indeterminately // sequenced). Again, check that we warn for both orderings. { A a; passByValue(std::move(a), (a = A())); a.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:17: note: move occurred here } { A a; passByValue((a = A()), std::move(a)); a.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:28: note: move occurred here } // Common usage pattern: Move the object to a function that mutates it in some // way, then reassign the result to the object. This pattern is fine. { A a; a = MutateA(std::move(a)); a.foo(); } } // Relative sequencing of reinitialization and use. If the two are unsequenced, // we conservatively assume that the reinitialization happens after the use, // i.e. that the object is not reinitialized at the point in time when it is // used. void sequencingOfReinitAndUse() { // Reinitialization and use in function arguments. Again, check both possible // orderings. { A a; std::move(a); passByValue(a.getInt(), (a = A())); // CHECK-NOTES: [[@LINE-1]]:17: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } { A a; std::move(a); passByValue((a = A()), a.getInt()); // CHECK-NOTES: [[@LINE-1]]:28: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } } // The comma operator sequences its operands. void commaOperatorSequences() { { A a; A(std::move(a)) , (a = A()); a.foo(); } { A a; (a = A()), A(std::move(a)); a.foo(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-3]]:16: note: move occurred here } } namespace InitializerListSequences { struct S1 { int i; A a; }; struct S2 { A a; int i; }; struct S3 { S3() {} S3(int, A) {} S3(A, int) {} }; // An initializer list sequences its initialization clauses. void initializerListSequences() { { A a; S1 s1{a.getInt(), std::move(a)}; } { A a; S1 s1{.i = a.getInt(), .a = std::move(a)}; } { A a; S2 s2{std::move(a), a.getInt()}; // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here } { A a; S2 s2{.a = std::move(a), .i = a.getInt()}; // CHECK-NOTES: [[@LINE-1]]:35: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here } { // Check the case where the constructed type has a constructor and the // initializer list therefore manifests as a `CXXConstructExpr` instead of // an `InitListExpr`. A a; S3 s3{a.getInt(), std::move(a)}; } { A a; S3 s3{std::move(a), a.getInt()}; // CHECK-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here } } } // namespace InitializerListSequences // A declaration statement containing multiple declarations sequences the // initializer expressions. void declarationSequences() { { A a; A a1 = a, a2 = std::move(a); } { A a; A a1 = std::move(a), a2 = a; // CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:12: note: move occurred here } } // The logical operators && and || sequence their operands. void logicalOperatorsSequence() { { A a; if (a.getInt() > 0 && A(std::move(a)).getInt() > 0) { A().foo(); } } // A variation: Negate the result of the && (which pushes the && further down // into the AST). { A a; if (!(a.getInt() > 0 && A(std::move(a)).getInt() > 0)) { A().foo(); } } { A a; if (A(std::move(a)).getInt() > 0 && a.getInt() > 0) { // CHECK-NOTES: [[@LINE-1]]:41: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:9: note: move occurred here A().foo(); } } { A a; if (a.getInt() > 0 || A(std::move(a)).getInt() > 0) { A().foo(); } } { A a; if (A(std::move(a)).getInt() > 0 || a.getInt() > 0) { // CHECK-NOTES: [[@LINE-1]]:41: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-2]]:9: note: move occurred here A().foo(); } } } // A range-based for sequences the loop variable declaration before the body. void forRangeSequences() { A v[2] = {A(), A()}; for (A &a : v) { std::move(a); } } // If a variable is declared in an if, while or switch statement, the init // statement (for if and switch) is sequenced before the variable declaration, // which in turn is sequenced before the evaluation of the condition. We place // all tests inside a for loop to ensure that the checker understands the // sequencing. If it didn't, then the loop would trigger the "moved twice" // logic. void ifWhileAndSwitchSequenceInitDeclAndCondition() { for (int i = 0; i < 10; ++i) { A a1; if (A a2 = std::move(a1)) { std::move(a2); } } for (int i = 0; i < 10; ++i) { A a1; if (A a2 = std::move(a1); a2) { std::move(a2); } } for (int i = 0; i < 10; ++i) { A a1; if (A a2 = std::move(a1); A a3 = std::move(a2)) { std::move(a3); } } for (int i = 0; i < 10; ++i) { // init followed by condition with move, but without variable declaration. if (A a1; A(std::move(a1)).getInt() > 0) {} } for (int i = 0; i < 10; ++i) { if (A a1; A(std::move(a1)).getInt() > a1.getInt()) {} // CHECK-NOTES: [[@LINE-1]]:43: warning: 'a1' used after it was moved // CHECK-NOTES: [[@LINE-2]]:15: note: move occurred here // CHECK-NOTES: [[@LINE-3]]:43: note: the use and move are unsequenced } for (int i = 0; i < 10; ++i) { A a1; if (A a2 = std::move(a1); A(a1) > 0) {} // CHECK-NOTES: [[@LINE-1]]:33: warning: 'a1' used after it was moved // CHECK-NOTES: [[@LINE-2]]:16: note: move occurred here } while (A a = A()) { std::move(a); } for (int i = 0; i < 10; ++i) { A a1; switch (A a2 = std::move(a1); a2) { case true: std::move(a2); } } for (int i = 0; i < 10; ++i) { A a1; switch (A a2 = a1; A a3 = std::move(a2)) { case true: std::move(a3); } } } // Some statements in templates (e.g. null, break and continue statements) may // be shared between the uninstantiated and instantiated versions of the // template and therefore have multiple parents. Make sure the sequencing code // handles this correctly. template void nullStatementSequencesInTemplate() { int c = 0; (void)c; ; std::move(c); } template void nullStatementSequencesInTemplate(); namespace PR33020 { class D { ~D(); }; struct A { D d; }; class B { A a; }; template class C : T, B { void m_fn1() { int a; std::move(a); C c; } }; } // namespace PR33020 namespace UnevalContext { struct Foo {}; void noExcept() { Foo Bar; (void) noexcept(Foo{std::move(Bar)}); Foo Other{std::move(Bar)}; } void sizeOf() { Foo Bar; (void)sizeof(Foo{std::move(Bar)}); Foo Other{std::move(Bar)}; } void alignOf() { Foo Bar; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu-alignof-expression" (void)alignof(Foo{std::move(Bar)}); #pragma clang diagnostic pop Foo Other{std::move(Bar)}; } void typeId() { Foo Bar; // error: you need to include before using the 'typeid' operator // (void) typeid(Foo{std::move(Bar)}).name(); Foo Other{std::move(Bar)}; } } // namespace UnevalContext class CtorInit { public: CtorInit(std::string val) : a{val.empty()}, // fine s{std::move(val)}, b{val.empty()} // CHECK-NOTES: [[@LINE-1]]:11: warning: 'val' used after it was moved // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here {} private: bool a; std::string s; bool b; }; class CtorInitLambda { public: CtorInitLambda(std::string val) : a{val.empty()}, // fine s{std::move(val)}, b{[&] { return val.empty(); }()}, // CHECK-NOTES: [[@LINE-1]]:12: warning: 'val' used after it was moved // CHECK-NOTES: [[@LINE-3]]:9: note: move occurred here c{[] { std::string str{}; std::move(str); return str.empty(); // CHECK-NOTES: [[@LINE-1]]:18: warning: 'str' used after it was moved // CHECK-NOTES: [[@LINE-3]]:11: note: move occurred here }()} { std::move(val); // CHECK-NOTES: [[@LINE-1]]:15: warning: 'val' used after it was moved // CHECK-NOTES: [[@LINE-13]]:9: note: move occurred here std::string val2{}; std::move(val2); val2.empty(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'val2' used after it was moved // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here } private: bool a; std::string s; bool b; bool c; bool d{}; }; class CtorInitOrder { public: CtorInitOrder(std::string val) : a{val.empty()}, // fine b{val.empty()}, // CHECK-NOTES: [[@LINE-1]]:11: warning: 'val' used after it was moved s{std::move(val)} {} // wrong order // CHECK-NOTES: [[@LINE-1]]:9: note: move occurred here // CHECK-NOTES: [[@LINE-4]]:11: note: the use happens in a later loop iteration than the move private: bool a; std::string s; bool b; }; struct Obj {}; struct CtorD { CtorD(Obj b); }; struct CtorC { CtorC(Obj b); }; struct CtorB { CtorB(Obj &b); }; struct CtorA : CtorB, CtorC, CtorD { CtorA(Obj b) : CtorB{b}, CtorC{std::move(b)}, CtorD{b} {} // CHECK-NOTES: [[@LINE-1]]:55: warning: 'b' used after it was moved // CHECK-NOTES: [[@LINE-2]]:34: note: move occurred here }; struct Base { Base(Obj b) : bb{std::move(b)} {} template Base(Call &&c) : bb{c()} {}; Obj bb; }; struct Derived : Base, CtorC { Derived(Obj b) : Base{[&] mutable { return std::move(b); }()}, // False negative: The lambda/std::move was executed, so it should warn // below CtorC{b} {} }; struct Derived2 : Base, CtorC { Derived2(Obj b) : Base{[&] mutable { return std::move(b); }}, // This was a move, but it doesn't warn below, because it can't know if // the lambda/std::move was actually called CtorC{b} {} }; struct Derived3 : Base, CtorC { Derived3(Obj b) : Base{[c = std::move(b)] mutable { return std::move(c); }}, CtorC{b} {} // CHECK-NOTES: [[@LINE-1]]:74: warning: 'b' used after it was moved // CHECK-NOTES: [[@LINE-2]]:19: note: move occurred here }; class PR38187 { public: PR38187(std::string val) : val_(std::move(val)) { val.empty(); // CHECK-NOTES: [[@LINE-1]]:5: warning: 'val' used after it was moved // CHECK-NOTES: [[@LINE-3]]:30: note: move occurred here } private: std::string val_; };