Jordan Rose 8647ffcda5 [analyzer] Correctly handle destructors for lifetime-extended temporaries.
The lifetime of a temporary can be extended when it is immediately bound
to a local reference:

  const Value &MyVal = Value("temporary");

In this case, the temporary object's lifetime is extended for the entire
scope of the reference; at the end of the scope it is destroyed.

The analyzer was modeling this improperly in two ways:
- Since we don't model temporary constructors just yet, we create a fake
  temporary region when it comes time to "materialize" a temporary into
  a real object (lvalue). This wasn't taking base casts into account when
  the bindings being materialized was Unknown; now it always respects base
  casts except when the temporary region is itself a pointer.
- When actually destroying the region, the analyzer did not actually load
  from the reference variable -- it was basically destroying the reference
  instead of its referent. Now it does do the load.

This will be more useful whenever we finally start modeling temporaries,
or at least those that get bound to local reference variables.

<rdar://problem/13552274>

llvm-svn: 178697
2013-04-03 21:16:58 +00:00

404 lines
8.4 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}}
}
}