// RUN: %check_clang_tidy %s bugprone-use-after-move %t -- -- -std=c++11 -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; }; #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(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); 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; }; //////////////////////////////////////////////////////////////////////////////// // General tests. // Simple case. void simple() { A a; a.foo(); A other_a = std::move(a); a.foo(); // CHECK-MESSAGES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:15: note: move occurred here } // 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-MESSAGES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:15: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:17: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-2]]:7: note: move occurred here // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:6: warning: 'ptr' used after it was moved // CHECK-MESSAGES: [[@LINE-5]]:5: note: move occurred here } { std::unique_ptr ptr; std::move(ptr); ptr->foo(); // CHECK-MESSAGES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:5: note: move occurred here } { std::unique_ptr ptr; std::move(ptr); ptr[0]; // CHECK-MESSAGES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:5: note: move occurred here } { std::shared_ptr ptr; std::move(ptr); ptr.get(); static_cast(ptr); *ptr; // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'ptr' used after it was moved // CHECK-MESSAGES: [[@LINE-5]]:5: note: move occurred here } { std::shared_ptr ptr; std::move(ptr); ptr->foo(); // CHECK-MESSAGES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved // CHECK-MESSAGES: [[@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(); } // 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-MESSAGES: [[@LINE-1]]:5: warning: 'ptr' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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(); } // Warn if the use consists of a capture that happens after a move. { A a; std::move(a); auto lambda = [a]() { a.foo(); }; // CHECK-MESSAGES: [[@LINE-1]]:20: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:5: note: move occurred here } // ...even if the capture was implicit. { A a; std::move(a); auto lambda = [=]() { a.foo(); }; // CHECK-MESSAGES: [[@LINE-1]]:27: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:21: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:5: note: move occurred here } { A a; std::move(a); auto lambda = [&]() { a.foo(); }; // CHECK-MESSAGES: [[@LINE-1]]:27: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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); } } // 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-MESSAGES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:3: warning: 't' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:3: warning: 'convertible' used after it was moved // CHECK-MESSAGES: [[@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(); } }; //////////////////////////////////////////////////////////////////////////////// // 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-MESSAGES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE+2]]:7: note: move occurred here // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:25: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:5: note: move occurred here } const A a; std::move(a); passByConstReference(a); // CHECK-MESSAGES: [[@LINE-1]]:24: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'container' used after it was // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'container1' used after it was // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'container' used after it was // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'container1' used after it was // CHECK-MESSAGES: [[@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; } } //////////////////////////////////////////////////////////////////////////////// // 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-MESSAGES: [[@LINE-1]]:17: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-2]]:29: note: move occurred here // CHECK-MESSAGES: [[@LINE-3]]:17: note: the use and move are unsequenced } { A a; passByValue(std::move(a), a.getInt()); // CHECK-MESSAGES: [[@LINE-1]]:31: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-2]]:17: note: move occurred here // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:9: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-2]]:27: note: move occurred here // CHECK-MESSAGES: [[@LINE-3]]:9: note: the use and move are unsequenced // CHECK-MESSAGES: [[@LINE-4]]:29: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-5]]:7: note: move occurred here // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:7: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-2]]:21: note: move occurred here // CHECK-MESSAGES: [[@LINE-3]]:7: note: the use and move are unsequenced } { A a; int v[3]; v[intFromA(std::move(a))] = intFromInt(a.i); // CHECK-MESSAGES: [[@LINE-1]]:44: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-2]]:7: note: move occurred here // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:17: note: move occurred here } { A a; passByValue((a = A()), std::move(a)); a.foo(); // CHECK-MESSAGES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:17: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:5: note: move occurred here } { A a; std::move(a); passByValue((a = A()), a.getInt()); // CHECK-MESSAGES: [[@LINE-1]]:28: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:5: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-3]]:16: note: move occurred here } } // An initializer list sequences its initialization clauses. void initializerListSequences() { { struct S1 { int i; A a; }; A a; S1 s1{a.getInt(), std::move(a)}; } { struct S2 { A a; int i; }; A a; S2 s2{std::move(a), a.getInt()}; // CHECK-MESSAGES: [[@LINE-1]]:25: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@LINE-2]]:11: note: move occurred here } } // 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-MESSAGES: [[@LINE-1]]:31: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:41: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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-MESSAGES: [[@LINE-1]]:41: warning: 'a' used after it was moved // CHECK-MESSAGES: [[@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 statement, the declaration of the variable // (which is treated like a reinitialization by the check) is sequenced before // the evaluation of the condition (which constitutes a use). void ifStmtSequencesDeclAndCondition() { for (int i = 0; i < 10; ++i) { if (A a = A()) { std::move(a); } } } 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; } }; }