diff options
Diffstat (limited to 'test/Analysis/lifetime-extension.cpp')
-rw-r--r-- | test/Analysis/lifetime-extension.cpp | 298 |
1 files changed, 293 insertions, 5 deletions
diff --git a/test/Analysis/lifetime-extension.cpp b/test/Analysis/lifetime-extension.cpp index 5e3c5dde0a8b..4337632b31c8 100644 --- a/test/Analysis/lifetime-extension.cpp +++ b/test/Analysis/lifetime-extension.cpp @@ -1,6 +1,14 @@ -// RUN: %clang_analyze_cc1 -Wno-unused -std=c++11 -analyzer-checker=debug.ExprInspection -verify %s +// 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; @@ -38,9 +46,289 @@ void f() { const int &y = A().j[1]; // no-crash const int &z = (A().j[1], A().j[0]); // no-crash - // FIXME: All of these should be TRUE, but constructors aren't inlined. - clang_analyzer_eval(x == 1); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(y == 3); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(z == 2); // expected-warning{{UNKNOWN}} + 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 |