isuckatcs b032e3ff61 [analyzer] Evaluate construction of non-POD type arrays
Introducing the support for evaluating the constructor
of every element in an array. The idea is to record the
index of the current array member being constructed and
create a loop during the analysis. We looping over the
same CXXConstructExpr as many times as many elements
the array has.

Differential Revision: https://reviews.llvm.org/D127973
2022-07-14 23:30:21 +02:00

598 lines
12 KiB
C++

// 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 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.
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 {{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}}
}
}
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 free released memory}}
}