llvm-project/clang-tools-extra/test/clang-tidy/checkers/bugprone-use-after-move.cpp

1353 lines
34 KiB
C++

// 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 <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;
};
template <typename T1, typename T2>
struct pair {};
template <typename Key, typename T>
struct map {
struct iterator {};
map();
void clear();
bool empty();
template <class... Args>
pair<iterator, bool> try_emplace(const Key &key, Args &&...args);
};
template <typename Key, typename T>
struct unordered_map {
struct iterator {};
unordered_map();
void clear();
bool empty();
template <class... Args>
pair<iterator, bool> try_emplace(const Key &key, Args &&...args);
};
#define DECLARE_STANDARD_CONTAINER(name) \
template <typename T> \
struct name { \
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(multiset);
DECLARE_STANDARD_CONTAINER(multimap);
DECLARE_STANDARD_CONTAINER(unordered_set);
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;
};
template <class T>
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
}
// 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<A> ptr;
std::move(ptr);
ptr.get();
static_cast<bool>(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<A> 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<A> 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<A> ptr;
std::move(ptr);
ptr.get();
static_cast<bool>(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<A> 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<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();
}
// Make sure we treat references to smart pointers correctly.
{
std::unique_ptr<A> ptr;
std::unique_ptr<A>& ref_to_ptr = ptr;
std::move(ref_to_ptr);
ref_to_ptr.get();
}
{
std::unique_ptr<A> ptr;
std::unique_ptr<A>&& 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();
}
// 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);
}
}
// 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-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 <class T>
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<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-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 <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();
}
};
// Ignore moves that happen in a try_emplace.
void ignoreMoveInTryEmplace() {
{
std::map<int, A> amap;
A a;
amap.try_emplace(1, std::move(a));
a.foo();
}
{
std::unordered_map<int, A> amap;
A a;
amap.try_emplace(1, std::move(a));
a.foo();
}
}
////////////////////////////////////////////////////////////////////////////////
// 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 <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-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<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, 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, 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-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<int> 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<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-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<int> 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<A> ptr;
std::move(ptr);
ptr.reset(new A);
*ptr;
}
{
std::shared_ptr<A> ptr;
std::move(ptr);
ptr.reset(new A);
*ptr;
}
}
void reinitAnnotation() {
{
AnnotatedContainer<int> 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<int> 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<int> 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
}
}
// 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-NOTES: [[@LINE-1]]:25: warning: 'a' used after it was moved
// CHECK-NOTES: [[@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-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 <class> void nullStatementSequencesInTemplate() {
int c = 0;
(void)c;
;
std::move(c);
}
template void nullStatementSequencesInTemplate<int>();
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;
}
};
} // 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 <typeinfo> before using the 'typeid' operator
// (void) typeid(Foo{std::move(Bar)}).name();
Foo Other{std::move(Bar)};
}
} // namespace UnevalContext
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_;
};