
Before this commit the LIT test framework of the static analyzer had a file called `analyzer_test.py` which implemented a tricky system for selecting the constraint manager: - (A) Test files without `REQUIRES: z3` were executed with the default range-based constraint manager. - (B) If clang was built with Z3 support _and_ `USE_Z3_SOLVER=1` was passed to the test run, the test was executed with Z3 as the constraint manager. (There was support for executing the same RUN line twice if both conditions were satisfied.) Unfortunately, using Z3 as the constraint manager does not work in practice (very slow and causes many crashes), so the (B) pathway became unused (or was never truly used?) and became broken due to bit rot. (In the CI bots the analyzer is built without Z3 support, so only the pathway (A) is used.) This commit removes `analyzer_test.py` (+ related logic in other build files + the test `z3/enabled.c` which just tested that `analyzer_test.py` is active), because it tries to implement a feature that we don't need (only one constraint manager is functional) and its code is so complicated and buggy that it isn't useful as a starting point for future development. The fact that this logic was broken implied that tests with `REQUIRES: z3` were not executed during normal testing, so they were also affected by bit rot. Unfortunately this also affected the tests of the `z3-crosscheck` mode (aka Z3 refutation) which also depends on Z3 but uses Z3 in a different way which is actually stable and functional. In this commit I'm fixing most of the `REQUIRES: z3` tests that were broken by straightforward issues. Two test files, `PR37855.c` and `z3-crosscheck.c` were affected by more complex issues, so I marked them as `XFAIL` for now. We're planning to fix them with follow-up commits in the foreseeable future. For additional background information see also the discourse thread https://discourse.llvm.org/t/taking-ownership-of-clang-test-analysis/84689
243 lines
5.0 KiB
C++
243 lines
5.0 KiB
C++
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core,debug.ExprInspection -verify -Wno-null-dereference -Wno-tautological-undefined-compare -analyzer-config eagerly-assume=false %s
|
|
|
|
void clang_analyzer_eval(bool);
|
|
|
|
typedef typeof(sizeof(int)) size_t;
|
|
void malloc (size_t);
|
|
|
|
void f1() {
|
|
int const &i = 3;
|
|
int b = i;
|
|
|
|
int *p = 0;
|
|
|
|
if (b != 3)
|
|
*p = 1; // no-warning
|
|
}
|
|
|
|
char* ptr();
|
|
char& ref();
|
|
|
|
// These next two tests just shouldn't crash.
|
|
char t1 () {
|
|
ref() = 'c';
|
|
return '0';
|
|
}
|
|
|
|
// just a basic correctness test, the same behavior as t1()
|
|
char t2 () {
|
|
*ptr() = 'c';
|
|
return '0';
|
|
}
|
|
|
|
// Each of the tests below is repeated with pointers as well as references.
|
|
// This is mostly a basic correctness check, but then again, both should work!
|
|
char t3 () {
|
|
char& r = ref();
|
|
r = 'c'; // no-warning
|
|
if (r) return r;
|
|
return *(char*)0; // no-warning
|
|
}
|
|
|
|
char t4 () {
|
|
char* p = ptr();
|
|
*p = 'c'; // no-warning
|
|
if (*p) return *p;
|
|
return *(char*)0; // no-warning
|
|
}
|
|
|
|
char t5 (char& r) {
|
|
r = 'c'; // no-warning
|
|
if (r) return r;
|
|
return *(char*)0; // no-warning
|
|
}
|
|
|
|
char t6 (char* p) {
|
|
*p = 'c'; // no-warning
|
|
if (*p) return *p;
|
|
return *(char*)0; // no-warning
|
|
}
|
|
|
|
|
|
// PR13440
|
|
// Test that the array-to-pointer decay works for array references as well.
|
|
// More generally, when we want an lvalue for a reference field, we still need
|
|
// to do one level of load.
|
|
namespace PR13440 {
|
|
typedef int T[1];
|
|
struct S {
|
|
T &x;
|
|
|
|
int *m() { return x; }
|
|
};
|
|
|
|
struct S2 {
|
|
int (&x)[1];
|
|
|
|
int *m() { return x; }
|
|
|
|
void testArrayToPointerDecayWithNonTypedValueRegion() {
|
|
int *p = x;
|
|
int *q = x;
|
|
clang_analyzer_eval(p[0] == q[0]); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
};
|
|
|
|
void test() {
|
|
int a[1];
|
|
S s = { a };
|
|
S2 s2 = { a };
|
|
|
|
if (s.x != a) return; // expected-warning {{comparison between two arrays}}
|
|
if (s2.x != a) return; // expected-warning {{comparison between two arrays}}
|
|
|
|
a[0] = 42;
|
|
clang_analyzer_eval(s.x[0] == 42); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(s2.x[0] == 42); // expected-warning{{TRUE}}
|
|
}
|
|
}
|
|
|
|
void testNullReference() {
|
|
int *x = 0;
|
|
int &y = *x; // expected-warning{{Dereference of null pointer}}
|
|
y = 5;
|
|
}
|
|
|
|
void testRetroactiveNullReference(int *x) {
|
|
// According to the C++ standard, there is no such thing as a
|
|
// "null reference". So the 'if' statement ought to be dead code.
|
|
// However, Clang (and other compilers) don't actually check that a pointer
|
|
// value is non-null in the implementation of references, so it is possible
|
|
// to produce a supposed "null reference" at runtime. The analyzer should
|
|
// still warn when it can prove such errors.
|
|
int &y = *x;
|
|
if (x != 0)
|
|
return;
|
|
y = 5; // expected-warning{{Dereference of null pointer}}
|
|
}
|
|
|
|
namespace TestReferenceAddress {
|
|
struct S { int &x; };
|
|
S getS();
|
|
S *getSP();
|
|
|
|
void testReferenceAddress(int &x) {
|
|
clang_analyzer_eval(&x != 0); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(&ref() != 0); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(&getS().x != 0); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(&getSP()->x != 0); // expected-warning{{TRUE}}
|
|
}
|
|
}
|
|
|
|
void testFunctionPointerReturn(void *opaque) {
|
|
typedef int &(*RefFn)();
|
|
|
|
RefFn getRef = (RefFn)opaque;
|
|
|
|
// Don't crash writing to or reading from this reference.
|
|
int &x = getRef();
|
|
x = 42;
|
|
clang_analyzer_eval(x == 42); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
int &testReturnNullReference() {
|
|
int *x = 0;
|
|
return *x; // expected-warning{{Returning null reference}}
|
|
}
|
|
|
|
char &refFromPointer() {
|
|
return *ptr();
|
|
}
|
|
|
|
void testReturnReference() {
|
|
clang_analyzer_eval(ptr() == 0); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(&refFromPointer() == 0); // expected-warning{{FALSE}}
|
|
}
|
|
|
|
void intRefParam(int &r) {
|
|
;
|
|
}
|
|
|
|
void test(int *ptr) {
|
|
clang_analyzer_eval(ptr == 0); // expected-warning{{UNKNOWN}}
|
|
|
|
extern void use(int &ref);
|
|
use(*ptr);
|
|
|
|
clang_analyzer_eval(ptr == 0); // expected-warning{{FALSE}}
|
|
}
|
|
|
|
void testIntRefParam() {
|
|
int i = 0;
|
|
intRefParam(i); // no-warning
|
|
}
|
|
|
|
int refParam(int &byteIndex) {
|
|
return byteIndex;
|
|
}
|
|
|
|
void testRefParam(int *p) {
|
|
if (p)
|
|
;
|
|
refParam(*p); // expected-warning {{Forming reference to null pointer}}
|
|
}
|
|
|
|
int ptrRefParam(int *&byteIndex) {
|
|
return *byteIndex; // expected-warning {{Dereference of null pointer}}
|
|
}
|
|
void testRefParam2() {
|
|
int *p = 0;
|
|
int *&rp = p;
|
|
ptrRefParam(rp);
|
|
}
|
|
|
|
int *maybeNull() {
|
|
extern bool coin();
|
|
static int x;
|
|
return coin() ? &x : 0;
|
|
}
|
|
|
|
void use(int &x) {
|
|
x = 1; // no-warning
|
|
}
|
|
|
|
void testSuppression() {
|
|
use(*maybeNull());
|
|
}
|
|
|
|
namespace rdar11212286 {
|
|
class B{};
|
|
|
|
B test() {
|
|
B *x = 0;
|
|
return *x; // expected-warning {{Forming reference to null pointer}}
|
|
}
|
|
|
|
B testif(B *x) {
|
|
if (x)
|
|
;
|
|
return *x; // expected-warning {{Forming reference to null pointer}}
|
|
}
|
|
|
|
void idc(B *x) {
|
|
if (x)
|
|
;
|
|
}
|
|
|
|
B testidc(B *x) {
|
|
idc(x);
|
|
return *x; // no-warning
|
|
}
|
|
}
|
|
|
|
namespace PR15694 {
|
|
class C {
|
|
bool bit : 1;
|
|
template <class T> void bar(const T &obj) {}
|
|
void foo() {
|
|
bar(bit); // don't crash
|
|
}
|
|
};
|
|
}
|