| // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 \ | |
| // RUN: -analyzer-config eagerly-assume=false -verify %s | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 \ | |
| // RUN: -analyzer-config eagerly-assume=false -verify %s | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 \ | |
| // RUN: -analyzer-config elide-constructors=false -DNO_ELIDE_FLAG \ | |
| // RUN: -analyzer-config eagerly-assume=false -verify=expected,no-elide %s | |
| // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 \ | |
| // RUN: -analyzer-config elide-constructors=false \ | |
| // RUN: -analyzer-config eagerly-assume=false -verify %s | |
| // Copy elision always occurs in C++17, otherwise it's under | |
| // an on-by-default flag. | |
| #if __cplusplus >= 201703L | |
| #define ELIDE 1 | |
| #else | |
| #ifndef NO_ELIDE_FLAG | |
| #define ELIDE 1 | |
| #endif | |
| #endif | |
| void clang_analyzer_eval(bool); | |
| void clang_analyzer_dump(int); | |
| namespace variable_functional_cast_crash { | |
| struct A { | |
| A(int) {} | |
| }; | |
| void foo() { | |
| A a = A(0); | |
| } | |
| struct B { | |
| A a; | |
| B(): a(A(0)) {} | |
| }; | |
| } // namespace variable_functional_cast_crash | |
| namespace ctor_initializer { | |
| struct S { | |
| int x, y, z; | |
| }; | |
| struct T { | |
| S s; | |
| int w; | |
| T(int w): s(), w(w) {} | |
| }; | |
| class C { | |
| T t; | |
| public: | |
| C() : t(T(4)) { | |
| S s = {1, 2, 3}; | |
| t.s = s; | |
| // FIXME: Should be TRUE regardless of copy elision. | |
| clang_analyzer_eval(t.w == 4); | |
| #ifdef ELIDE | |
| // expected-warning@-2{{TRUE}} | |
| #else | |
| // expected-warning@-4{{UNKNOWN}} | |
| #endif | |
| } | |
| }; | |
| struct A { | |
| int x; | |
| A(): x(0) {} | |
| ~A() {} | |
| }; | |
| struct B { | |
| A a; | |
| B() : a(A()) {} | |
| }; | |
| void foo() { | |
| B b; | |
| clang_analyzer_eval(b.a.x == 0); // expected-warning{{TRUE}} | |
| } | |
| } // namespace ctor_initializer | |
| namespace elision_on_ternary_op_branches { | |
| class C1 { | |
| int x; | |
| public: | |
| C1(int x): x(x) {} | |
| int getX() const { return x; } | |
| ~C1(); | |
| }; | |
| class C2 { | |
| int x; | |
| int y; | |
| public: | |
| C2(int x, int y): x(x), y(y) {} | |
| int getX() const { return x; } | |
| int getY() const { return y; } | |
| ~C2(); | |
| }; | |
| void foo(int coin) { | |
| C1 c1 = coin ? C1(1) : C1(2); | |
| if (coin) { | |
| clang_analyzer_eval(c1.getX() == 1); // expected-warning{{TRUE}} | |
| } else { | |
| clang_analyzer_eval(c1.getX() == 2); // expected-warning{{TRUE}} | |
| } | |
| C2 c2 = coin ? C2(3, 4) : C2(5, 6); | |
| if (coin) { | |
| clang_analyzer_eval(c2.getX() == 3); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(c2.getY() == 4); // expected-warning{{TRUE}} | |
| } else { | |
| clang_analyzer_eval(c2.getX() == 5); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(c2.getY() == 6); // expected-warning{{TRUE}} | |
| } | |
| } | |
| } // namespace elision_on_ternary_op_branches | |
| namespace address_vector_tests { | |
| template <typename T> struct AddressVector { | |
| T *buf[20]; | |
| int len; | |
| AddressVector() : len(0) {} | |
| void push(T *t) { | |
| buf[len] = t; | |
| ++len; | |
| } | |
| }; | |
| class ClassWithoutDestructor { | |
| AddressVector<ClassWithoutDestructor> &v; | |
| public: | |
| ClassWithoutDestructor(AddressVector<ClassWithoutDestructor> &v) : v(v) { | |
| push(); | |
| } | |
| ClassWithoutDestructor(ClassWithoutDestructor &&c) : v(c.v) { push(); } | |
| ClassWithoutDestructor(const ClassWithoutDestructor &c) : v(c.v) { push(); } | |
| void push() { v.push(this); } | |
| }; | |
| // Two warnings on no-elide: arg v holds the address of the temporary, and we | |
| // are returning an object which holds v which holds the address of the temporary | |
| ClassWithoutDestructor make1(AddressVector<ClassWithoutDestructor> &v) { | |
| return ClassWithoutDestructor(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}} | |
| // no-elide-warning@-1 {{Address of stack memory associated with temporary \ | |
| object of type 'ClassWithoutDestructor' is still \ | |
| referred to by the caller variable 'v' upon returning to the caller}} | |
| } | |
| // Two warnings on no-elide: arg v holds the address of the temporary, and we | |
| // are returning an object which holds v which holds the address of the temporary | |
| ClassWithoutDestructor make2(AddressVector<ClassWithoutDestructor> &v) { | |
| return make1(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}} | |
| // no-elide-warning@-1 {{Address of stack memory associated with temporary \ | |
| object of type 'ClassWithoutDestructor' is still \ | |
| referred to by the caller variable 'v' upon returning to the caller}} | |
| } | |
| // Two warnings on no-elide: arg v holds the address of the temporary, and we | |
| // are returning an object which holds v which holds the address of the temporary | |
| ClassWithoutDestructor make3(AddressVector<ClassWithoutDestructor> &v) { | |
| return make2(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}} | |
| // no-elide-warning@-1 {{Address of stack memory associated with temporary \ | |
| object of type 'ClassWithoutDestructor' is still \ | |
| referred to by the caller variable 'v' upon returning to the caller}} | |
| } | |
| void testMultipleReturns() { | |
| AddressVector<ClassWithoutDestructor> v; | |
| ClassWithoutDestructor c = make3(v); | |
| #if ELIDE | |
| clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == &c); // expected-warning{{TRUE}} | |
| #else | |
| clang_analyzer_eval(v.len == 5); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] != v.buf[1]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[1] != v.buf[2]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[2] != v.buf[3]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[3] != v.buf[4]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[4] == &c); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| void consume(ClassWithoutDestructor c) { | |
| c.push(); | |
| // expected-warning@-1 {{Address of stack memory associated with local \ | |
| variable 'c' is still referred to by the caller variable 'v' upon returning \ | |
| to the caller}} | |
| } | |
| void testArgumentConstructorWithoutDestructor() { | |
| AddressVector<ClassWithoutDestructor> v; | |
| consume(make3(v)); | |
| #if ELIDE | |
| clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}} | |
| #else | |
| clang_analyzer_eval(v.len == 6); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] != v.buf[1]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[1] != v.buf[2]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[2] != v.buf[3]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[3] != v.buf[4]); // expected-warning{{TRUE}} | |
| // We forced a push() in consume(), let's see if the address here matches | |
| // the address during construction. | |
| clang_analyzer_eval(v.buf[4] == v.buf[5]); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| class ClassWithDestructor { | |
| AddressVector<ClassWithDestructor> &v; | |
| public: | |
| ClassWithDestructor(AddressVector<ClassWithDestructor> &v) : v(v) { | |
| push(); | |
| } | |
| ClassWithDestructor(ClassWithDestructor &&c) : v(c.v) { push(); } | |
| ClassWithDestructor(const ClassWithDestructor &c) : v(c.v) { push(); } | |
| ~ClassWithDestructor() { push(); } | |
| void push() { v.push(this); } | |
| }; | |
| void testVariable() { | |
| AddressVector<ClassWithDestructor> v; | |
| { | |
| ClassWithDestructor c = ClassWithDestructor(v); | |
| // Check if the last destructor is an automatic destructor. | |
| // A temporary destructor would have fired by now. | |
| #if ELIDE | |
| clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}} | |
| #else | |
| clang_analyzer_eval(v.len == 3); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| #if ELIDE | |
| // 0. Construct the variable. | |
| // 1. Destroy the variable. | |
| clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}} | |
| #else | |
| // 0. Construct the temporary. | |
| // 1. Construct the variable. | |
| // 2. Destroy the temporary. | |
| // 3. Destroy the variable. | |
| clang_analyzer_eval(v.len == 4); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[2]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[1] == v.buf[3]); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| struct TestCtorInitializer { | |
| ClassWithDestructor c; | |
| TestCtorInitializer(AddressVector<ClassWithDestructor> &refParam) | |
| : c(ClassWithDestructor(refParam)) {} | |
| }; | |
| void testCtorInitializer() { | |
| AddressVector<ClassWithDestructor> v; | |
| { | |
| TestCtorInitializer t(v); | |
| // no-elide-warning@-1 {{Address of stack memory associated with temporary \ | |
| object of type 'ClassWithDestructor' is still referred \ | |
| to by the caller variable 'v' upon returning to the caller}} | |
| // Check if the last destructor is an automatic destructor. | |
| // A temporary destructor would have fired by now. | |
| #if ELIDE | |
| clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}} | |
| #else | |
| clang_analyzer_eval(v.len == 3); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| #if ELIDE | |
| // 0. Construct the member variable. | |
| // 1. Destroy the member variable. | |
| clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}} | |
| #else | |
| // 0. Construct the temporary. | |
| // 1. Construct the member variable. | |
| // 2. Destroy the temporary. | |
| // 3. Destroy the member variable. | |
| clang_analyzer_eval(v.len == 4); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[2]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[1] == v.buf[3]); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| // Two warnings on no-elide: arg v holds the address of the temporary, and we | |
| // are returning an object which holds v which holds the address of the temporary | |
| ClassWithDestructor make1(AddressVector<ClassWithDestructor> &v) { | |
| return ClassWithDestructor(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}} | |
| // no-elide-warning@-1 {{Address of stack memory associated with temporary \ | |
| object of type 'ClassWithDestructor' is still referred \ | |
| to by the caller variable 'v' upon returning to the caller}} | |
| } | |
| // Two warnings on no-elide: arg v holds the address of the temporary, and we | |
| // are returning an object which holds v which holds the address of the temporary | |
| ClassWithDestructor make2(AddressVector<ClassWithDestructor> &v) { | |
| return make1(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}} | |
| // no-elide-warning@-1 {{Address of stack memory associated with temporary \ | |
| object of type 'ClassWithDestructor' is still referred \ | |
| to by the caller variable 'v' upon returning to the caller}} | |
| } | |
| // Two warnings on no-elide: arg v holds the address of the temporary, and we | |
| // are returning an object which holds v which holds the address of the temporary | |
| ClassWithDestructor make3(AddressVector<ClassWithDestructor> &v) { | |
| return make2(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}} | |
| // no-elide-warning@-1 {{Address of stack memory associated with temporary \ | |
| object of type 'ClassWithDestructor' is still referred \ | |
| to by the caller variable 'v' upon returning to the caller}} | |
| } | |
| void testMultipleReturnsWithDestructors() { | |
| AddressVector<ClassWithDestructor> v; | |
| { | |
| ClassWithDestructor c = make3(v); | |
| // Check if the last destructor is an automatic destructor. | |
| // A temporary destructor would have fired by now. | |
| #if ELIDE | |
| clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}} | |
| #else | |
| clang_analyzer_eval(v.len == 9); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| #if ELIDE | |
| // 0. Construct the variable. Yes, constructor in make1() constructs | |
| // the variable 'c'. | |
| // 1. Destroy the variable. | |
| clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}} | |
| #else | |
| // 0. Construct the temporary in make1(). | |
| // 1. Construct the temporary in make2(). | |
| // 2. Destroy the temporary in make1(). | |
| // 3. Construct the temporary in make3(). | |
| // 4. Destroy the temporary in make2(). | |
| // 5. Construct the temporary here. | |
| // 6. Destroy the temporary in make3(). | |
| // 7. Construct the variable. | |
| // 8. Destroy the temporary here. | |
| // 9. Destroy the variable. | |
| clang_analyzer_eval(v.len == 10); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[2]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[1] == v.buf[4]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[3] == v.buf[6]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[5] == v.buf[8]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[7] == v.buf[9]); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| void consume(ClassWithDestructor c) { | |
| c.push(); | |
| // expected-warning@-1 {{Address of stack memory associated with local \ | |
| variable 'c' is still referred to by the caller variable 'v' upon returning \ | |
| to the caller}} | |
| } | |
| void testArgumentConstructorWithDestructor() { | |
| AddressVector<ClassWithDestructor> v; | |
| consume(make3(v)); | |
| #if ELIDE | |
| // 0. Construct the argument. | |
| // 1. Forced push() in consume(). | |
| // 2. Destroy the argument. | |
| clang_analyzer_eval(v.len == 3); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[1] == v.buf[2]); // expected-warning{{TRUE}} | |
| #else | |
| // 0. Construct the temporary in make1(). | |
| // 1. Construct the temporary in make2(). | |
| // 2. Destroy the temporary in make1(). | |
| // 3. Construct the temporary in make3(). | |
| // 4. Destroy the temporary in make2(). | |
| // 5. Construct the temporary here. | |
| // 6. Destroy the temporary in make3(). | |
| // 7. Construct the argument. | |
| // 8. Forced push() in consume(). | |
| // 9. Destroy the argument. Notice the reverse order! | |
| // 10. Destroy the temporary here. | |
| clang_analyzer_eval(v.len == 11); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[0] == v.buf[2]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[1] == v.buf[4]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[3] == v.buf[6]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[5] == v.buf[10]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[7] == v.buf[8]); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(v.buf[8] == v.buf[9]); // expected-warning{{TRUE}} | |
| #endif | |
| } | |
| struct Foo { | |
| Foo(Foo **q) { | |
| *q = this; | |
| } | |
| }; | |
| Foo make1(Foo **r) { | |
| return Foo(r); | |
| // no-elide-warning@-1 {{Address of stack memory associated with temporary \ | |
| object of type 'Foo' is still referred to by the caller \ | |
| variable 'z' upon returning to the caller}} | |
| } | |
| void test_copy_elision() { | |
| Foo *z; | |
| // If the copy elided, 'z' points to 'tmp', otherwise it's a dangling pointer. | |
| Foo tmp = make1(&z); | |
| (void)tmp; | |
| } | |
| } // namespace address_vector_tests | |
| namespace arg_directly_from_return_in_loop { | |
| struct Result { | |
| int value; | |
| }; | |
| Result create() { | |
| return Result{10}; | |
| } | |
| int accessValue(Result r) { | |
| return r.value; | |
| } | |
| void test() { | |
| for (int i = 0; i < 3; ++i) { | |
| int v = accessValue(create()); | |
| if (i == 0) { | |
| clang_analyzer_dump(v); // expected-warning {{10 S32b}} | |
| } else { | |
| clang_analyzer_dump(v); // expected-warning {{10 S32b}} | |
| // was {{reg_${{[0-9]+}}<int r.value> }} for C++11 | |
| } | |
| } | |
| } | |
| } // namespace arg_directly_from_return_in_loop |