| // RUN: %clang_analyze_cc1 -Wno-unused -std=c++11 -analyzer-checker=core,debug.ExprInspection -analyzer-config cfg-temporary-dtors=false -verify %s |
| // RUN: %clang_analyze_cc1 -Wno-unused -std=c++11 -analyzer-checker=core,debug.ExprInspection -analyzer-config cfg-temporary-dtors=true,c++-temp-dtor-inlining=true -DTEMPORARIES -verify %s |
| // RUN: %clang_analyze_cc1 -Wno-unused -std=c++17 -analyzer-checker=core,debug.ExprInspection -analyzer-config cfg-temporary-dtors=true,c++-temp-dtor-inlining=true -DTEMPORARIES %s |
| // RUN: %clang_analyze_cc1 -Wno-unused -std=c++11 -analyzer-checker=core,debug.ExprInspection -analyzer-config cfg-temporary-dtors=false -DMOVES -verify %s |
| // RUN: %clang_analyze_cc1 -Wno-unused -std=c++11 -analyzer-checker=core,debug.ExprInspection -analyzer-config cfg-temporary-dtors=true,c++-temp-dtor-inlining=true -DTEMPORARIES -DMOVES -verify %s |
| // RUN: %clang_analyze_cc1 -Wno-unused -std=c++17 -analyzer-checker=core,debug.ExprInspection -analyzer-config cfg-temporary-dtors=true,c++-temp-dtor-inlining=true -DTEMPORARIES -DMOVES %s |
| |
| // Note: The C++17 run-lines don't -verify yet - it is a no-crash test. |
| |
| void clang_analyzer_eval(bool); |
| void clang_analyzer_checkInlined(bool); |
| |
| namespace pr17001_call_wrong_destructor { |
| bool x; |
| struct A { |
| int *a; |
| A() {} |
| ~A() {} |
| }; |
| struct B : public A { |
| B() {} |
| ~B() { x = true; } |
| }; |
| |
| void f() { |
| { |
| const A &a = B(); |
| } |
| clang_analyzer_eval(x); // expected-warning{{TRUE}} |
| } |
| } // end namespace pr17001_call_wrong_destructor |
| |
| namespace pr19539_crash_on_destroying_an_integer { |
| struct A { |
| int i; |
| int j[2]; |
| A() : i(1) { |
| j[0] = 2; |
| j[1] = 3; |
| } |
| ~A() {} |
| }; |
| |
| void f() { |
| const int &x = A().i; // no-crash |
| const int &y = A().j[1]; // no-crash |
| const int &z = (A().j[1], A().j[0]); // no-crash |
| |
| clang_analyzer_eval(x == 1); |
| clang_analyzer_eval(y == 3); |
| clang_analyzer_eval(z == 2); |
| #ifdef TEMPORARIES |
| // expected-warning@-4{{TRUE}} |
| // expected-warning@-4{{TRUE}} |
| // expected-warning@-4{{TRUE}} |
| #else |
| // expected-warning@-8{{UNKNOWN}} |
| // expected-warning@-8{{UNKNOWN}} |
| // expected-warning@-8{{UNKNOWN}} |
| #endif |
| } |
| } // end namespace pr19539_crash_on_destroying_an_integer |
| |
| namespace maintain_original_object_address_on_lifetime_extension { |
| class C { |
| C **after, **before; |
| |
| public: |
| bool x; |
| |
| C(bool x, C **after, C **before) : x(x), after(after), before(before) { |
| *before = this; |
| } |
| |
| // Don't track copies in our tests. |
| C(const C &c) : x(c.x), after(nullptr), before(nullptr) {} |
| |
| ~C() { if (after) *after = this; } |
| |
| operator bool() const { return x; } |
| |
| static C make(C **after, C **before) { return C(false, after, before); } |
| }; |
| |
| void f1() { |
| C *after, *before; |
| { |
| const C &c = C(true, &after, &before); |
| } |
| clang_analyzer_eval(after == before); |
| #ifdef TEMPORARIES |
| // expected-warning@-2{{TRUE}} |
| #else |
| // expected-warning@-4{{UNKNOWN}} |
| #endif |
| } |
| |
| void f2() { |
| C *after, *before; |
| { |
| C c = C(1, &after, &before); |
| } |
| clang_analyzer_eval(after == before); // expected-warning{{TRUE}} |
| } |
| |
| void f3(bool coin) { |
| C *after, *before; |
| { |
| const C &c = coin ? C(true, &after, &before) : C(false, &after, &before); |
| } |
| clang_analyzer_eval(after == before); |
| #ifdef TEMPORARIES |
| // expected-warning@-2{{TRUE}} |
| #else |
| // expected-warning@-4{{UNKNOWN}} |
| #endif |
| } |
| |
| void f4(bool coin) { |
| C *after, *before; |
| { |
| // no-crash |
| const C &c = C(coin, &after, &before) ?: C(false, &after, &before); |
| } |
| // FIXME: Add support for lifetime extension through binary conditional |
| // operator. Ideally also add support for the binary conditional operator in |
| // C++. Because for now it calls the constructor for the condition twice. |
| if (coin) { |
| // FIXME: Should not warn. |
| clang_analyzer_eval(after == before); |
| #ifdef TEMPORARIES |
| // expected-warning@-2{{The left operand of '==' is a garbage value}} |
| #else |
| // expected-warning@-4{{UNKNOWN}} |
| #endif |
| } else { |
| // FIXME: Should be TRUE. |
| clang_analyzer_eval(after == before); |
| #ifdef TEMPORARIES |
| // expected-warning@-2{{FALSE}} |
| #else |
| // expected-warning@-4{{UNKNOWN}} |
| #endif |
| } |
| } |
| |
| void f5() { |
| C *after, *before; |
| { |
| const bool &x = C(true, &after, &before).x; // no-crash |
| } |
| clang_analyzer_eval(after == before); |
| #ifdef TEMPORARIES |
| // expected-warning@-2{{TRUE}} |
| #else |
| // expected-warning@-4{{UNKNOWN}} |
| #endif |
| } |
| |
| struct A { // A is an aggregate. |
| const C &c; |
| }; |
| |
| void f6() { |
| C *after, *before; |
| { |
| A a{C(true, &after, &before)}; |
| } |
| // FIXME: Should be TRUE. Should not warn about garbage value. |
| clang_analyzer_eval(after == before); // expected-warning{{UNKNOWN}} |
| } |
| |
| void f7() { |
| C *after, *before; |
| { |
| A a = {C(true, &after, &before)}; |
| } |
| // FIXME: Should be TRUE. Should not warn about garbage value. |
| clang_analyzer_eval(after == before); // expected-warning{{UNKNOWN}} |
| } |
| |
| void f8() { |
| C *after, *before; |
| { |
| A a[2] = {C(false, nullptr, nullptr), C(true, &after, &before)}; |
| } |
| // FIXME: Should be TRUE. Should not warn about garbage value. |
| clang_analyzer_eval(after == before); // expected-warning{{UNKNOWN}} |
| } |
| } // end namespace maintain_original_object_address_on_lifetime_extension |
| |
| namespace maintain_original_object_address_on_move { |
| class C { |
| int *x; |
| |
| public: |
| C() : x(nullptr) {} |
| C(int *x) : x(x) {} |
| C(const C &c) = delete; |
| C(C &&c) : x(c.x) { c.x = nullptr; } |
| C &operator=(C &&c) { |
| x = c.x; |
| c.x = nullptr; |
| return *this; |
| } |
| ~C() { |
| // This was triggering the division by zero warning in f1() and f2(): |
| // Because move-elision materialization was incorrectly causing the object |
| // to be relocated from one address to another before move, but destructor |
| // was operating on the old address, it was still thinking that 'x' is set. |
| if (x) |
| *x = 0; |
| } |
| }; |
| |
| void f1() { |
| int x = 1; |
| // &x is replaced with nullptr in move-constructor before the temporary dies. |
| C c = C(&x); |
| // Hence x was not set to 0 yet. |
| 1 / x; // no-warning |
| } |
| void f2() { |
| int x = 1; |
| C c; |
| // &x is replaced with nullptr in move-assignment before the temporary dies. |
| c = C(&x); |
| // Hence x was not set to 0 yet. |
| 1 / x; // no-warning |
| } |
| } // end namespace maintain_original_object_address_on_move |
| |
| namespace maintain_address_of_copies { |
| class C; |
| |
| struct AddressVector { |
| C *buf[10]; |
| int len; |
| |
| AddressVector() : len(0) {} |
| |
| void push(C *c) { |
| buf[len] = c; |
| ++len; |
| } |
| }; |
| |
| class C { |
| AddressVector &v; |
| |
| public: |
| C(AddressVector &v) : v(v) { v.push(this); } |
| ~C() { v.push(this); } |
| |
| #ifdef MOVES |
| C(C &&c) : v(c.v) { v.push(this); } |
| #endif |
| |
| // Note how return-statements prefer move-constructors when available. |
| C(const C &c) : v(c.v) { |
| #ifdef MOVES |
| clang_analyzer_checkInlined(false); // no-warning |
| #else |
| v.push(this); |
| #endif |
| } // no-warning |
| |
| static C make(AddressVector &v) { return C(v); } |
| }; |
| |
| void f1() { |
| AddressVector v; |
| { |
| C c = C(v); |
| } |
| // 0. Construct variable 'c' (copy/move elided). |
| // 1. Destroy variable 'c'. |
| clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}} |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}} |
| } |
| |
| void f2() { |
| AddressVector v; |
| { |
| const C &c = C::make(v); |
| } |
| // 0. Construct the return value of make() (copy/move elided) and |
| // lifetime-extend it directly via reference 'c', |
| // 1. Destroy the temporary lifetime-extended by 'c'. |
| clang_analyzer_eval(v.len == 2); |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); |
| #ifdef TEMPORARIES |
| // expected-warning@-3{{TRUE}} |
| // expected-warning@-3{{TRUE}} |
| #else |
| // expected-warning@-6{{UNKNOWN}} |
| // expected-warning@-6{{UNKNOWN}} |
| #endif |
| } |
| |
| void f3() { |
| AddressVector v; |
| { |
| C &&c = C::make(v); |
| } |
| // 0. Construct the return value of make() (copy/move elided) and |
| // lifetime-extend it directly via reference 'c', |
| // 1. Destroy the temporary lifetime-extended by 'c'. |
| clang_analyzer_eval(v.len == 2); |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); |
| #ifdef TEMPORARIES |
| // expected-warning@-3{{TRUE}} |
| // expected-warning@-3{{TRUE}} |
| #else |
| // expected-warning@-6{{UNKNOWN}} |
| // expected-warning@-6{{UNKNOWN}} |
| #endif |
| } |
| |
| C doubleMake(AddressVector &v) { |
| return C::make(v); |
| } |
| |
| void f4() { |
| AddressVector v; |
| { |
| C c = doubleMake(v); |
| } |
| // 0. Construct variable 'c' (all copies/moves elided), |
| // 1. Destroy variable 'c'. |
| clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}} |
| clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}} |
| } |
| } // end namespace maintain_address_of_copies |