[analyzer] Migrate argument invalidation from CFRefCount to ExprEngine.

This is a common path for function and C++ method calls, Objective-C messages and property accesses, and C++ construct-exprs.

As support, add message receiver accessors to ObjCMessage and CallOrObjCMessage.

llvm-svn: 138718
This commit is contained in:
Jordy Rose 2011-08-28 05:16:28 +00:00
parent 7bfd86d046
commit d188d66e69
7 changed files with 196 additions and 266 deletions

View File

@ -410,6 +410,10 @@ protected:
void evalObjCMessage(ExplodedNodeSet &Dst, const ObjCMessage &msg,
ExplodedNode *Pred, const ProgramState *state);
const ProgramState *invalidateArguments(const ProgramState *State,
const CallOrObjCMessage &Call,
const LocationContext *LC);
const ProgramState *MarkBranch(const ProgramState *St, const Stmt *Terminator,
bool branchTaken);

View File

@ -88,6 +88,21 @@ public:
return 0;
}
SVal getInstanceReceiverSVal(const ProgramState *State,
const LocationContext *LC) const {
assert(isValid() && "This ObjCMessage is uninitialized!");
if (!isInstanceMessage())
return UndefinedVal();
if (const Expr *Ex = getInstanceReceiver())
return State->getSValAsScalarOrLoc(Ex);
// An instance message with no expression means we are sending to super.
// In this case the object reference is the same as 'self'.
const ImplicitParamDecl *SelfDecl = LC->getSelfDecl();
assert(SelfDecl && "No message receiver Expr, but not in an ObjC method");
return State->getSVal(State->getRegion(SelfDecl, LC));
}
bool isInstanceMessage() const {
assert(isValid() && "This ObjCMessage is uninitialized!");
if (const ObjCMessageExpr *msgE = dyn_cast<ObjCMessageExpr>(MsgOrPropE))
@ -98,7 +113,7 @@ public:
}
const ObjCMethodDecl *getMethodDecl() const;
const ObjCInterfaceDecl *getReceiverInterface() const;
SourceLocation getSuperLoc() const {
@ -108,45 +123,58 @@ public:
return cast<ObjCPropertyRefExpr>(MsgOrPropE)->getReceiverLocation();
}
SourceRange getSourceRange() const {
assert(isValid() && "This ObjCMessage is uninitialized!");
SourceRange getSourceRange() const {
assert(isValid() && "This ObjCMessage is uninitialized!");
return MsgOrPropE->getSourceRange();
}
unsigned getNumArgs() const {
assert(isValid() && "This ObjCMessage is uninitialized!");
if (const ObjCMessageExpr *msgE = dyn_cast<ObjCMessageExpr>(MsgOrPropE))
return msgE->getNumArgs();
return isPropertySetter() ? 1 : 0;
}
unsigned getNumArgs() const {
assert(isValid() && "This ObjCMessage is uninitialized!");
if (const ObjCMessageExpr *msgE = dyn_cast<ObjCMessageExpr>(MsgOrPropE))
return msgE->getNumArgs();
return isPropertySetter() ? 1 : 0;
}
SVal getArgSVal(unsigned i, const ProgramState *state) const {
assert(isValid() && "This ObjCMessage is uninitialized!");
assert(i < getNumArgs() && "Invalid index for argument");
if (const ObjCMessageExpr *msgE = dyn_cast<ObjCMessageExpr>(MsgOrPropE))
return state->getSVal(msgE->getArg(i));
assert(isPropertySetter());
return SetterArgV;
}
SVal getArgSVal(unsigned i, const ProgramState *state) const {
assert(isValid() && "This ObjCMessage is uninitialized!");
assert(i < getNumArgs() && "Invalid index for argument");
if (const ObjCMessageExpr *msgE = dyn_cast<ObjCMessageExpr>(MsgOrPropE))
return state->getSVal(msgE->getArg(i));
assert(isPropertySetter());
return SetterArgV;
}
QualType getArgType(unsigned i) const {
assert(isValid() && "This ObjCMessage is uninitialized!");
assert(i < getNumArgs() && "Invalid index for argument");
if (const ObjCMessageExpr *msgE = dyn_cast<ObjCMessageExpr>(MsgOrPropE))
return msgE->getArg(i)->getType();
assert(isPropertySetter());
return cast<ObjCPropertyRefExpr>(MsgOrPropE)->getType();
}
const Expr *getArgExpr(unsigned i) const;
QualType getArgType(unsigned i) const {
assert(isValid() && "This ObjCMessage is uninitialized!");
assert(i < getNumArgs() && "Invalid index for argument");
if (const ObjCMessageExpr *msgE = dyn_cast<ObjCMessageExpr>(MsgOrPropE))
return msgE->getArg(i)->getType();
assert(isPropertySetter());
return cast<ObjCPropertyRefExpr>(MsgOrPropE)->getType();
}
SourceRange getArgSourceRange(unsigned i) const {
assert(isValid() && "This ObjCMessage is uninitialized!");
assert(i < getNumArgs() && "Invalid index for argument");
if (const Expr *argE = getArgExpr(i))
return argE->getSourceRange();
return OriginE->getSourceRange();
}
const Expr *getArgExpr(unsigned i) const;
SourceRange getArgSourceRange(unsigned i) const {
assert(isValid() && "This ObjCMessage is uninitialized!");
assert(i < getNumArgs() && "Invalid index for argument");
if (const Expr *argE = getArgExpr(i))
return argE->getSourceRange();
return OriginE->getSourceRange();
}
SourceRange getReceiverSourceRange() const {
assert(isValid() && "This ObjCMessage is uninitialized!");
if (const ObjCMessageExpr *msgE = dyn_cast<ObjCMessageExpr>(MsgOrPropE))
return msgE->getReceiverRange();
const ObjCPropertyRefExpr *propE = cast<ObjCPropertyRefExpr>(MsgOrPropE);
if (propE->isObjectReceiver())
return propE->getBase()->getSourceRange();
// FIXME: This isn't a range.
return propE->getReceiverLocation();
}
};
class ObjCPropertyGetter : public ObjCMessage {
@ -211,6 +239,7 @@ public:
SVal getFunctionCallee() const;
SVal getCXXCallee() const;
SVal getInstanceMessageReceiver(const LocationContext *LC) const;
unsigned getNumArgs() const {
if (!CallE)
@ -244,6 +273,11 @@ public:
return getArg(i)->getSourceRange();
return Msg.getArgSourceRange(i);
}
SourceRange getReceiverSourceRange() const {
assert(isObjCMessage());
return Msg.getReceiverSourceRange();
}
};
}

View File

@ -40,50 +40,6 @@ using namespace clang;
using namespace ento;
using llvm::StrInStrNoCase;
namespace {
class InstanceReceiver {
ObjCMessage Msg;
const LocationContext *LC;
public:
InstanceReceiver() : LC(0) { }
InstanceReceiver(const ObjCMessage &msg,
const LocationContext *lc = 0) : Msg(msg), LC(lc) {}
bool isValid() const {
return Msg.isValid() && Msg.isInstanceMessage();
}
operator bool() const {
return isValid();
}
SVal getSValAsScalarOrLoc(const ProgramState *state) {
assert(isValid());
// We have an expression for the receiver? Fetch the value
// of that expression.
if (const Expr *Ex = Msg.getInstanceReceiver())
return state->getSValAsScalarOrLoc(Ex);
// Otherwise we are sending a message to super. In this case the
// object reference is the same as 'self'.
if (const ImplicitParamDecl *SelfDecl = LC->getSelfDecl())
return state->getSVal(state->getRegion(SelfDecl, LC));
return UnknownVal();
}
SourceRange getSourceRange() const {
assert(isValid());
if (const Expr *Ex = Msg.getInstanceReceiver())
return Ex->getSourceRange();
// Otherwise we are sending a message to super.
SourceLocation L = Msg.getSuperLoc();
assert(L.isValid());
return SourceRange(L, L);
}
};
}
namespace {
class GenericNodeBuilderRefCount {
StmtNodeBuilder *SNB;
@ -1651,28 +1607,6 @@ public:
}
const LangOptions& getLangOptions() const { return LOpts; }
// Calls.
void evalCallOrMessage(ExplodedNodeSet &Dst, ExprEngine &Eng,
StmtNodeBuilder &Builder,
const CallOrObjCMessage &callOrMsg,
InstanceReceiver Receiver, const MemRegion *Callee,
ExplodedNode *Pred, const ProgramState *state);
virtual void evalCall(ExplodedNodeSet &Dst,
ExprEngine& Eng,
StmtNodeBuilder& Builder,
const CallExpr *CE, SVal L,
ExplodedNode *Pred);
virtual void evalObjCMessage(ExplodedNodeSet &Dst,
ExprEngine& Engine,
StmtNodeBuilder& Builder,
ObjCMessage msg,
ExplodedNode *Pred,
const ProgramState *state);
};
} // end anonymous namespace
@ -2439,133 +2373,6 @@ static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) {
return RetTy;
}
void CFRefCount::evalCallOrMessage(ExplodedNodeSet &Dst, ExprEngine &Eng,
StmtNodeBuilder &Builder,
const CallOrObjCMessage &callOrMsg,
InstanceReceiver Receiver,
const MemRegion *Callee,
ExplodedNode *Pred,
const ProgramState *state) {
SmallVector<const MemRegion*, 10> RegionsToInvalidate;
// Invalidate all instance variables of the receiver of a message.
// FIXME: We should be able to do better with inter-procedural analysis.
if (Receiver) {
SVal V = Receiver.getSValAsScalarOrLoc(state);
if (const MemRegion *region = V.getAsRegion())
RegionsToInvalidate.push_back(region);
}
// Invalidate all instance variables for the callee of a C++ method call.
// FIXME: We should be able to do better with inter-procedural analysis.
// FIXME: we can probably do better for const versus non-const methods.
if (callOrMsg.isCXXCall()) {
if (const MemRegion *callee = callOrMsg.getCXXCallee().getAsRegion())
RegionsToInvalidate.push_back(callee);
}
for (unsigned idx = 0, e = callOrMsg.getNumArgs(); idx != e; ++idx) {
SVal V = callOrMsg.getArgSVal(idx);
// If we are passing a location wrapped as an integer, unwrap it and
// invalidate the values referred by the location.
if (nonloc::LocAsInteger *Wrapped = dyn_cast<nonloc::LocAsInteger>(&V))
V = Wrapped->getLoc();
else if (!isa<Loc>(V))
continue;
if (const MemRegion *R = V.getAsRegion()) {
// Invalidate the value of the variable passed by reference.
// Are we dealing with an ElementRegion? If the element type is
// a basic integer type (e.g., char, int) and the underying region
// is a variable region then strip off the ElementRegion.
// FIXME: We really need to think about this for the general case
// as sometimes we are reasoning about arrays and other times
// about (char*), etc., is just a form of passing raw bytes.
// e.g., void *p = alloca(); foo((char*)p);
if (const ElementRegion *ER = dyn_cast<ElementRegion>(R)) {
// Checking for 'integral type' is probably too promiscuous, but
// we'll leave it in for now until we have a systematic way of
// handling all of these cases. Eventually we need to come up
// with an interface to StoreManager so that this logic can be
// approriately delegated to the respective StoreManagers while
// still allowing us to do checker-specific logic (e.g.,
// invalidating reference counts), probably via callbacks.
if (ER->getElementType()->isIntegralOrEnumerationType()) {
const MemRegion *superReg = ER->getSuperRegion();
if (isa<VarRegion>(superReg) || isa<FieldRegion>(superReg) ||
isa<ObjCIvarRegion>(superReg))
R = cast<TypedRegion>(superReg);
}
// FIXME: What about layers of ElementRegions?
}
// Mark this region for invalidation. We batch invalidate regions
// below for efficiency.
RegionsToInvalidate.push_back(R);
} else {
// Nuke all other arguments passed by reference.
// FIXME: is this necessary or correct? This handles the non-Region
// cases. Is it ever valid to store to these?
state = state->unbindLoc(cast<Loc>(V));
}
}
// Block calls result in all captured values passed-via-reference to be
// invalidated.
if (const BlockDataRegion *BR = dyn_cast_or_null<BlockDataRegion>(Callee))
RegionsToInvalidate.push_back(BR);
// Invalidate designated regions using the batch invalidation API.
// FIXME: We can have collisions on the conjured symbol if the
// expression *I also creates conjured symbols. We probably want
// to identify conjured symbols by an expression pair: the enclosing
// expression (the context) and the expression itself. This should
// disambiguate conjured symbols.
unsigned Count = Builder.getCurrentBlockCount();
StoreManager::InvalidatedSymbols IS;
const Expr *Ex = callOrMsg.getOriginExpr();
// NOTE: Even if RegionsToInvalidate is empty, we must still invalidate
// global variables.
// NOTE: RetainReleaseChecker handles the actual invalidation of symbols.
state =
state->invalidateRegions(RegionsToInvalidate,
Ex, Count, &IS,
/* invalidateGlobals = */
Eng.doesInvalidateGlobals(callOrMsg));
Builder.MakeNode(Dst, Ex, Pred, state);
}
void CFRefCount::evalCall(ExplodedNodeSet &Dst,
ExprEngine& Eng,
StmtNodeBuilder& Builder,
const CallExpr *CE, SVal L,
ExplodedNode *Pred) {
evalCallOrMessage(Dst, Eng, Builder, CallOrObjCMessage(CE, Pred->getState()),
InstanceReceiver(), L.getAsRegion(), Pred,
Pred->getState());
}
void CFRefCount::evalObjCMessage(ExplodedNodeSet &Dst,
ExprEngine& Eng,
StmtNodeBuilder& Builder,
ObjCMessage msg,
ExplodedNode *Pred,
const ProgramState *state) {
evalCallOrMessage(Dst, Eng, Builder, CallOrObjCMessage(msg, Pred->getState()),
InstanceReceiver(msg, Pred->getLocationContext()),
/* Callee = */ 0, Pred, state);
}
//===----------------------------------------------------------------------===//
// Pieces of the retain/release checker implemented using a CheckerVisitor.
// More pieces of the retain/release checker will be migrated to this interface
@ -2744,7 +2551,7 @@ public:
void checkPostStmt(const CXXConstructExpr *CE, CheckerContext &C) const;
void checkPostObjCMessage(const ObjCMessage &Msg, CheckerContext &C) const;
void checkSummary(const RetainSummary &Summ, const CallOrObjCMessage &Call,
InstanceReceiver Receiver, CheckerContext &C) const;
CheckerContext &C) const;
bool evalCall(const CallExpr *CE, CheckerContext &C) const;
@ -3013,7 +2820,7 @@ void RetainReleaseChecker::checkPostStmt(const CallExpr *CE,
if (!Summ)
return;
checkSummary(*Summ, CallOrObjCMessage(CE, state), InstanceReceiver(), C);
checkSummary(*Summ, CallOrObjCMessage(CE, state), C);
}
void RetainReleaseChecker::checkPostStmt(const CXXConstructExpr *CE,
@ -3030,7 +2837,7 @@ void RetainReleaseChecker::checkPostStmt(const CXXConstructExpr *CE,
return;
const ProgramState *state = C.getState();
checkSummary(*Summ, CallOrObjCMessage(CE, state), InstanceReceiver(), C);
checkSummary(*Summ, CallOrObjCMessage(CE, state), C);
}
void RetainReleaseChecker::checkPostObjCMessage(const ObjCMessage &Msg,
@ -3052,13 +2859,11 @@ void RetainReleaseChecker::checkPostObjCMessage(const ObjCMessage &Msg,
if (!Summ)
return;
checkSummary(*Summ, CallOrObjCMessage(Msg, state),
InstanceReceiver(Msg, Pred->getLocationContext()), C);
checkSummary(*Summ, CallOrObjCMessage(Msg, state), C);
}
void RetainReleaseChecker::checkSummary(const RetainSummary &Summ,
const CallOrObjCMessage &CallOrMsg,
InstanceReceiver Receiver,
CheckerContext &C) const {
const ProgramState *state = C.getState();
@ -3084,13 +2889,15 @@ void RetainReleaseChecker::checkSummary(const RetainSummary &Summ,
// Evaluate the effect on the message receiver.
bool ReceiverIsTracked = false;
if (!hasErr && Receiver) {
if (SymbolRef Sym = Receiver.getSValAsScalarOrLoc(state).getAsLocSymbol()) {
if (!hasErr && CallOrMsg.isObjCMessage()) {
const LocationContext *LC = C.getPredecessor()->getLocationContext();
SVal Receiver = CallOrMsg.getInstanceMessageReceiver(LC);
if (SymbolRef Sym = Receiver.getAsLocSymbol()) {
if (const RefVal *T = state->get<RefBindings>(Sym)) {
ReceiverIsTracked = true;
state = updateSymbol(state, Sym, *T, Summ.getReceiverEffect(), hasErr);
if (hasErr) {
ErrorRange = Receiver.getSourceRange();
ErrorRange = CallOrMsg.getReceiverSourceRange();
ErrorSym = Sym;
}
}

View File

@ -198,17 +198,6 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *E,
#endif
// Default semantics: invalidate all regions passed as arguments.
SmallVector<const MemRegion*, 10> regionsToInvalidate;
// FIXME: We can have collisions on the conjured symbol if the
// expression *I also creates conjured symbols. We probably want
// to identify conjured symbols by an expression pair: the enclosing
// expression (the context) and the expression itself. This should
// disambiguate conjured symbols.
unsigned blockCount = Builder->getCurrentBlockCount();
// NOTE: Even if RegionsToInvalidate is empty, we must still invalidate
// global variables.
ExplodedNodeSet destCall;
for (ExplodedNodeSet::iterator
@ -216,23 +205,10 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *E,
i != e; ++i)
{
ExplodedNode *Pred = *i;
const LocationContext *LC = Pred->getLocationContext();
const ProgramState *state = Pred->getState();
// Accumulate list of regions that are invalidated.
for (CXXConstructExpr::const_arg_iterator
ai = E->arg_begin(), ae = E->arg_end();
ai != ae; ++ai)
{
SVal val = state->getSVal(*ai);
if (const MemRegion *region = val.getAsRegion())
regionsToInvalidate.push_back(region);
}
// Invalidate the regions.
state = state->invalidateRegions(regionsToInvalidate,
E, blockCount, 0,
/* invalidateGlobals = */ true);
state = invalidateArguments(state, CallOrObjCMessage(E, state), LC);
Builder->MakeNode(destCall, E, Pred, state);
}

View File

@ -13,6 +13,7 @@
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h"
#include "clang/AST/DeclCXX.h"
#include "clang/Analysis/Support/SaveAndRestore.h"
@ -63,6 +64,100 @@ void ExprEngine::processCallExit(CallExitNodeBuilder &B) {
B.generateNode(state);
}
const ProgramState *
ExprEngine::invalidateArguments(const ProgramState *State,
const CallOrObjCMessage &Call,
const LocationContext *LC) {
SmallVector<const MemRegion *, 8> RegionsToInvalidate;
if (Call.isObjCMessage()) {
// Invalidate all instance variables of the receiver of an ObjC message.
// FIXME: We should be able to do better with inter-procedural analysis.
if (const MemRegion *MR = Call.getInstanceMessageReceiver(LC).getAsRegion())
RegionsToInvalidate.push_back(MR);
} else if (Call.isCXXCall()) {
// Invalidate all instance variables for the callee of a C++ method call.
// FIXME: We should be able to do better with inter-procedural analysis.
// FIXME: We can probably do better for const versus non-const methods.
if (const MemRegion *Callee = Call.getCXXCallee().getAsRegion())
RegionsToInvalidate.push_back(Callee);
} else if (Call.isFunctionCall()) {
// Block calls invalidate all captured-by-reference values.
if (const MemRegion *Callee = Call.getFunctionCallee().getAsRegion()) {
if (isa<BlockDataRegion>(Callee))
RegionsToInvalidate.push_back(Callee);
}
}
for (unsigned idx = 0, e = Call.getNumArgs(); idx != e; ++idx) {
SVal V = Call.getArgSVal(idx);
// If we are passing a location wrapped as an integer, unwrap it and
// invalidate the values referred by the location.
if (nonloc::LocAsInteger *Wrapped = dyn_cast<nonloc::LocAsInteger>(&V))
V = Wrapped->getLoc();
else if (!isa<Loc>(V))
continue;
if (const MemRegion *R = V.getAsRegion()) {
// Invalidate the value of the variable passed by reference.
// Are we dealing with an ElementRegion? If the element type is
// a basic integer type (e.g., char, int) and the underying region
// is a variable region then strip off the ElementRegion.
// FIXME: We really need to think about this for the general case
// as sometimes we are reasoning about arrays and other times
// about (char*), etc., is just a form of passing raw bytes.
// e.g., void *p = alloca(); foo((char*)p);
if (const ElementRegion *ER = dyn_cast<ElementRegion>(R)) {
// Checking for 'integral type' is probably too promiscuous, but
// we'll leave it in for now until we have a systematic way of
// handling all of these cases. Eventually we need to come up
// with an interface to StoreManager so that this logic can be
// approriately delegated to the respective StoreManagers while
// still allowing us to do checker-specific logic (e.g.,
// invalidating reference counts), probably via callbacks.
if (ER->getElementType()->isIntegralOrEnumerationType()) {
const MemRegion *superReg = ER->getSuperRegion();
if (isa<VarRegion>(superReg) || isa<FieldRegion>(superReg) ||
isa<ObjCIvarRegion>(superReg))
R = cast<TypedRegion>(superReg);
}
// FIXME: What about layers of ElementRegions?
}
// Mark this region for invalidation. We batch invalidate regions
// below for efficiency.
RegionsToInvalidate.push_back(R);
} else {
// Nuke all other arguments passed by reference.
// FIXME: is this necessary or correct? This handles the non-Region
// cases. Is it ever valid to store to these?
State = State->unbindLoc(cast<Loc>(V));
}
}
// Invalidate designated regions using the batch invalidation API.
// FIXME: We can have collisions on the conjured symbol if the
// expression *I also creates conjured symbols. We probably want
// to identify conjured symbols by an expression pair: the enclosing
// expression (the context) and the expression itself. This should
// disambiguate conjured symbols.
assert(Builder && "Invalidating arguments outside of a statement context");
unsigned Count = Builder->getCurrentBlockCount();
StoreManager::InvalidatedSymbols IS;
// NOTE: Even if RegionsToInvalidate is empty, we may still invalidate
// global variables.
return State->invalidateRegions(RegionsToInvalidate,
Call.getOriginExpr(), Count,
&IS, doesInvalidateGlobals(Call));
}
void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred,
ExplodedNodeSet &dst) {
// Perform the previsit of the CallExpr.
@ -108,16 +203,19 @@ void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred,
unsigned Count = Builder.getCurrentBlockCount();
SVal RetVal = SVB.getConjuredSymbolVal(0, CE, ResultTy, Count);
// Generate a new ExplodedNode with the return value set.
// Generate a new state with the return value set.
state = state->BindExpr(CE, RetVal);
Pred = Builder.generateNode(CE, state, Pred);
// Invalidate the arguments.
const LocationContext *LC = Pred->getLocationContext();
state = Eng.invalidateArguments(state, CallOrObjCMessage(CE, state), LC);
// Then handle everything else.
unsigned oldSize = Dst.size();
SaveOr OldHasGen(Builder.hasGeneratedNode);
// Dispatch to transfer function logic to handle the rest of the call.
Eng.getTF().evalCall(Dst, Eng, Builder, CE, L, Pred);
//Eng.getTF().evalCall(Dst, Eng, Builder, CE, L, Pred);
// Handle the case where no nodes where generated. Auto-generate that
// contains the updated state if we aren't generating sinks.

View File

@ -273,7 +273,12 @@ void ExprEngine::evalObjCMessage(ExplodedNodeSet &Dst, const ObjCMessage &msg,
// Bind the return value.
state = state->BindExpr(currentStmt, ReturnValue);
// Invalidate the arguments (and the receiver)
const LocationContext *LC = Pred->getLocationContext();
state = invalidateArguments(state, CallOrObjCMessage(msg, state), LC);
Builder->MakeNode(Dst, msg.getOriginExpr(), Pred, state);
// Now we can handle the other aspects of the message.
getTF().evalObjCMessage(Dst, *this, *Builder, msg, Pred, state);
//getTF().evalObjCMessage(Dst, *this, *Builder, msg, Pred, state);
}

View File

@ -150,3 +150,9 @@ SVal CallOrObjCMessage::getCXXCallee() const {
cast<CXXMemberCallExpr>(ActualCall)->getImplicitObjectArgument();
return State->getSVal(callee);
}
SVal
CallOrObjCMessage::getInstanceMessageReceiver(const LocationContext *LC) const {
assert(isObjCMessage());
return Msg.getInstanceReceiverSVal(State, LC);
}