Asserting on invalid input isn't very nice, hence the patch to emit an error instead. This is the first of many patches to overhaul the way we handle checker options. Differential Revision: https://reviews.llvm.org/D57850 llvm-svn: 355704
1159 lines
30 KiB
C++
1159 lines
30 KiB
C++
// RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move -verify %s\
|
|
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
|
|
// RUN: -analyzer-config exploration_strategy=unexplored_first_queue\
|
|
// RUN: -analyzer-checker debug.ExprInspection
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move -verify %s\
|
|
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
|
|
// RUN: -analyzer-config exploration_strategy=dfs -DDFS=1\
|
|
// RUN: -analyzer-checker debug.ExprInspection
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move -verify %s\
|
|
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
|
|
// RUN: -analyzer-config exploration_strategy=unexplored_first_queue\
|
|
// RUN: -analyzer-config cplusplus.Move:WarnOn=KnownsOnly -DPEACEFUL\
|
|
// RUN: -analyzer-checker debug.ExprInspection
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move -verify %s\
|
|
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
|
|
// RUN: -analyzer-config exploration_strategy=dfs -DDFS=1\
|
|
// RUN: -analyzer-config cplusplus.Move:WarnOn=KnownsOnly -DPEACEFUL\
|
|
// RUN: -analyzer-checker debug.ExprInspection
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move -verify %s\
|
|
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
|
|
// RUN: -analyzer-config exploration_strategy=unexplored_first_queue\
|
|
// RUN: -analyzer-config cplusplus.Move:WarnOn=All -DAGGRESSIVE\
|
|
// RUN: -analyzer-checker debug.ExprInspection
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move -verify %s\
|
|
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
|
|
// RUN: -analyzer-config exploration_strategy=dfs -DDFS=1\
|
|
// RUN: -analyzer-config cplusplus.Move:WarnOn=All -DAGGRESSIVE\
|
|
// RUN: -analyzer-checker debug.ExprInspection
|
|
|
|
// RUN: not %clang_analyze_cc1 -verify %s \
|
|
// RUN: -analyzer-checker=core \
|
|
// RUN: -analyzer-checker=cplusplus.Move \
|
|
// RUN: -analyzer-config cplusplus.Move:WarnOn="a bunch of things" \
|
|
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-MOVE-INVALID-VALUE
|
|
|
|
// CHECK-MOVE-INVALID-VALUE: (frontend): invalid input for checker option
|
|
// CHECK-MOVE-INVALID-VALUE-SAME: 'cplusplus.Move:WarnOn', that expects either
|
|
// CHECK-MOVE-INVALID-VALUE-SAME: "KnownsOnly", "KnownsAndLocals" or "All"
|
|
// CHECK-MOVE-INVALID-VALUE-SAME: string value
|
|
|
|
#include "Inputs/system-header-simulator-cxx.h"
|
|
|
|
void clang_analyzer_warnIfReached();
|
|
|
|
class B {
|
|
public:
|
|
B() = default;
|
|
B(const B &) = default;
|
|
B(B &&) = default;
|
|
B& operator=(const B &q) = default;
|
|
void operator=(B &&b) {
|
|
return;
|
|
}
|
|
void foo() { return; }
|
|
};
|
|
|
|
class A {
|
|
int i;
|
|
double d;
|
|
|
|
public:
|
|
B b;
|
|
A(int ii = 42, double dd = 1.0) : d(dd), i(ii), b(B()) {}
|
|
void moveconstruct(A &&other) {
|
|
std::swap(b, other.b);
|
|
std::swap(d, other.d);
|
|
std::swap(i, other.i);
|
|
return;
|
|
}
|
|
static A get() {
|
|
A v(12, 13);
|
|
return v;
|
|
}
|
|
A(A *a) {
|
|
moveconstruct(std::move(*a));
|
|
}
|
|
A(const A &other) : i(other.i), d(other.d), b(other.b) {}
|
|
A(A &&other) : i(other.i), d(other.d), b(std::move(other.b)) {
|
|
#ifdef AGGRESSIVE
|
|
// expected-note@-2{{Object 'b' is moved}}
|
|
#endif
|
|
}
|
|
A(A &&other, char *k) {
|
|
moveconstruct(std::move(other));
|
|
}
|
|
void operator=(const A &other) {
|
|
i = other.i;
|
|
d = other.d;
|
|
b = other.b;
|
|
return;
|
|
}
|
|
void operator=(A &&other) {
|
|
moveconstruct(std::move(other));
|
|
return;
|
|
}
|
|
int getI() { return i; }
|
|
int foo() const;
|
|
void bar() const;
|
|
void reset();
|
|
void destroy();
|
|
void clear();
|
|
void resize(std::size_t);
|
|
void assign(const A &);
|
|
bool empty() const;
|
|
bool isEmpty() const;
|
|
operator bool() const;
|
|
|
|
void testUpdateField() {
|
|
A a;
|
|
A b = std::move(a);
|
|
a.i = 1;
|
|
a.foo(); // no-warning
|
|
}
|
|
void testUpdateFieldDouble() {
|
|
A a;
|
|
A b = std::move(a);
|
|
a.d = 1.0;
|
|
a.foo(); // no-warning
|
|
}
|
|
};
|
|
|
|
int bignum();
|
|
|
|
void moveInsideFunctionCall(A a) {
|
|
A b = std::move(a);
|
|
}
|
|
void leftRefCall(A &a) {
|
|
a.foo();
|
|
}
|
|
void rightRefCall(A &&a) {
|
|
a.foo();
|
|
}
|
|
void constCopyOrMoveCall(const A a) {
|
|
a.foo();
|
|
}
|
|
|
|
void copyOrMoveCall(A a) {
|
|
a.foo();
|
|
}
|
|
|
|
void simpleMoveCtorTest() {
|
|
{
|
|
A a;
|
|
A b = std::move(a);
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
{
|
|
A a;
|
|
A b = std::move(a);
|
|
b = a;
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Moved-from object 'a' is copied}}
|
|
// expected-note@-4 {{Moved-from object 'a' is copied}}
|
|
#endif
|
|
}
|
|
{
|
|
A a;
|
|
A b = std::move(a);
|
|
b = std::move(a);
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Moved-from object 'a' is moved}}
|
|
// expected-note@-4 {{Moved-from object 'a' is moved}}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void simpleMoveAssignementTest() {
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
A c(a);
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Moved-from object 'a' is copied}}
|
|
// expected-note@-4 {{Moved-from object 'a' is copied}}
|
|
#endif
|
|
}
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
A c(std::move(a));
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Moved-from object 'a' is moved}}
|
|
// expected-note@-4 {{Moved-from object 'a' is moved}}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void moveInInitListTest() {
|
|
struct S {
|
|
A a;
|
|
};
|
|
A a;
|
|
S s{std::move(a)};
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
|
|
// Don't report a bug if the variable was assigned to in the meantime.
|
|
void reinitializationTest(int i) {
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
a = A();
|
|
a.foo();
|
|
}
|
|
{
|
|
A a;
|
|
if (i == 1) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Assuming 'i' is not equal to 1}}
|
|
// expected-note@-3 {{Taking false branch}}
|
|
// And the other report:
|
|
// expected-note@-5 {{Assuming 'i' is not equal to 1}}
|
|
// expected-note@-6 {{Taking false branch}}
|
|
#endif
|
|
A b;
|
|
b = std::move(a);
|
|
a = A();
|
|
}
|
|
if (i == 2) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Assuming 'i' is not equal to 2}}
|
|
// expected-note@-3 {{Taking false branch}}
|
|
// And the other report:
|
|
// expected-note@-5 {{Assuming 'i' is not equal to 2}}
|
|
// expected-note@-6 {{Taking false branch}}
|
|
#endif
|
|
a.foo(); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
if (i == 1) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Taking false branch}}
|
|
// expected-note@-3 {{Taking false branch}}
|
|
#endif
|
|
std::move(a);
|
|
}
|
|
if (i == 2) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Taking false branch}}
|
|
// expected-note@-3 {{Taking false branch}}
|
|
#endif
|
|
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;
|
|
std::swap(a1, a2);
|
|
}
|
|
// A std::move() after the assignment makes the variable invalid again.
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
a = A();
|
|
b = std::move(a);
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
// If a path exist where we not reinitialize the variable we report a bug.
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Object 'a' is moved}}
|
|
#endif
|
|
if (i < 10) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Assuming 'i' is >= 10}}
|
|
// expected-note@-3 {{Taking false branch}}
|
|
#endif
|
|
a = A();
|
|
}
|
|
if (i > 5) {
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Taking true branch}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// Using decltype on an expression is not a use.
|
|
void decltypeIsNotUseTest() {
|
|
A a;
|
|
// A b(std::move(a));
|
|
decltype(a) other_a; // no-warning
|
|
}
|
|
|
|
void loopTest() {
|
|
{
|
|
A a;
|
|
for (int i = 0; i < bignum(); i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
rightRefCall(std::move(a)); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < 2; i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-3 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-4 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
rightRefCall(std::move(a)); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < bignum(); i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
leftRefCall(a); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < 2; i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-3 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-4 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
leftRefCall(a); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < bignum(); i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
constCopyOrMoveCall(a); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < 2; i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-3 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-4 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
constCopyOrMoveCall(a); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < bignum(); i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
moveInsideFunctionCall(a); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < 2; i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-3 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-4 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
moveInsideFunctionCall(a); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < bignum(); i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
copyOrMoveCall(a); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < 2; i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-3 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-4 {{Loop condition is false. Execution jumps to the end of the function}}
|
|
#endif
|
|
copyOrMoveCall(a); // no-warning
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
for (int i = 0; i < bignum(); i++) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Loop condition is true. Entering loop body}}
|
|
// expected-note@-3 {{Loop condition is true. Entering loop body}}
|
|
#endif
|
|
constCopyOrMoveCall(std::move(a));
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Moved-from object 'a' is moved}}
|
|
// expected-note@-4 {{Moved-from object 'a' is moved}}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Don't warn if we return after the move.
|
|
{
|
|
A a;
|
|
for (int i = 0; i < 3; ++i) {
|
|
a.bar();
|
|
if (a.foo() > 0) {
|
|
A b;
|
|
b = std::move(a); // no-warning
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Report a usage of a moved-from object only at the first use.
|
|
void uniqueTest(bool cond) {
|
|
A a(42, 42.0);
|
|
A b;
|
|
b = std::move(a);
|
|
|
|
if (cond) {
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-5 {{Object 'a' is moved}}
|
|
// expected-note@-4 {{Assuming 'cond' is not equal to 0}}
|
|
// expected-note@-5 {{Taking true branch}}
|
|
// expected-warning@-5 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-6 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
if (cond) {
|
|
a.bar(); // no-warning
|
|
}
|
|
|
|
a.bar(); // no-warning
|
|
}
|
|
|
|
void uniqueTest2() {
|
|
A a;
|
|
A a1 = std::move(a);
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
|
|
A a2 = std::move(a); // no-warning
|
|
a.foo(); // no-warning
|
|
}
|
|
|
|
// There are exceptions where we assume in general that the method works fine
|
|
//even on moved-from objects.
|
|
void moveSafeFunctionsTest() {
|
|
A a;
|
|
A b = std::move(a);
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Object 'a' is moved}}
|
|
#endif
|
|
a.empty(); // no-warning
|
|
a.isEmpty(); // no-warning
|
|
(void)a; // no-warning
|
|
(bool)a; // expected-warning {{expression result unused}}
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-warning@-2 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-3 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
|
|
void moveStateResetFunctionsTest() {
|
|
{
|
|
A a;
|
|
A b = std::move(a);
|
|
a.reset(); // no-warning
|
|
a.foo(); // no-warning
|
|
// Test if resets the state of subregions as well.
|
|
a.b.foo(); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
A b = std::move(a);
|
|
a.destroy(); // no-warning
|
|
a.foo(); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
A b = std::move(a);
|
|
a.clear(); // no-warning
|
|
a.foo(); // no-warning
|
|
a.b.foo(); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
A b = std::move(a);
|
|
a.resize(0); // no-warning
|
|
a.foo(); // no-warning
|
|
a.b.foo(); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
A b = std::move(a);
|
|
a.assign(A()); // no-warning
|
|
a.foo(); // no-warning
|
|
a.b.foo(); // no-warning
|
|
}
|
|
}
|
|
|
|
// 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 templateArgIsNotUseTest() {
|
|
{
|
|
// 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)); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
functionTemplate<sizeof(A(std::move(a)))>(std::move(a)); // no-warning
|
|
}
|
|
}
|
|
|
|
// Moves of global variables are not reported.
|
|
A global_a;
|
|
void globalVariablesTest() {
|
|
std::move(global_a);
|
|
global_a.foo(); // no-warning
|
|
}
|
|
|
|
// Moves of member variables.
|
|
class memberVariablesTest {
|
|
A a;
|
|
static A static_a;
|
|
|
|
void f() {
|
|
A b;
|
|
b = std::move(a);
|
|
a.foo();
|
|
#ifdef AGGRESSIVE
|
|
// expected-note@-3{{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4{{Method called on moved-from object 'a'}}
|
|
#endif
|
|
|
|
b = std::move(static_a);
|
|
static_a.foo();
|
|
#ifdef AGGRESSIVE
|
|
// expected-note@-3{{Object 'static_a' is moved}}
|
|
// expected-warning@-3{{Method called on moved-from object 'static_a'}}
|
|
// expected-note@-4{{Method called on moved-from object 'static_a'}}
|
|
#endif
|
|
}
|
|
};
|
|
|
|
void PtrAndArrayTest() {
|
|
A *Ptr = new A(1, 1.5);
|
|
A Arr[10];
|
|
Arr[2] = std::move(*Ptr);
|
|
(*Ptr).foo();
|
|
#ifdef AGGRESSIVE
|
|
// expected-note@-3{{Object is moved}}
|
|
// expected-warning@-3{{Method called on moved-from object}}
|
|
// expected-note@-4{{Method called on moved-from object}}
|
|
#endif
|
|
|
|
Ptr = &Arr[1];
|
|
Arr[3] = std::move(Arr[1]);
|
|
Ptr->foo();
|
|
#ifdef AGGRESSIVE
|
|
// expected-note@-3{{Object is moved}}
|
|
// expected-warning@-3{{Method called on moved-from object}}
|
|
// expected-note@-4{{Method called on moved-from object}}
|
|
#endif
|
|
|
|
Arr[3] = std::move(Arr[2]);
|
|
Arr[2].foo();
|
|
#ifdef AGGRESSIVE
|
|
// expected-note@-3{{Object is moved}}
|
|
// expected-warning@-3{{Method called on moved-from object}}
|
|
// expected-note@-4{{Method called on moved-from object}}
|
|
#endif
|
|
|
|
Arr[2] = std::move(Arr[3]); // reinitialization
|
|
Arr[2].foo(); // no-warning
|
|
}
|
|
|
|
void exclusiveConditionsTest(bool cond) {
|
|
A a;
|
|
if (cond) {
|
|
A b;
|
|
b = std::move(a);
|
|
}
|
|
if (!cond) {
|
|
a.bar(); // no-warning
|
|
}
|
|
}
|
|
|
|
void differentBranchesTest(int i) {
|
|
// Don't warn if the use is in a different branch from the move.
|
|
{
|
|
A a;
|
|
if (i > 0) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Assuming 'i' is > 0}}
|
|
// expected-note@-3 {{Taking true branch}}
|
|
#endif
|
|
A b;
|
|
b = std::move(a);
|
|
} else {
|
|
a.foo(); // no-warning
|
|
}
|
|
}
|
|
// Same thing, but with a ternary operator.
|
|
{
|
|
A a, b;
|
|
i > 0 ? (void)(b = std::move(a)) : a.bar(); // no-warning
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{'?' condition is true}}
|
|
#endif
|
|
}
|
|
// A variation on the theme above.
|
|
{
|
|
A a;
|
|
a.foo() > 0 ? a.foo() : A(std::move(a)).foo();
|
|
#ifdef DFS
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Assuming the condition is false}}
|
|
// expected-note@-4 {{'?' condition is false}}
|
|
#endif
|
|
#else
|
|
#ifndef PEACEFUL
|
|
// expected-note@-8 {{Assuming the condition is true}}
|
|
// expected-note@-9 {{'?' condition is true}}
|
|
#endif
|
|
#endif
|
|
}
|
|
// Same thing, but with a switch statement.
|
|
{
|
|
A a, b;
|
|
switch (i) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Control jumps to 'case 1:'}}
|
|
#endif
|
|
case 1:
|
|
b = std::move(a); // no-warning
|
|
break;
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Execution jumps to the end of the function}}
|
|
#endif
|
|
case 2:
|
|
a.foo(); // no-warning
|
|
break;
|
|
}
|
|
}
|
|
// However, if there's a fallthrough, we do warn.
|
|
{
|
|
A a, b;
|
|
switch (i) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Control jumps to 'case 1:'}}
|
|
#endif
|
|
case 1:
|
|
b = std::move(a);
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Object 'a' is moved}}
|
|
#endif
|
|
case 2:
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-warning@-2 {{Method called on moved-from object}}
|
|
// expected-note@-3 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void tempTest() {
|
|
A a = A::get();
|
|
A::get().foo(); // no-warning
|
|
for (int i = 0; i < bignum(); i++) {
|
|
A::get().foo(); // no-warning
|
|
}
|
|
}
|
|
|
|
void interFunTest1(A &a) {
|
|
a.bar();
|
|
#ifndef PEACEFUL
|
|
// expected-warning@-2 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-3 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
|
|
void interFunTest2() {
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
interFunTest1(a);
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-note@-3 {{Calling 'interFunTest1'}}
|
|
#endif
|
|
}
|
|
|
|
void foobar(A a, int i);
|
|
void foobar(int i, A a);
|
|
|
|
void paramEvaluateOrderTest() {
|
|
A a;
|
|
foobar(std::move(a), a.getI());
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
|
|
//FALSE NEGATIVE since parameters evaluate order is undefined
|
|
foobar(a.getI(), std::move(a)); //no-warning
|
|
}
|
|
|
|
void not_known_pass_by_ref(A &a);
|
|
void not_known_pass_by_const_ref(const A &a);
|
|
void not_known_pass_by_rvalue_ref(A &&a);
|
|
void not_known_pass_by_ptr(A *a);
|
|
void not_known_pass_by_const_ptr(const A *a);
|
|
|
|
void regionAndPointerEscapeTest() {
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
not_known_pass_by_ref(a);
|
|
a.foo(); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
not_known_pass_by_const_ref(a);
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-4{{Object 'a' is moved}}
|
|
// expected-warning@-3{{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
not_known_pass_by_rvalue_ref(std::move(a));
|
|
a.foo(); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
not_known_pass_by_ptr(&a);
|
|
a.foo(); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
A b;
|
|
b = std::move(a);
|
|
not_known_pass_by_const_ptr(&a);
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-4{{Object 'a' is moved}}
|
|
// expected-warning@-3{{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// A declaration statement containing multiple declarations sequences the
|
|
// initializer expressions.
|
|
void declarationSequenceTest() {
|
|
{
|
|
A a;
|
|
A a1 = a, a2 = std::move(a); // no-warning
|
|
}
|
|
{
|
|
A a;
|
|
A a1 = std::move(a), a2 = a;
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Moved-from object 'a' is copied}}
|
|
// expected-note@-4 {{Moved-from object 'a' is copied}}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// The logical operators && and || sequence their operands.
|
|
void logicalOperatorsSequenceTest() {
|
|
{
|
|
A a;
|
|
if (a.foo() > 0 && A(std::move(a)).foo() > 0) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Assuming the condition is false}}
|
|
// expected-note@-3 {{Left side of '&&' is false}}
|
|
// expected-note@-4 {{Taking false branch}}
|
|
// And the other report:
|
|
// expected-note@-6 {{Assuming the condition is false}}
|
|
// expected-note@-7 {{Left side of '&&' is false}}
|
|
// expected-note@-8 {{Taking false branch}}
|
|
A().bar();
|
|
#endif
|
|
}
|
|
}
|
|
// A variation: Negate the result of the && (which pushes the && further down
|
|
// into the AST).
|
|
{
|
|
A a;
|
|
if (!(a.foo() > 0 && A(std::move(a)).foo() > 0)) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Assuming the condition is false}}
|
|
// expected-note@-3 {{Left side of '&&' is false}}
|
|
// expected-note@-4 {{Taking true branch}}
|
|
// And the other report:
|
|
// expected-note@-6 {{Assuming the condition is false}}
|
|
// expected-note@-7 {{Left side of '&&' is false}}
|
|
// expected-note@-8 {{Taking true branch}}
|
|
#endif
|
|
A().bar();
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
if (A(std::move(a)).foo() > 0 && a.foo() > 0) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Object 'a' is moved}}
|
|
// expected-note@-3 {{Assuming the condition is true}}
|
|
// expected-note@-4 {{Left side of '&&' is true}}
|
|
// expected-warning@-5 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-6 {{Method called on moved-from object 'a'}}
|
|
// And the other report:
|
|
// expected-note@-8 {{Assuming the condition is false}}
|
|
// expected-note@-9 {{Left side of '&&' is false}}
|
|
// expected-note@-10{{Taking false branch}}
|
|
#endif
|
|
A().bar();
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
if (a.foo() > 0 || A(std::move(a)).foo() > 0) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Assuming the condition is true}}
|
|
// expected-note@-3 {{Left side of '||' is true}}
|
|
// expected-note@-4 {{Taking true branch}}
|
|
#endif
|
|
A().bar();
|
|
}
|
|
}
|
|
{
|
|
A a;
|
|
if (A(std::move(a)).foo() > 0 || a.foo() > 0) {
|
|
#ifndef PEACEFUL
|
|
// expected-note@-2 {{Object 'a' is moved}}
|
|
// expected-note@-3 {{Assuming the condition is false}}
|
|
// expected-note@-4 {{Left side of '||' is false}}
|
|
// expected-warning@-5 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-6 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
A().bar();
|
|
}
|
|
}
|
|
}
|
|
|
|
// A range-based for sequences the loop variable declaration before the body.
|
|
void forRangeSequencesTest() {
|
|
A v[2] = {A(), A()};
|
|
for (A &a : v) {
|
|
A b;
|
|
b = std::move(a); // no-warning
|
|
}
|
|
}
|
|
|
|
// If a variable is declared in an if statement, the declaration of the variable
|
|
// (which is treated like a reinitialization by the check) is sequenced before
|
|
// the evaluation of the condition (which constitutes a use).
|
|
void ifStmtSequencesDeclAndConditionTest() {
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (A a = A()) {
|
|
A b;
|
|
b = std::move(a); // no-warning
|
|
}
|
|
}
|
|
}
|
|
|
|
struct C : public A {
|
|
[[clang::reinitializes]] void reinit();
|
|
};
|
|
|
|
void subRegionMoveTest() {
|
|
{
|
|
A a;
|
|
B b = std::move(a.b);
|
|
a.b.foo();
|
|
#ifdef AGGRESSIVE
|
|
// expected-note@-3{{Object 'b' is moved}}
|
|
// expected-warning@-3{{Method called on moved-from object 'b'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'b'}}
|
|
#endif
|
|
}
|
|
{
|
|
A a;
|
|
A a1 = std::move(a);
|
|
a.b.foo();
|
|
#ifdef AGGRESSIVE
|
|
// expected-note@-3{{Calling move constructor for 'A'}}
|
|
// expected-note@-4{{Returning from move constructor for 'A'}}
|
|
// expected-warning@-4{{Method called on moved-from object 'b'}}
|
|
// expected-note@-5{{Method called on moved-from object 'b'}}
|
|
#endif
|
|
}
|
|
// Don't report a misuse if any SuperRegion is already reported.
|
|
{
|
|
A a;
|
|
A a1 = std::move(a);
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
a.b.foo(); // no-warning
|
|
}
|
|
{
|
|
C c;
|
|
C c1 = std::move(c);
|
|
c.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'c' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'c'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'c'}}
|
|
#endif
|
|
c.b.foo(); // no-warning
|
|
}
|
|
}
|
|
|
|
void resetSuperClass() {
|
|
C c;
|
|
C c1 = std::move(c);
|
|
c.clear();
|
|
C c2 = c; // no-warning
|
|
}
|
|
|
|
void resetSuperClass2() {
|
|
C c;
|
|
C c1 = std::move(c);
|
|
c.reinit();
|
|
C c2 = c; // no-warning
|
|
}
|
|
|
|
void reportSuperClass() {
|
|
C c;
|
|
C c1 = std::move(c);
|
|
c.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'c' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'c'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'c'}}
|
|
#endif
|
|
C c2 = c; // no-warning
|
|
}
|
|
|
|
struct Empty {};
|
|
|
|
Empty inlinedCall() {
|
|
// Used to warn because region 'e' failed to be cleaned up because no symbols
|
|
// have ever died during the analysis and the checkDeadSymbols callback
|
|
// was skipped entirely.
|
|
Empty e{};
|
|
return e; // no-warning
|
|
}
|
|
|
|
void checkInlinedCallZombies() {
|
|
while (true)
|
|
inlinedCall();
|
|
}
|
|
|
|
void checkLoopZombies() {
|
|
while (true) {
|
|
Empty e{};
|
|
Empty f = std::move(e); // no-warning
|
|
}
|
|
}
|
|
|
|
void checkMoreLoopZombies1(bool flag) {
|
|
while (flag) {
|
|
Empty e{};
|
|
if (true)
|
|
e; // expected-warning {{expression result unused}}
|
|
Empty f = std::move(e); // no-warning
|
|
}
|
|
}
|
|
|
|
bool coin();
|
|
|
|
void checkMoreLoopZombies2(bool flag) {
|
|
while (flag) {
|
|
Empty e{};
|
|
while (coin())
|
|
e; // expected-warning {{expression result unused}}
|
|
Empty f = std::move(e); // no-warning
|
|
}
|
|
}
|
|
|
|
void checkMoreLoopZombies3(bool flag) {
|
|
while (flag) {
|
|
Empty e{};
|
|
do
|
|
e; // expected-warning {{expression result unused}}
|
|
while (coin());
|
|
Empty f = std::move(e); // no-warning
|
|
}
|
|
}
|
|
|
|
void checkMoreLoopZombies4(bool flag) {
|
|
while (flag) {
|
|
Empty e{};
|
|
for (; coin();)
|
|
e; // expected-warning {{expression result unused}}
|
|
Empty f = std::move(e); // no-warning
|
|
}
|
|
}
|
|
|
|
struct MoveOnlyWithDestructor {
|
|
MoveOnlyWithDestructor();
|
|
~MoveOnlyWithDestructor();
|
|
MoveOnlyWithDestructor(const MoveOnlyWithDestructor &m) = delete;
|
|
MoveOnlyWithDestructor(MoveOnlyWithDestructor &&m);
|
|
};
|
|
|
|
MoveOnlyWithDestructor foo() {
|
|
MoveOnlyWithDestructor m;
|
|
return m;
|
|
}
|
|
|
|
class HasSTLField {
|
|
std::vector<int> V;
|
|
void testVector() {
|
|
// Warn even in non-aggressive mode when it comes to STL, because
|
|
// in STL the object is left in "valid but unspecified state" after move.
|
|
std::vector<int> W = std::move(V); // expected-note{{Object 'V' of type 'std::vector' is left in a valid but unspecified state after move}}
|
|
V.push_back(123); // expected-warning{{Method called on moved-from object 'V'}}
|
|
// expected-note@-1{{Method called on moved-from object 'V'}}
|
|
}
|
|
|
|
std::unique_ptr<int> P;
|
|
void testUniquePtr() {
|
|
// unique_ptr remains in a well-defined state after move.
|
|
std::unique_ptr<int> Q = std::move(P);
|
|
P.get();
|
|
#ifdef AGGRESSIVE
|
|
// expected-warning@-2{{Method called on moved-from object 'P'}}
|
|
// expected-note@-4{{Object 'P' is moved}}
|
|
// expected-note@-4{{Method called on moved-from object 'P'}}
|
|
#endif
|
|
|
|
// Because that well-defined state is null, dereference is still UB.
|
|
// Note that in aggressive mode we already warned about 'P',
|
|
// so no extra warning is generated.
|
|
*P += 1;
|
|
#ifndef AGGRESSIVE
|
|
// expected-warning@-2{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}}
|
|
// expected-note@-14{{Smart pointer 'P' of type 'std::unique_ptr' is reset to null when moved from}}
|
|
// expected-note@-4{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}}
|
|
#endif
|
|
|
|
// The program should have crashed by now.
|
|
clang_analyzer_warnIfReached(); // no-warning
|
|
}
|
|
};
|
|
|
|
void localRValueMove(A &&a) {
|
|
A b = std::move(a);
|
|
a.foo();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'a' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'a'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'a'}}
|
|
#endif
|
|
}
|
|
|
|
void localUniquePtr(std::unique_ptr<int> P) {
|
|
// Even though unique_ptr is safe to use after move,
|
|
// reusing a local variable this way usually indicates a bug.
|
|
std::unique_ptr<int> Q = std::move(P);
|
|
P.get();
|
|
#ifndef PEACEFUL
|
|
// expected-note@-3 {{Object 'P' is moved}}
|
|
// expected-warning@-3 {{Method called on moved-from object 'P'}}
|
|
// expected-note@-4 {{Method called on moved-from object 'P'}}
|
|
#endif
|
|
}
|
|
|
|
void localUniquePtrWithArrow(std::unique_ptr<A> P) {
|
|
std::unique_ptr<A> Q = std::move(P); // expected-note{{Smart pointer 'P' of type 'std::unique_ptr' is reset to null when moved from}}
|
|
P->foo(); // expected-warning{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}}
|
|
// expected-note@-1{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}}
|
|
}
|