It turns out that in certain cases `SymbolRegions` are wrapped by
`ElementRegions`; in others, it's not. This discrepancy can cause the
analyzer not to recognize if the two regions are actually referring to
the same entity, which then can lead to unreachable paths discovered.
Consider this example:
```lang=C++
struct Node { int* ptr; };
void with_structs(Node* n1) {
Node c = *n1; // copy
Node* n2 = &c;
clang_analyzer_dump(*n1); // lazy...
clang_analyzer_dump(*n2); // lazy...
clang_analyzer_dump(n1->ptr); // rval(n1->ptr): reg_$2<int * SymRegion{reg_$0<struct Node * n1>}.ptr>
clang_analyzer_dump(n2->ptr); // rval(n2->ptr): reg_$1<int * Element{SymRegion{reg_$0<struct Node * n1>},0 S64b,struct Node}.ptr>
clang_analyzer_eval(n1->ptr != n2->ptr); // UNKNOWN, bad!
(void)(*n1);
(void)(*n2);
}
```
The copy of `n1` will insert a new binding to the store; but for doing
that it actually must create a `TypedValueRegion` which it could pass to
the `LazyCompoundVal`. Since the memregion in question is a
`SymbolicRegion` - which is untyped, it needs to first wrap it into an
`ElementRegion` basically implementing this untyped -> typed conversion
for the sake of passing it to the `LazyCompoundVal`.
So, this is why we have `Element{SymRegion{.}, 0,struct Node}` for `n1`.
The problem appears if the analyzer evaluates a read from the expression
`n1->ptr`. The same logic won't apply for `SymbolRegionValues`, since
they accept raw `SubRegions`, hence the `SymbolicRegion` won't be
wrapped into an `ElementRegion` in that case.
Later when we arrive at the equality comparison, we cannot prove that
they are equal.
For more details check the corresponding thread on discourse:
https://discourse.llvm.org/t/are-symbolicregions-really-untyped/64406
---
In this patch, I'm eagerly wrapping each `SymbolicRegion` by an
`ElementRegion`; basically canonicalizing to this form.
It seems reasonable to do so since any object can be thought of as a single
array of that object; so this should not make much of a difference.
The tests also underpin this assumption, as only a few were broken by
this change; and actually fixed a FIXME along the way.
About the second example, which does the same copy operation - but on
the heap - it will be fixed by the next patch.
Reviewed By: martong
Differential Revision: https://reviews.llvm.org/D132142
144 lines
2.9 KiB
C++
144 lines
2.9 KiB
C++
// RUN: %clang_analyze_cc1 -Wno-unused-value -std=c++14 -analyzer-checker=core,debug.ExprInspection,alpha.core.PointerArithm -verify %s
|
|
|
|
template <typename T> void clang_analyzer_dump(T);
|
|
|
|
struct X {
|
|
int *p;
|
|
int zero;
|
|
void foo () {
|
|
reset(p - 1);
|
|
}
|
|
void reset(int *in) {
|
|
while (in != p) // Loop must be entered.
|
|
zero = 1;
|
|
}
|
|
};
|
|
|
|
int test (int *in) {
|
|
X littleX;
|
|
littleX.zero = 0;
|
|
littleX.p = in;
|
|
littleX.foo();
|
|
return 5/littleX.zero; // no-warning
|
|
}
|
|
|
|
|
|
class Base {};
|
|
class Derived : public Base {};
|
|
|
|
void checkPolymorphicUse() {
|
|
Derived d[10];
|
|
|
|
Base *p = d;
|
|
++p; // expected-warning{{Pointer arithmetic on a pointer to base class is dangerous}}
|
|
}
|
|
|
|
void checkBitCasts() {
|
|
long l;
|
|
char *p = (char*)&l;
|
|
p = p+2;
|
|
}
|
|
|
|
void checkBasicarithmetic(int i) {
|
|
int t[10];
|
|
int *p = t;
|
|
++p;
|
|
int a = 5;
|
|
p = &a;
|
|
++p; // expected-warning{{Pointer arithmetic on non-array variables relies on memory layout, which is dangerous}}
|
|
p = p + 2; // expected-warning{{}}
|
|
p = 2 + p; // expected-warning{{}}
|
|
p += 2; // expected-warning{{}}
|
|
a += p[2]; // expected-warning{{}}
|
|
p = i*0 + p;
|
|
p = p + i*0;
|
|
p += i*0;
|
|
}
|
|
|
|
void checkArithOnSymbolic(int*p) {
|
|
++p;
|
|
p = p + 2;
|
|
p = 2 + p;
|
|
p += 2;
|
|
(void)p[2];
|
|
}
|
|
|
|
struct S {
|
|
int t[10];
|
|
};
|
|
|
|
void arrayInStruct() {
|
|
S s;
|
|
int * p = s.t;
|
|
++p;
|
|
S *sp = new S;
|
|
p = sp->t;
|
|
++p;
|
|
delete sp;
|
|
}
|
|
|
|
void checkNew() {
|
|
int *p = new int;
|
|
p[1] = 1; // expected-warning{{}}
|
|
}
|
|
|
|
void InitState(int* state) {
|
|
state[1] = 1; // expected-warning{{}}
|
|
}
|
|
|
|
int* getArray(int size) {
|
|
if (size == 0)
|
|
return new int;
|
|
return new int[5];
|
|
}
|
|
|
|
void checkConditionalArray() {
|
|
int* maybeArray = getArray(0);
|
|
InitState(maybeArray);
|
|
}
|
|
|
|
void checkMultiDimansionalArray() {
|
|
int a[5][5];
|
|
*(*(a+1)+2) = 2;
|
|
}
|
|
|
|
unsigned ptrSubtractionNoCrash(char *Begin, char *End) {
|
|
auto N = End - Begin;
|
|
if (Begin)
|
|
return 0;
|
|
return N;
|
|
}
|
|
|
|
// Bug 34309
|
|
bool ptrAsIntegerSubtractionNoCrash(__UINTPTR_TYPE__ x, char *p) {
|
|
__UINTPTR_TYPE__ y = (__UINTPTR_TYPE__)p - 1;
|
|
return y == x;
|
|
}
|
|
|
|
// Bug 34374
|
|
bool integerAsPtrSubtractionNoCrash(char *p, __UINTPTR_TYPE__ m) {
|
|
auto n = p - reinterpret_cast<char*>((__UINTPTR_TYPE__)1);
|
|
return n == m;
|
|
}
|
|
|
|
namespace Bug_55934 {
|
|
struct header {
|
|
unsigned a : 1;
|
|
unsigned b : 1;
|
|
};
|
|
struct parse_t {
|
|
unsigned bits0 : 1;
|
|
unsigned bits2 : 2; // <-- header
|
|
unsigned bits4 : 4;
|
|
};
|
|
int parse(parse_t *p) {
|
|
unsigned copy = p->bits2;
|
|
clang_analyzer_dump(copy);
|
|
// expected-warning@-1 {{reg_$1<unsigned int Element{SymRegion{reg_$0<parse_t * p>},0 S64b,struct Bug_55934::parse_t}.bits2>}}
|
|
header *bits = (header *)©
|
|
clang_analyzer_dump(bits->b);
|
|
// expected-warning@-1 {{derived_$2{reg_$1<unsigned int Element{SymRegion{reg_$0<parse_t * p>},0 S64b,struct Bug_55934::parse_t}.bits2>,Element{copy,0 S64b,struct Bug_55934::header}.b}}}
|
|
return bits->b; // no-warning
|
|
}
|
|
} // namespace Bug_55934
|