| // 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 <typename T> |
| 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 <typename T> |
| struct shared_ptr { |
| shared_ptr(); |
| T *get() const; |
| explicit operator bool() const; |
| void reset(T *ptr); |
| T &operator*() const; |
| T *operator->() const; |
| }; |
| |
| template <typename T> |
| struct weak_ptr { |
| weak_ptr(); |
| bool expired() const; |
| }; |
| |
| #define DECLARE_STANDARD_CONTAINER(name) \ |
| template <typename T> \ |
| struct name { \ |
| name(); \ |
| void clear(); \ |
| bool empty(); \ |
| } |
| |
| #define DECLARE_STANDARD_CONTAINER_WITH_ASSIGN(name) \ |
| template <typename T> \ |
| 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<char> string; |
| |
| template <typename> |
| struct remove_reference; |
| |
| template <typename _Tp> |
| struct remove_reference { |
| typedef _Tp type; |
| }; |
| |
| template <typename _Tp> |
| struct remove_reference<_Tp &> { |
| typedef _Tp type; |
| }; |
| |
| template <typename _Tp> |
| struct remove_reference<_Tp &&> { |
| typedef _Tp type; |
| }; |
| |
| template <typename _Tp> |
| constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) noexcept { |
| return static_cast<typename remove_reference<_Tp>::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<A> ptr; |
| std::move(ptr); |
| ptr.get(); |
| static_cast<bool>(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<A> 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<A> 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<A> ptr; |
| std::move(ptr); |
| ptr.get(); |
| static_cast<bool>(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<A> 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<A> 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<A> PtrToA; |
| PtrToA ptr; |
| std::move(ptr); |
| ptr.get(); |
| } |
| { |
| typedef std::shared_ptr<A> 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<B::AnotherNameForA> 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 <class T> |
| 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 <class T> |
| 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<A>(); |
| |
| // 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 <int> |
| class ClassTemplate { |
| public: |
| void foo(A a); |
| }; |
| template <int> |
| 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<sizeof(A(std::move(a)))>().foo(std::move(a)); |
| } |
| { |
| A a; |
| functionTemplate<sizeof(A(std::move(a)))>(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 <class T> |
| 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<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| |
| auto container2 = container; |
| std::move(container2); |
| container2.clear(); |
| container2.empty(); |
| } |
| { |
| std::deque<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::forward_list<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::list<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::set<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::map<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::multiset<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::multimap<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::unordered_set<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::unordered_map<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::unordered_multiset<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| { |
| std::unordered_multimap<int> container; |
| std::move(container); |
| container.clear(); |
| container.empty(); |
| } |
| // This should also work for typedefs of standard containers. |
| { |
| typedef std::vector<int> 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<int> 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<int> container; |
| std::move(container); |
| container.assign(0, 0); |
| container.empty(); |
| } |
| { |
| std::deque<int> container; |
| std::move(container); |
| container.assign(0, 0); |
| container.empty(); |
| } |
| { |
| std::forward_list<int> container; |
| std::move(container); |
| container.assign(0, 0); |
| container.empty(); |
| } |
| { |
| std::list<int> 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<int> 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<A> ptr; |
| std::move(ptr); |
| ptr.reset(new A); |
| *ptr; |
| } |
| { |
| std::shared_ptr<A> 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 <typename T> |
| class C : T, B { |
| void m_fn1() { |
| int a; |
| std::move(a); |
| C c; |
| } |
| }; |
| } |