[analyzer] Be more forgiving about calling methods on struct rvalues.

The problem is that the value of 'this' in a C++ member function call
should always be a region (or NULL). However, if the object is an rvalue,
it has no associated region (only a conjured symbol or LazyCompoundVal).
For now, we handle this in two ways:

1) Actually respect MaterializeTemporaryExpr. Before, it was relying on
   CXXConstructExpr to create temporary regions for all struct values.
   Now it just does the right thing: if the value is not in a temporary
   region, create one.

2) Have CallEvent recognize the case where its 'this' pointer is a
   non-region, and just return UnknownVal to keep from confusing clients.

The long-term problem is being tracked internally in <rdar://problem/12137950>,
but this makes many test cases pass.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@163220 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Jordan Rose 2012-09-05 17:11:26 +00:00
Родитель 4e45dba1c0
Коммит 6ebea89be2
5 изменённых файлов: 118 добавлений и 19 удалений

Просмотреть файл

@ -504,13 +504,7 @@ public:
virtual const Expr *getCXXThisExpr() const { return 0; }
/// \brief Returns the value of the implicit 'this' object.
virtual SVal getCXXThisVal() const {
const Expr *Base = getCXXThisExpr();
// FIXME: This doesn't handle an overloaded ->* operator.
if (!Base)
return UnknownVal();
return getSVal(Base);
}
virtual SVal getCXXThisVal() const;
virtual const FunctionDecl *getDecl() const;

Просмотреть файл

@ -384,6 +384,25 @@ void CXXInstanceCall::getExtraInvalidatedRegions(RegionList &Regions) const {
Regions.push_back(R);
}
SVal CXXInstanceCall::getCXXThisVal() const {
const Expr *Base = getCXXThisExpr();
// FIXME: This doesn't handle an overloaded ->* operator.
if (!Base)
return UnknownVal();
SVal ThisVal = getSVal(Base);
// FIXME: This is only necessary because we can call member functions on
// struct rvalues, which do not have regions we can use for a 'this' pointer.
// Ideally this should eventually be changed to an assert, i.e. all
// non-Unknown, non-null 'this' values should be loc::MemRegionVals.
if (isa<DefinedSVal>(ThisVal))
if (!ThisVal.getAsRegion() && !ThisVal.isConstant())
return UnknownVal();
return ThisVal;
}
RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
// Do we have a decl at all?

Просмотреть файл

@ -34,9 +34,10 @@ void ExprEngine::CreateCXXTemporaryObject(const MaterializeTemporaryExpr *ME,
// the expression to the location of the object.
SVal V = state->getSVal(tempExpr, LCtx);
// If the object is a record, the constructor will have already created
// a temporary object region. If it is not, we need to copy the value over.
if (!ME->getType()->isRecordType()) {
// If the value is already a CXXTempObjectRegion, it is fine as it is.
// Otherwise, create a new CXXTempObjectRegion, and copy the value into it.
const MemRegion *MR = V.getAsRegion();
if (!MR || !isa<CXXTempObjectRegion>(MR)) {
const MemRegion *R =
svalBuilder.getRegionManager().getCXXTempObjectRegion(ME, LCtx);

Просмотреть файл

@ -0,0 +1,87 @@
// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.core,debug.ExprInspection -verify -x c %s
// RUN: %clang_cc1 -analyze -analyzer-checker=core,alpha.core,debug.ExprInspection -verify -x c++ -analyzer-config c++-inlining=constructors %s
void clang_analyzer_eval(int);
struct S {
int field;
#if __cplusplus
const struct S *getThis() const { return this; }
#endif
};
struct S getS();
void testAssignment() {
struct S s = getS();
if (s.field != 42) return;
clang_analyzer_eval(s.field == 42); // expected-warning{{TRUE}}
s.field = 0;
clang_analyzer_eval(s.field == 0); // expected-warning{{TRUE}}
#if __cplusplus
clang_analyzer_eval(s.getThis() == &s); // expected-warning{{TRUE}}
#endif
}
void testImmediateUse() {
int x = getS().field;
if (x != 42) return;
clang_analyzer_eval(x == 42); // expected-warning{{TRUE}}
#if __cplusplus
clang_analyzer_eval((void *)getS().getThis() == (void *)&x); // expected-warning{{FALSE}}
#endif
}
int getConstrainedField(struct S s) {
if (s.field != 42) return 42;
return s.field;
}
int getAssignedField(struct S s) {
s.field = 42;
return s.field;
}
void testArgument() {
clang_analyzer_eval(getConstrainedField(getS()) == 42); // expected-warning{{TRUE}}
clang_analyzer_eval(getAssignedField(getS()) == 42); // expected-warning{{TRUE}}
}
//--------------------
// C++-only tests
//--------------------
#if __cplusplus
void testReferenceAssignment() {
const S &s = getS();
if (s.field != 42) return;
clang_analyzer_eval(s.field == 42); // expected-warning{{TRUE}}
clang_analyzer_eval(s.getThis() == &s); // expected-warning{{TRUE}}
}
int getConstrainedFieldRef(const S &s) {
if (s.field != 42) return 42;
return s.field;
}
bool checkThis(const S &s) {
return s.getThis() == &s;
}
void testReferenceArgument() {
clang_analyzer_eval(getConstrainedFieldRef(getS()) == 42); // expected-warning{{TRUE}}
clang_analyzer_eval(checkThis(getS())); // expected-warning{{TRUE}}
}
#endif

Просмотреть файл

@ -116,8 +116,13 @@ void testReferenceAddress(int &x) {
struct S { int &x; };
extern S *getS();
clang_analyzer_eval(&getS()->x != 0); // expected-warning{{TRUE}}
// FIXME: Should be TRUE. Fields of return-by-value structs are not yet
// symbolicated. Tracked by <rdar://problem/12137950>.
extern S getS();
clang_analyzer_eval(&getS().x != 0); // expected-warning{{UNKNOWN}}
extern S *getSP();
clang_analyzer_eval(&getSP()->x != 0); // expected-warning{{TRUE}}
}
@ -150,10 +155,3 @@ namespace rdar11212286 {
return *x; // should warn here!
}
}
void testReferenceFieldAddress() {
struct S { int &x; };
extern S getS();
clang_analyzer_eval(&getS().x != 0); // expected-warning{{UNKNOWN}}
}