| // RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc,debug.ExprInspection,cplusplus -analyzer-config c++-inlining=destructors -Wno-null-dereference -Wno-inaccessible-base -verify -analyzer-config eagerly-assume=false %s | |
| void clang_analyzer_eval(bool); | |
| void clang_analyzer_checkInlined(bool); | |
| class A { | |
| public: | |
| ~A() { | |
| int *x = 0; | |
| *x = 3; // expected-warning{{Dereference of null pointer}} | |
| } | |
| }; | |
| int main() { | |
| A a; | |
| } | |
| typedef __typeof(sizeof(int)) size_t; | |
| void *malloc(size_t); | |
| void free(void *); | |
| class SmartPointer { | |
| void *X; | |
| public: | |
| SmartPointer(void *x) : X(x) {} | |
| ~SmartPointer() { | |
| free(X); | |
| } | |
| }; | |
| void testSmartPointer() { | |
| char *mem = (char*)malloc(4); | |
| { | |
| SmartPointer Deleter(mem); | |
| // destructor called here | |
| } | |
| *mem = 0; // expected-warning{{Use of memory after it is released}} | |
| } | |
| void doSomething(); | |
| void testSmartPointer2() { | |
| char *mem = (char*)malloc(4); | |
| { | |
| SmartPointer Deleter(mem); | |
| // Remove dead bindings... | |
| doSomething(); | |
| // destructor called here | |
| } | |
| *mem = 0; // expected-warning{{Use of memory after it is released}} | |
| } | |
| class Subclass : public SmartPointer { | |
| public: | |
| Subclass(void *x) : SmartPointer(x) {} | |
| }; | |
| void testSubclassSmartPointer() { | |
| char *mem = (char*)malloc(4); | |
| { | |
| Subclass Deleter(mem); | |
| // Remove dead bindings... | |
| doSomething(); | |
| // destructor called here | |
| } | |
| *mem = 0; // expected-warning{{Use of memory after it is released}} | |
| } | |
| class MultipleInheritance : public Subclass, public SmartPointer { | |
| public: | |
| MultipleInheritance(void *a, void *b) : Subclass(a), SmartPointer(b) {} | |
| }; | |
| void testMultipleInheritance1() { | |
| char *mem = (char*)malloc(4); | |
| { | |
| MultipleInheritance Deleter(mem, 0); | |
| // Remove dead bindings... | |
| doSomething(); | |
| // destructor called here | |
| } | |
| *mem = 0; // expected-warning{{Use of memory after it is released}} | |
| } | |
| void testMultipleInheritance2() { | |
| char *mem = (char*)malloc(4); | |
| { | |
| MultipleInheritance Deleter(0, mem); | |
| // Remove dead bindings... | |
| doSomething(); | |
| // destructor called here | |
| } | |
| *mem = 0; // expected-warning{{Use of memory after it is released}} | |
| } | |
| void testMultipleInheritance3() { | |
| char *mem = (char*)malloc(4); | |
| { | |
| MultipleInheritance Deleter(mem, mem); | |
| // Remove dead bindings... | |
| doSomething(); | |
| // destructor called here | |
| // expected-warning@28 {{Attempt to release already released memory}} | |
| } | |
| } | |
| class SmartPointerMember { | |
| SmartPointer P; | |
| public: | |
| SmartPointerMember(void *x) : P(x) {} | |
| }; | |
| void testSmartPointerMember() { | |
| char *mem = (char*)malloc(4); | |
| { | |
| SmartPointerMember Deleter(mem); | |
| // Remove dead bindings... | |
| doSomething(); | |
| // destructor called here | |
| } | |
| *mem = 0; // expected-warning{{Use of memory after it is release}} | |
| } | |
| struct IntWrapper { | |
| IntWrapper() : x(0) {} | |
| ~IntWrapper(); | |
| int *x; | |
| }; | |
| void testArrayInvalidation() { | |
| int i = 42; | |
| int j = 42; | |
| { | |
| IntWrapper arr[2]; | |
| // There should be no undefined value warnings here. | |
| clang_analyzer_eval(arr[0].x == 0); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(arr[1].x == 0); // expected-warning{{TRUE}} | |
| arr[0].x = &i; | |
| arr[1].x = &j; | |
| clang_analyzer_eval(*arr[0].x == 42); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(*arr[1].x == 42); // expected-warning{{TRUE}} | |
| } | |
| // The destructors should have invalidated i and j. | |
| clang_analyzer_eval(i == 42); // expected-warning{{UNKNOWN}} | |
| clang_analyzer_eval(j == 42); // expected-warning{{UNKNOWN}} | |
| } | |
| // Don't crash on a default argument inside an initializer. | |
| struct DefaultArg { | |
| DefaultArg(int x = 0) {} | |
| ~DefaultArg(); | |
| }; | |
| struct InheritsDefaultArg : DefaultArg { | |
| InheritsDefaultArg() {} | |
| virtual ~InheritsDefaultArg(); | |
| }; | |
| void testDefaultArg() { | |
| InheritsDefaultArg a; | |
| // Force a bug to be emitted. | |
| *(char *)0 = 1; // expected-warning{{Dereference of null pointer}} | |
| } | |
| namespace DestructorVirtualCalls { | |
| class A { | |
| public: | |
| int *out1, *out2, *out3; | |
| virtual int get() { return 1; } | |
| ~A() { | |
| *out1 = get(); | |
| } | |
| }; | |
| class B : public A { | |
| public: | |
| virtual int get() { return 2; } | |
| ~B() { | |
| *out2 = get(); | |
| } | |
| }; | |
| class C : public B { | |
| public: | |
| virtual int get() { return 3; } | |
| ~C() { | |
| *out3 = get(); | |
| } | |
| }; | |
| void test() { | |
| int a, b, c; | |
| // New scope for the C object. | |
| { | |
| C obj; | |
| clang_analyzer_eval(obj.get() == 3); // expected-warning{{TRUE}} | |
| // Correctness check for devirtualization. | |
| A *base = &obj; | |
| clang_analyzer_eval(base->get() == 3); // expected-warning{{TRUE}} | |
| obj.out1 = &a; | |
| obj.out2 = &b; | |
| obj.out3 = &c; | |
| } | |
| clang_analyzer_eval(a == 1); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(b == 2); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(c == 3); // expected-warning{{TRUE}} | |
| } | |
| } | |
| namespace DestructorsShouldNotAffectReturnValues { | |
| class Dtor { | |
| public: | |
| ~Dtor() { | |
| clang_analyzer_checkInlined(true); // expected-warning{{TRUE}} | |
| } | |
| }; | |
| void *allocate() { | |
| Dtor d; | |
| return malloc(4); // no-warning | |
| } | |
| void test() { | |
| // At one point we had an issue where the statements inside an | |
| // inlined destructor kept us from finding the return statement, | |
| // leading the analyzer to believe that the malloc'd memory had leaked. | |
| void *p = allocate(); | |
| free(p); // no-warning | |
| } | |
| } | |
| namespace MultipleInheritanceVirtualDtors { | |
| class VirtualDtor { | |
| protected: | |
| virtual ~VirtualDtor() { | |
| clang_analyzer_checkInlined(true); // expected-warning{{TRUE}} | |
| } | |
| }; | |
| class NonVirtualDtor { | |
| protected: | |
| ~NonVirtualDtor() { | |
| clang_analyzer_checkInlined(true); // expected-warning{{TRUE}} | |
| } | |
| }; | |
| class SubclassA : public VirtualDtor, public NonVirtualDtor { | |
| public: | |
| virtual ~SubclassA() {} | |
| }; | |
| class SubclassB : public NonVirtualDtor, public VirtualDtor { | |
| public: | |
| virtual ~SubclassB() {} | |
| }; | |
| void test() { | |
| SubclassA a; | |
| SubclassB b; | |
| } | |
| } | |
| namespace ExplicitDestructorCall { | |
| class VirtualDtor { | |
| public: | |
| virtual ~VirtualDtor() { | |
| clang_analyzer_checkInlined(true); // expected-warning{{TRUE}} | |
| } | |
| }; | |
| class Subclass : public VirtualDtor { | |
| public: | |
| virtual ~Subclass() { | |
| clang_analyzer_checkInlined(false); // no-warning | |
| } | |
| }; | |
| void destroy(Subclass *obj) { | |
| obj->VirtualDtor::~VirtualDtor(); | |
| } | |
| } | |
| namespace MultidimensionalArrays { | |
| void testArrayInvalidation() { | |
| int i = 42; | |
| int j = 42; | |
| { | |
| IntWrapper arr[2][2]; | |
| // There should be no undefined value warnings here. | |
| clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(arr[1][1].x == 0); // expected-warning{{TRUE}} | |
| arr[0][0].x = &i; | |
| arr[1][1].x = &j; | |
| clang_analyzer_eval(*arr[0][0].x == 42); // expected-warning{{TRUE}} | |
| clang_analyzer_eval(*arr[1][1].x == 42); // expected-warning{{TRUE}} | |
| } | |
| // The destructors should have invalidated i and j. | |
| clang_analyzer_eval(i == 42); // expected-warning{{UNKNOWN}} | |
| clang_analyzer_eval(j == 42); // expected-warning{{UNKNOWN}} | |
| } | |
| } | |
| namespace LifetimeExtension { | |
| struct IntWrapper { | |
| int x; | |
| IntWrapper(int y) : x(y) {} | |
| IntWrapper() { | |
| extern void use(int); | |
| use(x); // no-warning | |
| } | |
| }; | |
| struct DerivedWrapper : public IntWrapper { | |
| DerivedWrapper(int y) : IntWrapper(y) {} | |
| }; | |
| DerivedWrapper get() { | |
| return DerivedWrapper(1); | |
| } | |
| void test() { | |
| const DerivedWrapper &d = get(); // lifetime extended here | |
| } | |
| class SaveOnDestruct { | |
| public: | |
| static int lastOutput; | |
| int value; | |
| SaveOnDestruct(); | |
| ~SaveOnDestruct() { | |
| lastOutput = value; | |
| } | |
| }; | |
| void testSimple() { | |
| { | |
| const SaveOnDestruct &obj = SaveOnDestruct(); | |
| if (obj.value != 42) | |
| return; | |
| // destructor called here | |
| } | |
| clang_analyzer_eval(SaveOnDestruct::lastOutput == 42); // expected-warning{{TRUE}} | |
| } | |
| struct NRCheck { | |
| bool bool_; | |
| NRCheck():bool_(true) {} | |
| ~NRCheck() __attribute__((noreturn)); | |
| operator bool() const { return bool_; } | |
| }; | |
| struct CheckAutoDestructor { | |
| bool bool_; | |
| CheckAutoDestructor():bool_(true) {} | |
| operator bool() const { return bool_; } | |
| }; | |
| struct CheckCustomDestructor { | |
| bool bool_; | |
| CheckCustomDestructor():bool_(true) {} | |
| ~CheckCustomDestructor(); | |
| operator bool() const { return bool_; } | |
| }; | |
| bool testUnnamedNR() { | |
| if (NRCheck()) | |
| return true; | |
| return false; | |
| } | |
| bool testNamedNR() { | |
| if (NRCheck c = NRCheck()) | |
| return true; | |
| return false; | |
| } | |
| bool testUnnamedAutoDestructor() { | |
| if (CheckAutoDestructor()) | |
| return true; | |
| return false; | |
| } | |
| bool testNamedAutoDestructor() { | |
| if (CheckAutoDestructor c = CheckAutoDestructor()) | |
| return true; | |
| return false; | |
| } | |
| bool testUnnamedCustomDestructor() { | |
| if (CheckCustomDestructor()) | |
| return true; | |
| return false; | |
| } | |
| // This case used to cause an unexpected "Undefined or garbage value returned | |
| // to caller" warning | |
| bool testNamedCustomDestructor() { | |
| if (CheckCustomDestructor c = CheckCustomDestructor()) | |
| return true; | |
| return false; | |
| } | |
| bool testMultipleTemporariesCustomDestructor() { | |
| if (CheckCustomDestructor c = (CheckCustomDestructor(), CheckCustomDestructor())) | |
| return true; | |
| return false; | |
| } | |
| class VirtualDtorBase { | |
| public: | |
| int value; | |
| virtual ~VirtualDtorBase() {} | |
| }; | |
| class SaveOnVirtualDestruct : public VirtualDtorBase { | |
| public: | |
| static int lastOutput; | |
| SaveOnVirtualDestruct(); | |
| virtual ~SaveOnVirtualDestruct() { | |
| lastOutput = value; | |
| } | |
| }; | |
| void testVirtual() { | |
| { | |
| const VirtualDtorBase &obj = SaveOnVirtualDestruct(); | |
| if (obj.value != 42) | |
| return; | |
| // destructor called here | |
| } | |
| clang_analyzer_eval(SaveOnVirtualDestruct::lastOutput == 42); // expected-warning{{TRUE}} | |
| } | |
| } | |
| namespace NoReturn { | |
| struct NR { | |
| ~NR() __attribute__((noreturn)); | |
| }; | |
| void f(int **x) { | |
| NR nr; | |
| } | |
| void g() { | |
| int *x; | |
| f(&x); | |
| *x = 47; // no warning | |
| } | |
| void g2(int *x) { | |
| if (! x) NR(); | |
| *x = 47; // no warning | |
| } | |
| } | |
| namespace PseudoDtor { | |
| template <typename T> | |
| void destroy(T &obj) { | |
| clang_analyzer_checkInlined(true); // expected-warning{{TRUE}} | |
| obj.~T(); | |
| } | |
| void test() { | |
| int i; | |
| destroy(i); | |
| clang_analyzer_eval(true); // expected-warning{{TRUE}} | |
| } | |
| } | |
| namespace Incomplete { | |
| class Foo; // expected-note{{forward declaration}} | |
| void f(Foo *foo) { delete foo; } // expected-warning{{deleting pointer to incomplete type}} | |
| } | |
| namespace TypeTraitExpr { | |
| template <bool IsSimple, typename T> | |
| struct copier { | |
| static void do_copy(T *dest, const T *src, unsigned count); | |
| }; | |
| template <typename T, typename U> | |
| void do_copy(T *dest, const U *src, unsigned count) { | |
| const bool IsSimple = __is_trivial(T) && __is_same(T, U); | |
| copier<IsSimple, T>::do_copy(dest, src, count); | |
| } | |
| struct NonTrivial { | |
| int *p; | |
| NonTrivial() : p(new int[1]) { p[0] = 0; } | |
| NonTrivial(const NonTrivial &other) { | |
| p = new int[1]; | |
| do_copy(p, other.p, 1); | |
| } | |
| NonTrivial &operator=(const NonTrivial &other) { | |
| p = other.p; | |
| return *this; | |
| } | |
| ~NonTrivial() { | |
| delete[] p; // expected-warning {{release already released memory}} | |
| } | |
| }; | |
| void f() { | |
| NonTrivial nt1; | |
| NonTrivial nt2(nt1); | |
| nt1 = nt2; | |
| clang_analyzer_eval(__is_trivial(NonTrivial)); // expected-warning{{FALSE}} | |
| clang_analyzer_eval(__alignof(NonTrivial) > 0); // expected-warning{{TRUE}} | |
| } | |
| } | |
| namespace dtor_over_loc_concrete_int { | |
| struct A { | |
| ~A() {} | |
| }; | |
| struct B { | |
| A a; | |
| ~B() {} | |
| }; | |
| struct C : A { | |
| ~C() {} | |
| }; | |
| void testB() { | |
| B *b = (B *)-1; | |
| b->~B(); // no-crash | |
| } | |
| void testC() { | |
| C *c = (C *)-1; | |
| c->~C(); // no-crash | |
| } | |
| void testAutoDtor() { | |
| const A &a = *(A *)-1; | |
| // no-crash | |
| } | |
| } // namespace dtor_over_loc_concrete_int | |
| // Test overriden new/delete operators | |
| struct CustomOperators { | |
| void *operator new(size_t count) { | |
| return malloc(count); | |
| } | |
| void operator delete(void *addr) { | |
| free(addr); | |
| } | |
| private: | |
| int i; | |
| }; | |
| void compliant() { | |
| auto *a = new CustomOperators(); | |
| delete a; | |
| } | |
| void overrideLeak() { | |
| auto *a = new CustomOperators(); | |
| } // expected-warning{{Potential leak of memory pointed to by 'a'}} | |
| void overrideDoubleDelete() { | |
| auto *a = new CustomOperators(); | |
| delete a; | |
| delete a; // expected-warning@577 {{Attempt to release already released memory}} | |
| } |