| // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ | |
| // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ | |
| // RUN: -analyzer-config exploration_strategy=unexplored_first_queue\ | |
| // RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ | |
| // RUN: -verify=expected,peaceful,non-aggressive | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ | |
| // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ | |
| // RUN: -analyzer-config exploration_strategy=dfs -DDFS\ | |
| // RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ | |
| // RUN: -verify=expected,peaceful,non-aggressive | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %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\ | |
| // RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ | |
| // RUN: -verify=expected,non-aggressive | |
| // 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\ | |
| // RUN: -analyzer-config cplusplus.Move:WarnOn=KnownsOnly\ | |
| // RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ | |
| // RUN: -verify=expected,non-aggressive | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %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\ | |
| // RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ | |
| // RUN: -verify=expected,peaceful,aggressive | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ | |
| // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ | |
| // RUN: -analyzer-config exploration_strategy=dfs -DDFS\ | |
| // RUN: -analyzer-config cplusplus.Move:WarnOn=All\ | |
| // RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ | |
| // RUN: -verify=expected,peaceful,aggressive | |
| // 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 | |
| // Tests checker-messages printing. | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ | |
| // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ | |
| // RUN: -analyzer-config exploration_strategy=dfs -DDFS\ | |
| // RUN: -analyzer-config cplusplus.Move:WarnOn=All -DAGGRESSIVE_DFS\ | |
| // RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ | |
| // RUN: -verify=expected,peaceful,aggressive %s 2>&1 | FileCheck %s | |
| #include "Inputs/system-header-simulator-cxx.h" | |
| void clang_analyzer_warnIfReached(); | |
| void clang_analyzer_printState(); | |
| 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)) { // aggressive-note{{Object 'b' is moved}} | |
| } | |
| 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); // peaceful-note {{Object 'a' is moved}} | |
| #ifdef AGGRESSIVE_DFS | |
| clang_analyzer_printState(); | |
| // CHECK: "checker_messages": [ | |
| // CHECK-NEXT: { "checker": "cplusplus.Move", "messages": [ | |
| // CHECK-NEXT: "Moved-from objects :", | |
| // CHECK: "a: moved", | |
| // CHECK: "" | |
| // CHECK-NEXT: ]} | |
| // CHECK-NEXT: ] | |
| #endif | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| { | |
| A a; | |
| A b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| b = a; // peaceful-warning {{Moved-from object 'a' is copied}} | |
| // peaceful-note@-1 {{Moved-from object 'a' is copied}} | |
| } | |
| { | |
| A a; | |
| A b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| b = std::move(a); // peaceful-warning {{Moved-from object 'a' is moved}} | |
| // peaceful-note@-1 {{Moved-from object 'a' is moved}} | |
| } | |
| } | |
| void simpleMoveAssignementTest() { | |
| { | |
| A a; | |
| A b; | |
| b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| { | |
| A a; | |
| A b; | |
| b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| A c(a); // peaceful-warning {{Moved-from object 'a' is copied}} | |
| // peaceful-note@-1 {{Moved-from object 'a' is copied}} | |
| } | |
| { | |
| A a; | |
| A b; | |
| b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| A c(std::move(a)); // peaceful-warning {{Moved-from object 'a' is moved}} | |
| // peaceful-note@-1 {{Moved-from object 'a' is moved}} | |
| } | |
| } | |
| void moveInInitListTest() { | |
| struct S { | |
| A a; | |
| }; | |
| A a; | |
| S s{std::move(a)}; // peaceful-note {{Object 'a' is moved}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| // 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) { // peaceful-note 2 {{Assuming 'i' is not equal to 1}} | |
| // peaceful-note@-1 2 {{Taking false branch}} | |
| A b; | |
| b = std::move(a); | |
| a = A(); | |
| } | |
| if (i == 2) { // peaceful-note 2 {{Assuming 'i' is not equal to 2}} | |
| // peaceful-note@-1 2 {{Taking false branch}} | |
| a.foo(); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| if (i == 1) { // peaceful-note 2 {{'i' is not equal to 1}} | |
| // peaceful-note@-1 2 {{Taking false branch}} | |
| (void)std::move(a); | |
| } | |
| if (i == 2) { // peaceful-note 2 {{'i' is not equal to 2}} | |
| // peaceful-note@-1 2 {{Taking false branch}} | |
| 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); // peaceful-note {{Object 'a' is moved}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| // If a path exist where we not reinitialize the variable we report a bug. | |
| { | |
| A a; | |
| A b; | |
| b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| if (i < 10) { // peaceful-note {{Assuming 'i' is >= 10}} | |
| // peaceful-note@-1 {{Taking false branch}} | |
| a = A(); | |
| } | |
| if (i > 5) { // peaceful-note {{'i' is > 5}} | |
| // peaceful-note@-1 {{Taking true branch}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| } | |
| } | |
| // 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; | |
| // FIXME: Execution doesn't jump to the end of the function yet. | |
| for (int i = 0; i < bignum(); i++) { // peaceful-note {{Loop condition is false. Execution jumps to the end of the function}} | |
| rightRefCall(std::move(a)); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < 2; i++) { // peaceful-note {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-1 {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} | |
| rightRefCall(std::move(a)); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < bignum(); i++) { // peaceful-note {{Loop condition is false. Execution jumps to the end of the function}} | |
| leftRefCall(a); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < 2; i++) { // peaceful-note {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-1 {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} | |
| leftRefCall(a); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < bignum(); i++) { // peaceful-note {{Loop condition is false. Execution jumps to the end of the function}} | |
| constCopyOrMoveCall(a); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < 2; i++) { // peaceful-note {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-1 {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} | |
| constCopyOrMoveCall(a); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < bignum(); i++) { // peaceful-note {{Loop condition is false. Execution jumps to the end of the function}} | |
| moveInsideFunctionCall(a); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < 2; i++) { // peaceful-note {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-1 {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} | |
| moveInsideFunctionCall(a); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < bignum(); i++) { // peaceful-note {{Loop condition is false. Execution jumps to the end of the function}} | |
| copyOrMoveCall(a); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < 2; i++) { // peaceful-note {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-1 {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-2 {{Loop condition is false. Execution jumps to the end of the function}} | |
| copyOrMoveCall(a); // no-warning | |
| } | |
| } | |
| { | |
| A a; | |
| for (int i = 0; i < bignum(); i++) { // peaceful-note {{Loop condition is true. Entering loop body}} | |
| // peaceful-note@-1 {{Loop condition is true. Entering loop body}} | |
| constCopyOrMoveCall(std::move(a)); // peaceful-note {{Object 'a' is moved}} | |
| // peaceful-warning@-1 {{Moved-from object 'a' is moved}} | |
| // peaceful-note@-2 {{Moved-from object 'a' is moved}} | |
| } | |
| } | |
| // 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); // peaceful-note {{Object 'a' is moved}} | |
| if (cond) { // peaceful-note {{Assuming 'cond' is true}} | |
| // peaceful-note@-1 {{Taking true branch}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| if (cond) { | |
| a.bar(); // no-warning | |
| } | |
| a.bar(); // no-warning | |
| } | |
| void uniqueTest2() { | |
| A a; | |
| A a1 = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| 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); // peaceful-note {{Object 'a' is moved}} | |
| a.empty(); // no-warning | |
| a.isEmpty(); // no-warning | |
| (void)a; // no-warning | |
| (bool)a; // expected-warning {{expression result unused}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| 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() { | |
| (void)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); // aggressive-note {{Object 'a' is moved}} | |
| a.foo(); // aggressive-warning {{Method called on moved-from object 'a'}} | |
| // aggressive-note@-1 {{Method called on moved-from object 'a'}} | |
| b = std::move(static_a); // aggressive-note {{Object 'static_a' is moved}} | |
| static_a.foo(); // aggressive-warning {{Method called on moved-from object 'static_a'}} | |
| // aggressive-note@-1 {{Method called on moved-from object 'static_a'}} | |
| } | |
| }; | |
| void PtrAndArrayTest() { | |
| A *Ptr = new A(1, 1.5); | |
| A Arr[10]; | |
| Arr[2] = std::move(*Ptr); // aggressive-note{{Object is moved}} | |
| (*Ptr).foo(); // aggressive-warning{{Method called on moved-from object}} | |
| // aggressive-note@-1{{Method called on moved-from object}} | |
| Ptr = &Arr[1]; | |
| Arr[3] = std::move(Arr[1]); // aggressive-note {{Object is moved}} | |
| Ptr->foo(); // aggressive-warning {{Method called on moved-from object}} | |
| // aggressive-note@-1 {{Method called on moved-from object}} | |
| Arr[3] = std::move(Arr[2]); // aggressive-note{{Object is moved}} | |
| Arr[2].foo(); // aggressive-warning{{Method called on moved-from object}} | |
| // aggressive-note@-1{{Method called on moved-from object}} | |
| 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) { // peaceful-note {{Assuming 'i' is > 0}} | |
| // peaceful-note@-1 {{Taking true branch}} | |
| 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 | |
| // peaceful-note@-1 {{'i' is > 0}} | |
| // peaceful-note@-2 {{'?' condition is true}} | |
| } | |
| // A variation on the theme above. | |
| { | |
| A a; | |
| a.foo() > 0 ? a.foo() : A(std::move(a)).foo(); | |
| // peaceful-note@-1 {{Assuming the condition is true}} | |
| // peaceful-note@-2 {{'?' condition is true}} | |
| } | |
| // Same thing, but with a switch statement. | |
| { | |
| A a, b; | |
| switch (i) { // peaceful-note {{Control jumps to 'case 1:'}} | |
| case 1: | |
| b = std::move(a); // no-warning | |
| // FIXME: Execution doesn't jump to the end of the function yet. | |
| break; // peaceful-note {{Execution jumps to the end of the function}} | |
| case 2: | |
| a.foo(); // no-warning | |
| break; | |
| } | |
| } | |
| // However, if there's a fallthrough, we do warn. | |
| { | |
| A a, b; | |
| switch (i) { // peaceful-note {{Control jumps to 'case 1:'}} | |
| case 1: | |
| b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| case 2: | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| 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 lifeTimeExtendTest() { | |
| A&& a = A{}; | |
| A b = std::move(a); // peaceful-note {{Object is moved}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object}} | |
| // peaceful-note@-1 {{Method called on moved-from object}} | |
| } | |
| void interFunTest1(A &a) { | |
| a.bar(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| void interFunTest2() { | |
| A a; | |
| A b; | |
| b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| interFunTest1(a); // peaceful-note {{Calling 'interFunTest1'}} | |
| } | |
| void foobar(A a, int i); | |
| void foobar(int i, A a); | |
| void paramEvaluateOrderTest() { | |
| A a; | |
| foobar(std::move(a), a.getI()); // peaceful-note {{Object 'a' is moved}} | |
| // peaceful-warning@-1 {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-2 {{Method called on moved-from object 'a'}} | |
| //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); // peaceful-note{{Object 'a' is moved}} | |
| not_known_pass_by_const_ref(a); | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| { | |
| 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); // peaceful-note {{Object 'a' is moved}} | |
| not_known_pass_by_const_ptr(&a); | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| } | |
| // 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; // peaceful-note {{Object 'a' is moved}} | |
| // peaceful-warning@-1 {{Moved-from object 'a' is copied}} | |
| // peaceful-note@-2 {{Moved-from object 'a' is copied}} | |
| } | |
| } | |
| // The logical operators && and || sequence their operands. | |
| void logicalOperatorsSequenceTest() { | |
| { | |
| A a; | |
| if (a.foo() > 0 && A(std::move(a)).foo() > 0) { // peaceful-note {{Assuming the condition is false}} | |
| // peaceful-note@-1 {{Left side of '&&' is false}} | |
| // peaceful-note@-2 {{Taking false branch}} | |
| // And the other report: | |
| // peaceful-note@-4 {{Assuming the condition is false}} | |
| // peaceful-note@-5 {{Left side of '&&' is false}} | |
| // peaceful-note@-6 {{Taking false branch}} | |
| A().bar(); | |
| } | |
| } | |
| // 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)) { // peaceful-note {{Assuming the condition is false}} | |
| // peaceful-note@-1 {{Left side of '&&' is false}} | |
| // peaceful-note@-2 {{Taking true branch}} | |
| // And the other report: | |
| // peaceful-note@-4 {{Assuming the condition is false}} | |
| // peaceful-note@-5 {{Left side of '&&' is false}} | |
| // peaceful-note@-6 {{Taking true branch}} | |
| A().bar(); | |
| } | |
| } | |
| { | |
| A a; | |
| if (A(std::move(a)).foo() > 0 && a.foo() > 0) { // peaceful-note {{Object 'a' is moved}} | |
| // peaceful-note@-1 {{Assuming the condition is true}} | |
| // peaceful-note@-2 {{Left side of '&&' is true}} | |
| // peaceful-warning@-3 {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-4 {{Method called on moved-from object 'a'}} | |
| // And the other report: | |
| // peaceful-note@-6 {{Assuming the condition is false}} | |
| // peaceful-note@-7 {{Left side of '&&' is false}} | |
| // peaceful-note@-8 {{Taking false branch}} | |
| A().bar(); | |
| } | |
| } | |
| { | |
| A a; | |
| if (a.foo() > 0 || A(std::move(a)).foo() > 0) { // peaceful-note {{Assuming the condition is true}} | |
| // peaceful-note@-1 {{Left side of '||' is true}} | |
| // peaceful-note@-2 {{Taking true branch}} | |
| A().bar(); | |
| } | |
| } | |
| { | |
| A a; | |
| if (A(std::move(a)).foo() > 0 || a.foo() > 0) { // peaceful-note {{Object 'a' is moved}} | |
| // peaceful-note@-1 {{Assuming the condition is false}} | |
| // peaceful-note@-2 {{Left side of '||' is false}} | |
| // peaceful-warning@-3 {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-4 {{Method called on moved-from object 'a'}} | |
| 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); // aggressive-note {{Object 'b' is moved}} | |
| a.b.foo(); // aggressive-warning {{Method called on moved-from object 'b'}} | |
| // aggressive-note@-1 {{Method called on moved-from object 'b'}} | |
| } | |
| { | |
| A a; | |
| A a1 = std::move(a); // aggressive-note {{Calling move constructor for 'A'}} | |
| // aggressive-note@-1 {{Returning from move constructor for 'A'}} | |
| a.b.foo(); // aggressive-warning{{Method called on moved-from object 'b'}} | |
| // aggressive-note@-1{{Method called on moved-from object 'b'}} | |
| } | |
| // Don't report a misuse if any SuperRegion is already reported. | |
| { | |
| A a; | |
| A a1 = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| a.b.foo(); // no-warning | |
| } | |
| { | |
| C c; | |
| C c1 = std::move(c); // peaceful-note {{Object 'c' is moved}} | |
| c.foo(); // peaceful-warning {{Method called on moved-from object 'c'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'c'}} | |
| 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); // peaceful-note {{Object 'c' is moved}} | |
| c.foo(); // peaceful-warning {{Method called on moved-from object 'c'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'c'}} | |
| 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 | |
| } | |
| } | |
| void checkExplicitDestructorCalls() { | |
| // The below code segments invoke the destructor twice (explicit and | |
| // implicit). While this is not a desired code behavior, it is | |
| // not the use-after-move checker's responsibility to issue such a warning. | |
| { | |
| B* b = new B; | |
| B a = std::move(*b); | |
| b->~B(); // no-warning | |
| delete b; | |
| } | |
| { | |
| B a, b; | |
| new (&a) B(reinterpret_cast<B &&>(b)); | |
| (&b)->~B(); // no-warning | |
| } | |
| { | |
| B b; | |
| B a = std::move(b); | |
| b.~B(); // 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); // aggressive-note {{Object 'P' is moved}} | |
| // non-aggressive-note@-1 {{Smart pointer 'P' of type 'std::unique_ptr' is reset to null when moved from}} | |
| P.get(); // aggressive-warning{{Method called on moved-from object 'P'}} | |
| // aggressive-note@-1{{Method called on moved-from object 'P'}} | |
| // 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; // non-aggressive-warning{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}} | |
| // non-aggressive-note@-1{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}} | |
| // The program should have crashed by now. | |
| clang_analyzer_warnIfReached(); // no-warning | |
| } | |
| }; | |
| void localRValueMove(A &&a) { | |
| A b = std::move(a); // peaceful-note {{Object 'a' is moved}} | |
| a.foo(); // peaceful-warning {{Method called on moved-from object 'a'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'a'}} | |
| } | |
| 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); // peaceful-note {{Object 'P' is moved}} | |
| P.get(); // peaceful-warning {{Method called on moved-from object 'P'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'P'}} | |
| } | |
| 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'}} | |
| } | |
| void getAfterMove(std::unique_ptr<A> P) { | |
| std::unique_ptr<A> Q = std::move(P); // peaceful-note {{Object 'P' is moved}} | |
| // TODO: Explain why (bool)P is false. | |
| if (P) // peaceful-note{{Taking false branch}} | |
| clang_analyzer_warnIfReached(); // no-warning | |
| A *a = P.get(); // peaceful-warning {{Method called on moved-from object 'P'}} | |
| // peaceful-note@-1 {{Method called on moved-from object 'P'}} | |
| // TODO: Warn on a null dereference here. | |
| a->foo(); | |
| } | |
| struct OtherMoveSafeClasses { | |
| std::packaged_task<int(void)> Task; | |
| void test() { | |
| // Test the suppression caused by use-after-move semantics of | |
| // std::package_task being different from other standard classes. | |
| // Only warn in aggressive mode. Don't say that the object | |
| // is left in unspecified state after move. | |
| std::packaged_task<int(void)> Task2 = std::move(Task); | |
| // aggressive-note@-1 {{Object 'Task' is moved}} | |
| std::packaged_task<int(void)> Task3 = std::move(Task); | |
| // aggressive-warning@-1{{Moved-from object 'Task' is moved}} | |
| // aggressive-note@-2 {{Moved-from object 'Task' is moved}} | |
| } | |
| }; |