Files
TheRock/compiler/amd-llvm/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1944 lines
50 KiB
C++

// RUN: %check_clang_tidy -std=c++11,c++14 -check-suffixes=,CXX11 %s bugprone-use-after-move %t -- \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-use-after-move.InvalidationFunctions: "::Database<>::StaticCloseConnection;Database<>::CloseConnection;FriendCloseConnection;FreeCloseConnection", \
// RUN: bugprone-use-after-move.ReinitializationFunctions: "::Database<>::Reset;::Database<>::StaticReset;::FriendReset;::RegularReset" \
// RUN: }}' -- \
// RUN: -fno-delayed-template-parsing
// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t -- \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-use-after-move.InvalidationFunctions: "::Database<>::StaticCloseConnection;Database<>::CloseConnection;FriendCloseConnection;FreeCloseConnection", \
// RUN: bugprone-use-after-move.ReinitializationFunctions: "::Database<>::Reset;::Database<>::StaticReset;::FriendReset;::RegularReset" \
// RUN: }}' -- \
// RUN: -fno-delayed-template-parsing
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <vector>
typedef decltype(nullptr) nullptr_t;
namespace std {
template <typename T>
struct optional {
optional();
T& operator*();
const T& operator*() const;
void reset();
};
struct any {
any();
void reset();
};
} // namespace std
class A {
public:
A();
A(const A &);
A(A &&);
A &operator=(const A &);
A &operator=(A &&);
void foo() const;
void bar(int i) 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
}
// Don't flag a move-to-self.
void selfMove() {
A a;
a = std::move(a);
a.foo();
}
void * operator new(size_t, void *p);
// Don't flag an explicit destructor call
void explicitDestructor() {
alignas(A) char storage[sizeof(A)];
A& a = *new (storage) A();
std::move(a);
a.~A(); // It's always valid to destruct a moved object.
using B = AnnotatedContainer<int>;
alignas(B) char other_storage[sizeof(B)];
B& a_p = *new (other_storage) B();
std::move(a_p);
a_p.~B(); // Same as above, but with a template class.
A& b = *new (storage) A();
std::move(b);
(b).~A(); // Parenthesis should not change the behavior.
b.foo(); // But destruction is not a reinitialization.
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'b' used after it was moved
// CHECK-NOTES: [[@LINE-4]]:3: note: move occurred here
A& c = *new (storage) A();
std::move(c);
c.foo();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'c' used after it was moved
// CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here
c.~A();
}
// 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::optional<A> opt;
std::move(opt);
A val = *opt;
(void)val;
// CHECK-NOTES: [[@LINE-2]]:14: warning: 'opt' used after it was moved
// CHECK-NOTES: [[@LINE-4]]: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();
}
// 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 <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);
}
}
// Same as above, but the use and the move are in different CFG blocks.
{
A a;
for (int i = 0; i < 10; ++i) {
if (i < 10)
a.foo();
// CHECK-NOTES: [[@LINE-1]]:9: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE+3]]:9: note: move occurred here
// CHECK-NOTES: [[@LINE-3]]:9: note: the use happens in a later loop
if (i < 10)
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, 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, 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 owning types using reset() is treated as a
// re-initialization. (We don't test std::weak_ptr<> because it can't be
// dereferenced directly.)
void resetIsReinit() {
{
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;
}
{
std::optional<A> opt;
std::move(opt);
opt.reset();
std::optional<A> opt2 = opt;
(void)opt2;
}
{
std::any a;
std::move(a);
a.reset();
std::any a2 = a;
(void)a2;
}
}
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 for annotations on smart-pointer-like types
namespace null_after_move {
template <typename T>
class [[clang::annotate("clang-tidy",
"bugprone-use-after-move",
"null_after_move")]] SmartPtrAlike {
public:
SmartPtrAlike();
~SmartPtrAlike();
SmartPtrAlike(const SmartPtrAlike&) = delete;
SmartPtrAlike &operator=(const SmartPtrAlike&) = delete;
SmartPtrAlike(SmartPtrAlike&& other);
SmartPtrAlike &operator=(SmartPtrAlike&& other);
T* get() const;
T& operator*() const;
};
// Don't flag uses of smart-pointer-like types correctly annotated, unless it's
// a dereference.
void smartPointerLikeTypeUseAfterMove() {
SmartPtrAlike<int> ptr;
ptr.get();
SmartPtrAlike<int> other_ptr = std::move(ptr);
ptr.get();
int inv_value = *ptr;
// CHECK-NOTES: [[@LINE-1]]:20: warning: 'ptr' used after it was moved
// CHECK-NOTES: [[@LINE-4]]:34: note: move occurred here
}
class [[clang::annotate("other"),
clang::annotate("clang-tidy",
"bugprone-use-after-move",
"null_after_move")]] MultipleAnnotationsType {
public:
MultipleAnnotationsType();
~MultipleAnnotationsType();
MultipleAnnotationsType(const MultipleAnnotationsType&) = delete;
MultipleAnnotationsType &operator=(const MultipleAnnotationsType&) = delete;
MultipleAnnotationsType(MultipleAnnotationsType&& other);
MultipleAnnotationsType &operator=(MultipleAnnotationsType&& other);
int* get() const;
int& operator*() const;
};
// Handle smart-pointer-like types correctly annotated, even in the case of
// multiple annotations.
void typeWithMultipleAnnotations() {
MultipleAnnotationsType ptr;
ptr.get();
MultipleAnnotationsType other_ptr = std::move(ptr);
ptr.get();
int inv_value = *ptr;
// CHECK-NOTES: [[@LINE-1]]:20: warning: 'ptr' used after it was moved
// CHECK-NOTES: [[@LINE-4]]:39: note: move occurred here
}
class [[clang::annotate("null_after_move")]] BadAnnotation {
public:
BadAnnotation();
~BadAnnotation();
BadAnnotation(const BadAnnotation&) = delete;
BadAnnotation &operator=(const BadAnnotation&) = delete;
BadAnnotation(BadAnnotation&& other);
BadAnnotation &operator=(BadAnnotation&& other);
int* get() const;
};
// Flag uses of smart-pointer-like types with incorrect annotate.
void badUseAfterMoveAnnotationIgnored() {
BadAnnotation ptr;
BadAnnotation other_ptr = std::move(ptr);
ptr.get();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'ptr' used after it was moved
// CHECK-NOTES: [[@LINE-3]]:29: note: move occurred here
}
class [[clang::annotate("clang-tidy", "null_after_move")]] BadAnnotationArgs {
public:
BadAnnotationArgs();
~BadAnnotationArgs();
BadAnnotationArgs(const BadAnnotationArgs&) = delete;
BadAnnotationArgs &operator=(const BadAnnotationArgs&) = delete;
BadAnnotationArgs(BadAnnotationArgs&& other);
BadAnnotationArgs &operator=(BadAnnotationArgs&& other);
int* get() const;
};
// Flag uses of smart-pointer-like types with wrong annotate arguments.
void UseAfterMoveAnnotationWithBadArgsIgnored() {
BadAnnotationArgs ptr;
BadAnnotationArgs other_ptr = std::move(ptr);
ptr.get();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'ptr' used after it was moved
// CHECK-NOTES: [[@LINE-3]]:33: note: move occurred here
}
} // namespace null_after_move
////////////////////////////////////////////////////////////////////////////////
// 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);
}
}
}
// In a function call, the expression that determines the callee is sequenced
// before the arguments -- but only in C++17 and later.
namespace CalleeSequencedBeforeArguments {
int consumeA(std::unique_ptr<A> a);
int consumeA(A &&a);
void calleeSequencedBeforeArguments() {
{
std::unique_ptr<A> a;
a->bar(consumeA(std::move(a)));
// CHECK-NOTES-CXX11: [[@LINE-1]]:5: warning: 'a' used after it was moved
// CHECK-NOTES-CXX11: [[@LINE-2]]:21: note: move occurred here
// CHECK-NOTES-CXX11: [[@LINE-3]]:5: note: the use and move are unsequenced
}
{
std::unique_ptr<A> a;
std::unique_ptr<A> getArg(std::unique_ptr<A> a);
getArg(std::move(a))->bar(a->getInt());
// CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE-2]]:12: note: move occurred here
// CHECK-NOTES-CXX11: [[@LINE-3]]:31: note: the use and move are unsequenced
}
{
A a;
// Nominally, the callee `a.bar` is evaluated before the argument
// `consumeA(std::move(a))`, but in effect `a` is only accessed after the
// call to `A::bar()` happens, i.e. after the argument has been evaluted.
a.bar(consumeA(std::move(a)));
// CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here
}
}
} // namespace CalleeSequencedBeforeArguments
// 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 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
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 <typename Call> 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_;
};
namespace issue82023
{
struct S {
S();
S(S&&);
};
void consume(S s);
template <typename T>
void forward(T&& t) {
consume(std::forward<T>(t));
consume(std::forward<T>(t));
// CHECK-NOTES: [[@LINE-1]]:27: warning: 't' used after it was forwarded
// CHECK-NOTES: [[@LINE-3]]:11: note: forward occurred here
}
void create() {
S s;
forward(std::move(s));
}
} // namespace issue82023
namespace custom_invalidation
{
template<class T = int>
struct Database {
template<class...>
void CloseConnection(T = T()) {}
template<class...>
static void StaticCloseConnection(Database&, T = T()) {}
template<class...>
friend void FriendCloseConnection(Database&, T = T()) {}
void Query();
};
void FreeCloseConnection(Database<int>&) {}
void Run() {
using DB = Database<>;
DB db1;
db1.CloseConnection();
db1.Query();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db1' used after it was invalidated by 'CloseConnection<>'
// CHECK-NOTES: [[@LINE-3]]:7: note: invalidation occurred here
DB db2;
DB::StaticCloseConnection(db2);
db2.Query();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db2' used after it was invalidated by 'StaticCloseConnection<>'
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
DB db3;
DB().StaticCloseConnection(db3);
db3.Query();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db3' used after it was invalidated by 'StaticCloseConnection<>'
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
DB db4;
FriendCloseConnection(db4);
db4.Query();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db4' used after it was invalidated by 'FriendCloseConnection<>'
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
DB db5;
FriendCloseConnection(db5, /*disconnect timeout*/ 5);
db5.Query();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db5' used after it was invalidated by 'FriendCloseConnection<>'
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
DB db6;
FreeCloseConnection(db6);
db6.Query();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db6' used after it was invalidated by 'FreeCloseConnection'
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
}
} // namespace custom_invalidation
namespace custom_reinitialization {
template <class T = int>
struct Database {
template <class... Args>
void Reset(T = T(), Args &&...) {}
template <class... Args>
static void StaticReset(Database &, T = T(), Args &&...) {}
template <class... Args>
friend void FriendReset(Database &, T = T(), Args &&...) {}
void Query(T = T()) {}
};
template <class T = int>
void RegularReset(Database<T> &d, T = T()) {}
void Run() {
using DB = Database<>;
DB db1;
std::move(db1);
db1.Reset();
db1.Query();
DB db2;
std::move(db2);
db2.Query();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db2' used after it was moved
// CHECK-NOTES: [[@LINE-3]]:3: note: move occurred here
db2.Reset();
DB db3;
std::move(db3);
DB::StaticReset(db3);
db3.Query();
DB db4;
std::move(db4);
FriendReset(db4);
db4.Query();
DB db5;
std::move(db5);
db5.Reset(0, 1.5, "extra");
db5.Query();
DB db6;
std::move(db6);
RegularReset(db6);
db6.Query();
}
} // namespace custom_reinitialization
namespace GH62206 {
struct Base {
};
struct Derived: public Base {
using Base::operator=;
};
void foo() {
Base b;
Derived d;
std::move(d);
d = b; // Should not warn
}
void paren_version() {
Base b;
Derived d;
std::move(d);
(d) = b; // Should not warn
}
} // namespace GH62206
std::pair<std::string, std::string> makeStringPair(std::string a,
std::string b);
void stdTieIsReinit() {
std::string a, b;
std::move(a);
std::move(b);
std::tie(a, b) = makeStringPair("x", "y");
a.size();
b.size();
}
void stdTieWithIgnore() {
std::string a;
std::move(a);
std::tie(a, std::ignore) = makeStringPair("x", "y");
a.size();
}
void stdTieIgnoreFlipped() {
std::string a;
std::move(a);
std::tie(std::ignore, a) = makeStringPair("x", "y");
a.size();
}
void stdTieThreeVars() {
std::string a, b, c;
std::move(a);
std::move(b);
std::move(c);
std::tie(a, b, c) =
std::make_tuple(std::string("x"), std::string("y"), std::string("z"));
a.size();
b.size();
c.size();
}
void stdTiePartialReinit() {
std::string a, b, c;
std::move(a);
std::move(b);
std::move(c);
std::tie(b) = std::make_tuple(std::string("y"));
b.size();
std::tie(c, b, std::ignore) =
std::make_tuple(std::string("x"), std::string("y"), std::string("z"));
b.size();
c.size();
a.size();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE-11]]:3: note: move occurred here
}
void stdTieInLoop() {
std::string a, b;
while (true) {
std::tie(a, b) = makeStringPair(std::move(a), std::move(b));
std::tie(a, b) = makeStringPair(std::move(a), std::move(b));
}
}
template <typename T>
void stdTieReinitInTemplate() {
T a, b;
std::move(a);
std::move(b);
std::tie(a, b) = std::make_tuple(T(), T());
a.size();
b.size();
}
template <typename T>
void stdTiePartialReinitInTemplate() {
T a, b;
std::move(a);
std::tie(b) = std::make_tuple(T());
b.size();
a.size();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE-5]]:3: note: move occurred here
}
void callStdTieTemplateTests() {
stdTieReinitInTemplate<std::string>();
stdTiePartialReinitInTemplate<std::string>();
}
void stdTieParenthesized() {
// Parenthesized std::tie on the LHS still reinitializes captured variables.
std::string a, b;
std::move(a);
std::move(b);
(std::tie(a, b)) = makeStringPair("x", "y"); // no-warning: both reinitialized
a.size();
b.size();
}
void stdTieParenthesizedWithIgnore() {
// Parenthesized std::tie with std::ignore still reinitializes named variables.
std::string a;
std::move(a);
(std::tie(a, std::ignore)) = makeStringPair("x", "y"); // no-warning: a reinitialized
a.size();
}
void stdTieParenthesizedIgnoreFlipped() {
// std::ignore in first position inside parenthesized std::tie.
std::string a;
std::move(a);
(std::tie(std::ignore, a)) = makeStringPair("x", "y"); // no-warning: a reinitialized
a.size();
}
void stdTieParenthesizedPartialReinit() {
// Parenthesized std::tie only reinitializes variables named in the call.
std::string a, b;
std::move(a);
(std::tie(b)) = std::make_tuple(std::string("y")); // reinitializes b, not a
b.size(); // no-warning: b was reinitialized
a.size();
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE-5]]:3: note: move occurred here
}