Summary: This patch enables ExprEndgine to reason about temporary object destructors. However, these destructor calls are never inlined, since this feature is still broken. Still, this is sufficient to properly handle noreturn temporary destructors and close bug #15599. I have also enabled the cfg-temporary-dtors analyzer option by default. Reviewers: jordan_rose CC: cfe-commits Differential Revision: http://llvm-reviews.chandlerc.com/D1131 llvm-svn: 186498
435 lines
8.8 KiB
C++
435 lines
8.8 KiB
C++
// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.Malloc,debug.ExprInspection -analyzer-config c++-inlining=destructors -Wno-null-dereference -verify %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 freed}}
|
|
}
|
|
|
|
|
|
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 freed}}
|
|
}
|
|
|
|
|
|
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 freed}}
|
|
}
|
|
|
|
|
|
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 freed}}
|
|
}
|
|
|
|
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 freed}}
|
|
}
|
|
|
|
void testMultipleInheritance3() {
|
|
char *mem = (char*)malloc(4);
|
|
{
|
|
MultipleInheritance Deleter(mem, mem);
|
|
// Remove dead bindings...
|
|
doSomething();
|
|
// destructor called here
|
|
// expected-warning@28 {{Attempt to free 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 freed}}
|
|
}
|
|
|
|
|
|
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.
|
|
// Eventually these should be TRUE as well, but right now
|
|
// we can't handle array constructors.
|
|
clang_analyzer_eval(arr[0].x == 0); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(arr[1].x == 0); // expected-warning{{UNKNOWN}}
|
|
|
|
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}}
|
|
|
|
// Sanity 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.
|
|
// Eventually these should be TRUE as well, but right now
|
|
// we can't handle array constructors.
|
|
clang_analyzer_eval(arr[0][0].x == 0); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(arr[1][1].x == 0); // expected-warning{{UNKNOWN}}
|
|
|
|
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}}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
void f3(int **x) {
|
|
NR();
|
|
}
|
|
|
|
void g3() {
|
|
int *x;
|
|
f3(&x);
|
|
*x = 47; // no warning
|
|
}
|
|
}
|