You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
543 lines
12 KiB
543 lines
12 KiB
// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.Malloc,debug.ExprInspection,cplusplus -analyzer-config c++-inlining=destructors,cfg-temporary-dtors=true -Wno-null-dereference -Wno-inaccessible-base -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}}
|
|
}
|
|
|
|
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 {{free 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}}
|
|
}
|
|
}
|