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
1944 lines
50 KiB
C++
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
|
|
}
|