[analyzer] Track runtime types represented by Obj-C Class objects

Summary:
Objective-C Class objects can be used to do a dynamic dispatch on
class methods. The analyzer had a few places where we tried to overcome
the dynamic nature of it and still guess the actual function that
is being called. That was done mostly using some simple heuristics
covering the most widespread cases (e.g. [[self class] classmethod]).
This solution introduces a way to track types represented by Class
objects and work with that instead of direct AST matching.

rdar://problem/50739539

Differential Revision: https://reviews.llvm.org/D78286
This commit is contained in:
Valeriy Savchenko 2020-04-22 18:15:03 +03:00
parent e018b8bbb0
commit 239c53b72b
15 changed files with 701 additions and 313 deletions

View File

@ -1115,9 +1115,6 @@ public:
/// Returns the value of the receiver at the time of this call. /// Returns the value of the receiver at the time of this call.
SVal getReceiverSVal() const; SVal getReceiverSVal() const;
/// Return the value of 'self' if available.
SVal getSelfSVal() const;
/// Get the interface for the receiver. /// Get the interface for the receiver.
/// ///
/// This works whether this is an instance message or a class message. /// This works whether this is an instance message or a class message.

View File

@ -36,6 +36,10 @@ DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR);
const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State, const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State,
const MemRegion *MR); const MemRegion *MR);
/// Get dynamic type information stored in a class object represented by \p Sym.
DynamicTypeInfo getClassObjectDynamicTypeInfo(ProgramStateRef State,
SymbolRef Sym);
/// Get dynamic cast information from \p CastFromTy to \p CastToTy of \p MR. /// Get dynamic cast information from \p CastFromTy to \p CastToTy of \p MR.
const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State,
const MemRegion *MR, const MemRegion *MR,
@ -50,6 +54,16 @@ ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR,
ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR,
QualType NewTy, bool CanBeSubClassed = true); QualType NewTy, bool CanBeSubClassed = true);
/// Set constraint on a type contained in a class object; return the new state.
ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State,
SymbolRef Sym,
DynamicTypeInfo NewTy);
/// Set constraint on a type contained in a class object; return the new state.
ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State,
SymbolRef Sym, QualType NewTy,
bool CanBeSubClassed = true);
/// Set dynamic type and cast information of the region; return the new state. /// Set dynamic type and cast information of the region; return the new state.
ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State,
const MemRegion *MR, const MemRegion *MR,
@ -63,6 +77,10 @@ ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR);
/// Removes the dead cast informations from \p State. /// Removes the dead cast informations from \p State.
ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR); ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR);
/// Removes the dead Class object type informations from \p State.
ProgramStateRef removeDeadClassObjectTypes(ProgramStateRef State,
SymbolReaper &SR);
void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State,
const char *NL = "\n", unsigned int Space = 0, const char *NL = "\n", unsigned int Space = 0,
bool IsDot = false); bool IsDot = false);

View File

@ -33,6 +33,8 @@ public:
/// Returns the currently inferred upper bound on the runtime type. /// Returns the currently inferred upper bound on the runtime type.
QualType getType() const { return DynTy; } QualType getType() const { return DynTy; }
operator bool() const { return isValid(); }
bool operator==(const DynamicTypeInfo &RHS) const { bool operator==(const DynamicTypeInfo &RHS) const {
return DynTy == RHS.DynTy && CanBeASubClass == RHS.CanBeASubClass; return DynTy == RHS.DynTy && CanBeASubClass == RHS.CanBeASubClass;
} }

View File

@ -298,6 +298,9 @@ public:
LLVM_NODISCARD ProgramStateRef enterStackFrame( LLVM_NODISCARD ProgramStateRef enterStackFrame(
const CallEvent &Call, const StackFrameContext *CalleeCtx) const; const CallEvent &Call, const StackFrameContext *CalleeCtx) const;
/// Return the value of 'self' if available in the given context.
SVal getSelfSVal(const LocationContext *LC) const;
/// Get the lvalue for a base class object reference. /// Get the lvalue for a base class object reference.
Loc getLValue(const CXXBaseSpecifier &BaseSpec, const SubRegion *Super) const; Loc getLValue(const CXXBaseSpecifier &BaseSpec, const SubRegion *Super) const;

View File

@ -30,7 +30,7 @@ using namespace clang;
using namespace ento; using namespace ento;
namespace { namespace {
class CastValueChecker : public Checker<eval::Call> { class CastValueChecker : public Checker<check::DeadSymbols, eval::Call> {
enum class CallKind { Function, Method, InstanceOf }; enum class CallKind { Function, Method, InstanceOf };
using CastCheck = using CastCheck =
@ -51,6 +51,7 @@ public:
// 1) isa: The parameter is non-null, returns boolean. // 1) isa: The parameter is non-null, returns boolean.
// 2) isa_and_nonnull: The parameter is null or non-null, returns boolean. // 2) isa_and_nonnull: The parameter is null or non-null, returns boolean.
bool evalCall(const CallEvent &Call, CheckerContext &C) const; bool evalCall(const CallEvent &Call, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
private: private:
// These are known in the LLVM project. The pairs are in the following form: // These are known in the LLVM project. The pairs are in the following form:
@ -432,6 +433,11 @@ bool CastValueChecker::evalCall(const CallEvent &Call,
return true; return true;
} }
void CastValueChecker::checkDeadSymbols(SymbolReaper &SR,
CheckerContext &C) const {
C.addTransition(removeDeadCasts(C.getState(), SR));
}
void ento::registerCastValueChecker(CheckerManager &Mgr) { void ento::registerCastValueChecker(CheckerManager &Mgr) {
Mgr.registerChecker<CastValueChecker>(); Mgr.registerChecker<CastValueChecker>();
} }

View File

@ -109,11 +109,127 @@ public:
/// This value is set to true, when the Generics checker is turned on. /// This value is set to true, when the Generics checker is turned on.
DefaultBool CheckGenerics; DefaultBool CheckGenerics;
}; };
bool isObjCClassType(QualType Type) {
if (const auto *PointerType = dyn_cast<ObjCObjectPointerType>(Type)) {
return PointerType->getObjectType()->isObjCClass();
}
return false;
}
struct RuntimeType {
const ObjCObjectType *Type = nullptr;
bool Precise = false;
operator bool() const { return Type != nullptr; }
};
RuntimeType inferReceiverType(const ObjCMethodCall &Message,
CheckerContext &C) {
const ObjCMessageExpr *MessageExpr = Message.getOriginExpr();
// Check if we can statically infer the actual type precisely.
//
// 1. Class is written directly in the message:
// \code
// [ActualClass classMethod];
// \endcode
if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class) {
return {MessageExpr->getClassReceiver()->getAs<ObjCObjectType>(),
/*Precise=*/true};
}
// 2. Receiver is 'super' from a class method (a.k.a 'super' is a
// class object).
// \code
// [super classMethod];
// \endcode
if (MessageExpr->getReceiverKind() == ObjCMessageExpr::SuperClass) {
return {MessageExpr->getSuperType()->getAs<ObjCObjectType>(),
/*Precise=*/true};
}
// 3. Receiver is 'super' from an instance method (a.k.a 'super' is an
// instance of a super class).
// \code
// [super instanceMethod];
// \encode
if (MessageExpr->getReceiverKind() == ObjCMessageExpr::SuperInstance) {
if (const auto *ObjTy =
MessageExpr->getSuperType()->getAs<ObjCObjectPointerType>())
return {ObjTy->getObjectType(), /*Precise=*/true};
}
const Expr *RecE = MessageExpr->getInstanceReceiver();
if (!RecE)
return {};
// Otherwise, let's try to get type information from our estimations of
// runtime types.
QualType InferredType;
SVal ReceiverSVal = C.getSVal(RecE);
ProgramStateRef State = C.getState();
if (const MemRegion *ReceiverRegion = ReceiverSVal.getAsRegion()) {
if (DynamicTypeInfo DTI = getDynamicTypeInfo(State, ReceiverRegion)) {
InferredType = DTI.getType().getCanonicalType();
}
}
if (SymbolRef ReceiverSymbol = ReceiverSVal.getAsSymbol()) {
if (InferredType.isNull()) {
InferredType = ReceiverSymbol->getType();
}
// If receiver is a Class object, we want to figure out the type it
// represents.
if (isObjCClassType(InferredType)) {
// We actually might have some info on what type is contained in there.
if (DynamicTypeInfo DTI =
getClassObjectDynamicTypeInfo(State, ReceiverSymbol)) {
// Types in Class objects can be ONLY Objective-C types
return {cast<ObjCObjectType>(DTI.getType()), !DTI.canBeASubClass()};
}
SVal SelfSVal = State->getSelfSVal(C.getLocationContext());
// Another way we can guess what is in Class object, is when it is a
// 'self' variable of the current class method.
if (ReceiverSVal == SelfSVal) {
// In this case, we should return the type of the enclosing class
// declaration.
if (const ObjCMethodDecl *MD =
dyn_cast<ObjCMethodDecl>(C.getStackFrame()->getDecl()))
if (const ObjCObjectType *ObjTy = dyn_cast<ObjCObjectType>(
MD->getClassInterface()->getTypeForDecl()))
return {ObjTy};
}
}
}
// Unfortunately, it seems like we have no idea what that type is.
if (InferredType.isNull()) {
return {};
}
// We can end up here if we got some dynamic type info and the
// receiver is not one of the known Class objects.
if (const auto *ReceiverInferredType =
dyn_cast<ObjCObjectPointerType>(InferredType)) {
return {ReceiverInferredType->getObjectType()};
}
// Any other type (like 'Class') is not really useful at this point.
return {};
}
} // end anonymous namespace } // end anonymous namespace
void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR, void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR,
CheckerContext &C) const { CheckerContext &C) const {
ProgramStateRef State = removeDeadTypes(C.getState(), SR); ProgramStateRef State = removeDeadTypes(C.getState(), SR);
State = removeDeadClassObjectTypes(State, SR);
MostSpecializedTypeArgsMapTy TyArgMap = MostSpecializedTypeArgsMapTy TyArgMap =
State->get<MostSpecializedTypeArgsMap>(); State->get<MostSpecializedTypeArgsMap>();
@ -209,12 +325,21 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call,
case OMF_alloc: case OMF_alloc:
case OMF_new: { case OMF_new: {
// Get the type of object that will get created. // Get the type of object that will get created.
const ObjCMessageExpr *MsgE = Msg->getOriginExpr(); RuntimeType ObjTy = inferReceiverType(*Msg, C);
const ObjCObjectType *ObjTy = getObjectTypeForAllocAndNew(MsgE, C);
if (!ObjTy) if (!ObjTy)
return; return;
QualType DynResTy = QualType DynResTy =
C.getASTContext().getObjCObjectPointerType(QualType(ObjTy, 0)); C.getASTContext().getObjCObjectPointerType(QualType(ObjTy.Type, 0));
// We used to assume that whatever type we got from inferring the
// type is actually precise (and it is not exactly correct).
// A big portion of the existing behavior depends on that assumption
// (e.g. certain inlining won't take place). For this reason, we don't
// use ObjTy.Precise flag here.
//
// TODO: We should mitigate this problem some time in the future
// and replace hardcoded 'false' with '!ObjTy.Precise'.
C.addTransition(setDynamicTypeInfo(State, RetReg, DynResTy, false)); C.addTransition(setDynamicTypeInfo(State, RetReg, DynResTy, false));
break; break;
} }
@ -303,40 +428,6 @@ void DynamicTypePropagation::checkPostStmt(const CXXNewExpr *NewE,
/*CanBeSubClassed=*/false)); /*CanBeSubClassed=*/false));
} }
const ObjCObjectType *
DynamicTypePropagation::getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE,
CheckerContext &C) const {
if (MsgE->getReceiverKind() == ObjCMessageExpr::Class) {
if (const ObjCObjectType *ObjTy
= MsgE->getClassReceiver()->getAs<ObjCObjectType>())
return ObjTy;
}
if (MsgE->getReceiverKind() == ObjCMessageExpr::SuperClass) {
if (const ObjCObjectType *ObjTy
= MsgE->getSuperType()->getAs<ObjCObjectType>())
return ObjTy;
}
const Expr *RecE = MsgE->getInstanceReceiver();
if (!RecE)
return nullptr;
RecE= RecE->IgnoreParenImpCasts();
if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(RecE)) {
const StackFrameContext *SFCtx = C.getStackFrame();
// Are we calling [self alloc]? If this is self, get the type of the
// enclosing ObjC class.
if (DRE->getDecl() == SFCtx->getSelfDecl()) {
if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(SFCtx->getDecl()))
if (const ObjCObjectType *ObjTy =
dyn_cast<ObjCObjectType>(MD->getClassInterface()->getTypeForDecl()))
return ObjTy;
}
}
return nullptr;
}
// Return a better dynamic type if one can be derived from the cast. // Return a better dynamic type if one can be derived from the cast.
// Compare the current dynamic type of the region and the new type to which we // Compare the current dynamic type of the region and the new type to which we
// are casting. If the new type is lower in the inheritance hierarchy, pick it. // are casting. If the new type is lower in the inheritance hierarchy, pick it.
@ -821,25 +912,56 @@ void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M,
Selector Sel = MessageExpr->getSelector(); Selector Sel = MessageExpr->getSelector();
ProgramStateRef State = C.getState(); ProgramStateRef State = C.getState();
// Inference for class variables.
// We are only interested in cases where the class method is invoked on a // Here we try to propagate information on Class objects.
// class. This method is provided by the runtime and available on all classes. if (Sel.getAsString() == "class") {
if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class && // We try to figure out the type from the receiver of the 'class' message.
Sel.getAsString() == "class") { if (RuntimeType ReceiverRuntimeType = inferReceiverType(M, C)) {
QualType ReceiverType = MessageExpr->getClassReceiver();
const auto *ReceiverClassType = ReceiverType->castAs<ObjCObjectType>(); ReceiverRuntimeType.Type->getSuperClassType();
if (!ReceiverClassType->isSpecialized()) QualType ReceiverClassType(ReceiverRuntimeType.Type, 0);
// We want to consider only precise information on generics.
if (ReceiverRuntimeType.Type->isSpecialized() &&
ReceiverRuntimeType.Precise) {
QualType ReceiverClassPointerType =
C.getASTContext().getObjCObjectPointerType(ReceiverClassType);
const auto *InferredType =
ReceiverClassPointerType->castAs<ObjCObjectPointerType>();
State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType);
}
// Constrain the resulting class object to the inferred type.
State = setClassObjectDynamicTypeInfo(State, RetSym, ReceiverClassType,
!ReceiverRuntimeType.Precise);
C.addTransition(State);
return; return;
}
}
QualType ReceiverClassPointerType = if (Sel.getAsString() == "superclass") {
C.getASTContext().getObjCObjectPointerType( // We try to figure out the type from the receiver of the 'superclass'
QualType(ReceiverClassType, 0)); // message.
const auto *InferredType = if (RuntimeType ReceiverRuntimeType = inferReceiverType(M, C)) {
ReceiverClassPointerType->castAs<ObjCObjectPointerType>();
State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType); // Result type would be a super class of the receiver's type.
C.addTransition(State); QualType ReceiversSuperClass =
return; ReceiverRuntimeType.Type->getSuperClassType();
// Check if it really had super class.
//
// TODO: we can probably pay closer attention to cases when the class
// object can be 'nil' as the result of such message.
if (!ReceiversSuperClass.isNull()) {
// Constrain the resulting class object to the inferred type.
State = setClassObjectDynamicTypeInfo(
State, RetSym, ReceiversSuperClass, !ReceiverRuntimeType.Precise);
C.addTransition(State);
}
return;
}
} }
// Tracking for return types. // Tracking for return types.

View File

@ -116,13 +116,14 @@ void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
return; return;
ProgramStateRef State = C.getState(); ProgramStateRef State = C.getState();
SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol(); const LocationContext *LC = C.getLocationContext();
assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?"); SymbolRef SelfSymbol = State->getSelfSVal(LC).getAsSymbol();
assert(SelfSymbol && "No receiver symbol at call to [super dealloc]?");
// We add this transition in checkPostObjCMessage to avoid warning when // We add this transition in checkPostObjCMessage to avoid warning when
// we inline a call to [super dealloc] where the inlined call itself // we inline a call to [super dealloc] where the inlined call itself
// calls [super dealloc]. // calls [super dealloc].
State = State->add<CalledSuperDealloc>(ReceiverSymbol); State = State->add<CalledSuperDealloc>(SelfSymbol);
C.addTransition(State); C.addTransition(State);
} }

View File

@ -972,14 +972,6 @@ void ObjCMethodCall::getExtraInvalidatedValues(
Values.push_back(getReceiverSVal()); Values.push_back(getReceiverSVal());
} }
SVal ObjCMethodCall::getSelfSVal() const {
const LocationContext *LCtx = getLocationContext();
const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl();
if (!SelfDecl)
return SVal();
return getState()->getSVal(getState()->getRegion(SelfDecl, LCtx));
}
SVal ObjCMethodCall::getReceiverSVal() const { SVal ObjCMethodCall::getReceiverSVal() const {
// FIXME: Is this the best way to handle class receivers? // FIXME: Is this the best way to handle class receivers?
if (!isInstanceMessage()) if (!isInstanceMessage())
@ -991,7 +983,7 @@ SVal ObjCMethodCall::getReceiverSVal() const {
// An instance message with no expression means we are sending to super. // An instance message with no expression means we are sending to super.
// In this case the object reference is the same as 'self'. // In this case the object reference is the same as 'self'.
assert(getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance); assert(getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance);
SVal SelfVal = getSelfSVal(); SVal SelfVal = getState()->getSelfSVal(getLocationContext());
assert(SelfVal.isValid() && "Calling super but not in ObjC method"); assert(SelfVal.isValid() && "Calling super but not in ObjC method");
return SelfVal; return SelfVal;
} }
@ -1005,8 +997,9 @@ bool ObjCMethodCall::isReceiverSelfOrSuper() const {
return false; return false;
SVal RecVal = getSVal(getOriginExpr()->getInstanceReceiver()); SVal RecVal = getSVal(getOriginExpr()->getInstanceReceiver());
SVal SelfVal = getState()->getSelfSVal(getLocationContext());
return (RecVal == getSelfSVal()); return (RecVal == SelfVal);
} }
SourceRange ObjCMethodCall::getSourceRange() const { SourceRange ObjCMethodCall::getSourceRange() const {
@ -1173,23 +1166,75 @@ static const ObjCMethodDecl *findDefiningRedecl(const ObjCMethodDecl *MD) {
return MD; return MD;
} }
static bool isCallToSelfClass(const ObjCMessageExpr *ME) { struct PrivateMethodKey {
const Expr* InstRec = ME->getInstanceReceiver(); const ObjCInterfaceDecl *Interface;
if (!InstRec) Selector LookupSelector;
return false; bool IsClassMethod;
const auto *InstRecIg = dyn_cast<DeclRefExpr>(InstRec->IgnoreParenImpCasts()); };
// Check that receiver is called 'self'. template <> struct llvm::DenseMapInfo<PrivateMethodKey> {
if (!InstRecIg || !InstRecIg->getFoundDecl() || using InterfaceInfo = DenseMapInfo<const ObjCInterfaceDecl *>;
!InstRecIg->getFoundDecl()->getName().equals("self")) using SelectorInfo = DenseMapInfo<Selector>;
return false;
// Check that the method name is 'class'. static inline PrivateMethodKey getEmptyKey() {
if (ME->getSelector().getNumArgs() != 0 || return {InterfaceInfo::getEmptyKey(), SelectorInfo::getEmptyKey(), false};
!ME->getSelector().getNameForSlot(0).equals("class")) }
return false;
return true; static inline PrivateMethodKey getTombstoneKey() {
return {InterfaceInfo::getTombstoneKey(), SelectorInfo::getTombstoneKey(),
true};
}
static unsigned getHashValue(const PrivateMethodKey &Key) {
return llvm::hash_combine(
llvm::hash_code(InterfaceInfo::getHashValue(Key.Interface)),
llvm::hash_code(SelectorInfo::getHashValue(Key.LookupSelector)),
Key.IsClassMethod);
}
static bool isEqual(const PrivateMethodKey &LHS,
const PrivateMethodKey &RHS) {
return InterfaceInfo::isEqual(LHS.Interface, RHS.Interface) &&
SelectorInfo::isEqual(LHS.LookupSelector, RHS.LookupSelector) &&
LHS.IsClassMethod == RHS.IsClassMethod;
}
};
const ObjCMethodDecl *
lookupRuntimeDefinition(const ObjCInterfaceDecl *Interface,
Selector LookupSelector, bool InstanceMethod) {
// Repeatedly calling lookupPrivateMethod() is expensive, especially
// when in many cases it returns null. We cache the results so
// that repeated queries on the same ObjCIntefaceDecl and Selector
// don't incur the same cost. On some test cases, we can see the
// same query being issued thousands of times.
//
// NOTE: This cache is essentially a "global" variable, but it
// only gets lazily created when we get here. The value of the
// cache probably comes from it being global across ExprEngines,
// where the same queries may get issued. If we are worried about
// concurrency, or possibly loading/unloading ASTs, etc., we may
// need to revisit this someday. In terms of memory, this table
// stays around until clang quits, which also may be bad if we
// need to release memory.
using PrivateMethodCache =
llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>;
static PrivateMethodCache PMC;
Optional<const ObjCMethodDecl *> &Val =
PMC[{Interface, LookupSelector, InstanceMethod}];
// Query lookupPrivateMethod() if the cache does not hit.
if (!Val.hasValue()) {
Val = Interface->lookupPrivateMethod(LookupSelector, InstanceMethod);
if (!*Val) {
// Query 'lookupMethod' as a backup.
Val = Interface->lookupMethod(LookupSelector, InstanceMethod);
}
}
return Val.getValue();
} }
RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
@ -1199,8 +1244,9 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
if (E->isInstanceMessage()) { if (E->isInstanceMessage()) {
// Find the receiver type. // Find the receiver type.
const ObjCObjectPointerType *ReceiverT = nullptr; const ObjCObjectType *ReceiverT = nullptr;
bool CanBeSubClassed = false; bool CanBeSubClassed = false;
bool LookingForInstanceMethod = true;
QualType SupersType = E->getSuperType(); QualType SupersType = E->getSuperType();
const MemRegion *Receiver = nullptr; const MemRegion *Receiver = nullptr;
@ -1208,7 +1254,7 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
// The receiver is guaranteed to be 'super' in this case. // The receiver is guaranteed to be 'super' in this case.
// Super always means the type of immediate predecessor to the method // Super always means the type of immediate predecessor to the method
// where the call occurs. // where the call occurs.
ReceiverT = cast<ObjCObjectPointerType>(SupersType); ReceiverT = cast<ObjCObjectPointerType>(SupersType)->getObjectType();
} else { } else {
Receiver = getReceiverSVal().getAsRegion(); Receiver = getReceiverSVal().getAsRegion();
if (!Receiver) if (!Receiver)
@ -1223,100 +1269,59 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
QualType DynType = DTI.getType(); QualType DynType = DTI.getType();
CanBeSubClassed = DTI.canBeASubClass(); CanBeSubClassed = DTI.canBeASubClass();
ReceiverT = dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType());
if (ReceiverT && CanBeSubClassed) const auto *ReceiverDynT =
if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType());
if (!canBeOverridenInSubclass(IDecl, Sel))
CanBeSubClassed = false;
}
// Handle special cases of '[self classMethod]' and if (ReceiverDynT) {
// '[[self class] classMethod]', which are treated by the compiler as ReceiverT = ReceiverDynT->getObjectType();
// instance (not class) messages. We will statically dispatch to those.
if (auto *PT = dyn_cast_or_null<ObjCObjectPointerType>(ReceiverT)) {
// For [self classMethod], return the compiler visible declaration.
if (PT->getObjectType()->isObjCClass() &&
Receiver == getSelfSVal().getAsRegion())
return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl()));
// Similarly, handle [[self class] classMethod]. // It can be actually class methods called with Class object as a
// TODO: We are currently doing a syntactic match for this pattern with is // receiver. This type of messages is treated by the compiler as
// limiting as the test cases in Analysis/inlining/InlineObjCClassMethod.m // instance (not class).
// shows. A better way would be to associate the meta type with the symbol if (ReceiverT->isObjCClass()) {
// using the dynamic type info tracking and use it here. We can add a new
// SVal for ObjC 'Class' values that know what interface declaration they SVal SelfVal = getState()->getSelfSVal(getLocationContext());
// come from. Then 'self' in a class method would be filled in with // For [self classMethod], return compiler visible declaration.
// something meaningful in ObjCMethodCall::getReceiverSVal() and we could if (Receiver == SelfVal.getAsRegion()) {
// do proper dynamic dispatch for class methods just like we do for
// instance methods now.
if (E->getInstanceReceiver())
if (const auto *M = dyn_cast<ObjCMessageExpr>(E->getInstanceReceiver()))
if (isCallToSelfClass(M))
return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl())); return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl()));
}
// Otherwise, let's check if we know something about the type
// inside of this class object.
if (SymbolRef ReceiverSym = getReceiverSVal().getAsSymbol()) {
DynamicTypeInfo DTI =
getClassObjectDynamicTypeInfo(getState(), ReceiverSym);
if (DTI.isValid()) {
// Let's use this type for lookup.
ReceiverT =
cast<ObjCObjectType>(DTI.getType().getCanonicalType());
CanBeSubClassed = DTI.canBeASubClass();
// And it should be a class method instead.
LookingForInstanceMethod = false;
}
}
}
if (CanBeSubClassed)
if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface())
// Even if `DynamicTypeInfo` told us that it can be
// not necessarily this type, but its descendants, we still want
// to check again if this selector can be actually overridden.
CanBeSubClassed = canBeOverridenInSubclass(IDecl, Sel);
}
} }
// Lookup the instance method implementation. // Lookup the instance method implementation.
if (ReceiverT) if (ReceiverT)
if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) { if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) {
// Repeatedly calling lookupPrivateMethod() is expensive, especially const ObjCMethodDecl *MD =
// when in many cases it returns null. We cache the results so lookupRuntimeDefinition(IDecl, Sel, LookingForInstanceMethod);
// that repeated queries on the same ObjCIntefaceDecl and Selector
// don't incur the same cost. On some test cases, we can see the
// same query being issued thousands of times.
//
// NOTE: This cache is essentially a "global" variable, but it
// only gets lazily created when we get here. The value of the
// cache probably comes from it being global across ExprEngines,
// where the same queries may get issued. If we are worried about
// concurrency, or possibly loading/unloading ASTs, etc., we may
// need to revisit this someday. In terms of memory, this table
// stays around until clang quits, which also may be bad if we
// need to release memory.
using PrivateMethodKey = std::pair<const ObjCInterfaceDecl *, Selector>;
using PrivateMethodCache =
llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>;
static PrivateMethodCache PMC;
Optional<const ObjCMethodDecl *> &Val = PMC[std::make_pair(IDecl, Sel)];
// Query lookupPrivateMethod() if the cache does not hit.
if (!Val.hasValue()) {
Val = IDecl->lookupPrivateMethod(Sel);
// If the method is a property accessor, we should try to "inline" it
// even if we don't actually have an implementation.
if (!*Val)
if (const ObjCMethodDecl *CompileTimeMD = E->getMethodDecl())
if (CompileTimeMD->isPropertyAccessor()) {
if (!CompileTimeMD->getSelfDecl() &&
isa<ObjCCategoryDecl>(CompileTimeMD->getDeclContext())) {
// If the method is an accessor in a category, and it doesn't
// have a self declaration, first
// try to find the method in a class extension. This
// works around a bug in Sema where multiple accessors
// are synthesized for properties in class
// extensions that are redeclared in a category and the
// the implicit parameters are not filled in for
// the method on the category.
// This ensures we find the accessor in the extension, which
// has the implicit parameters filled in.
auto *ID = CompileTimeMD->getClassInterface();
for (auto *CatDecl : ID->visible_extensions()) {
Val = CatDecl->getMethod(Sel,
CompileTimeMD->isInstanceMethod());
if (*Val)
break;
}
}
if (!*Val)
Val = IDecl->lookupInstanceMethod(Sel);
}
}
const ObjCMethodDecl *MD = Val.getValue();
if (MD && !MD->hasBody()) if (MD && !MD->hasBody())
MD = MD->getCanonicalDecl(); MD = MD->getCanonicalDecl();
if (CanBeSubClassed) if (CanBeSubClassed)
return RuntimeDefinition(MD, Receiver); return RuntimeDefinition(MD, Receiver);
else else

View File

@ -34,6 +34,10 @@ REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(CastSet, clang::ento::DynamicCastInfo)
REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *, REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *,
CastSet) CastSet)
// A map from Class object symbols to the most likely pointed-to type.
REGISTER_MAP_WITH_PROGRAMSTATE(DynamicClassObjectMap, clang::ento::SymbolRef,
clang::ento::DynamicTypeInfo)
namespace clang { namespace clang {
namespace ento { namespace ento {
@ -76,6 +80,12 @@ const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State,
return nullptr; return nullptr;
} }
DynamicTypeInfo getClassObjectDynamicTypeInfo(ProgramStateRef State,
SymbolRef Sym) {
const DynamicTypeInfo *DTI = State->get<DynamicClassObjectMap>(Sym);
return DTI ? *DTI : DynamicTypeInfo{};
}
ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR,
DynamicTypeInfo NewTy) { DynamicTypeInfo NewTy) {
State = State->set<DynamicTypeMap>(MR->StripCasts(), NewTy); State = State->set<DynamicTypeMap>(MR->StripCasts(), NewTy);
@ -118,111 +128,165 @@ ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State,
return State; return State;
} }
ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State,
SymbolRef Sym,
DynamicTypeInfo NewTy) {
State = State->set<DynamicClassObjectMap>(Sym, NewTy);
return State;
}
ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State,
SymbolRef Sym, QualType NewTy,
bool CanBeSubClassed) {
return setClassObjectDynamicTypeInfo(State, Sym,
DynamicTypeInfo(NewTy, CanBeSubClassed));
}
static bool isLive(SymbolReaper &SR, const MemRegion *MR) {
return SR.isLiveRegion(MR);
}
static bool isLive(SymbolReaper &SR, SymbolRef Sym) { return SR.isLive(Sym); }
template <typename MapTy> template <typename MapTy>
ProgramStateRef removeDead(ProgramStateRef State, const MapTy &Map, static ProgramStateRef removeDeadImpl(ProgramStateRef State, SymbolReaper &SR) {
SymbolReaper &SR) { const auto &Map = State->get<MapTy>();
for (const auto &Elem : Map) for (const auto &Elem : Map)
if (!SR.isLiveRegion(Elem.first)) if (!isLive(SR, Elem.first))
State = State->remove<DynamicCastMap>(Elem.first); State = State->remove<MapTy>(Elem.first);
return State; return State;
} }
ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR) { ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR) {
return removeDead(State, State->get<DynamicTypeMap>(), SR); return removeDeadImpl<DynamicTypeMap>(State, SR);
} }
ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) { ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) {
return removeDead(State, State->get<DynamicCastMap>(), SR); return removeDeadImpl<DynamicCastMap>(State, SR);
}
ProgramStateRef removeDeadClassObjectTypes(ProgramStateRef State,
SymbolReaper &SR) {
return removeDeadImpl<DynamicClassObjectMap>(State, SR);
}
//===----------------------------------------------------------------------===//
// Implementation of the 'printer-to-JSON' function
//===----------------------------------------------------------------------===//
static raw_ostream &printJson(const MemRegion *Region, raw_ostream &Out,
const char *NL, unsigned int Space, bool IsDot) {
return Out << "\"region\": \"" << Region << "\"";
}
static raw_ostream &printJson(const SymExpr *Symbol, raw_ostream &Out,
const char *NL, unsigned int Space, bool IsDot) {
return Out << "\"symbol\": \"" << Symbol << "\"";
}
static raw_ostream &printJson(const DynamicTypeInfo &DTI, raw_ostream &Out,
const char *NL, unsigned int Space, bool IsDot) {
Out << "\"dyn_type\": ";
if (!DTI.isValid()) {
Out << "null";
} else {
QualType ToPrint = DTI.getType();
if (ToPrint->isAnyPointerType())
ToPrint = ToPrint->getPointeeType();
Out << '\"' << ToPrint.getAsString() << "\", \"sub_classable\": "
<< (DTI.canBeASubClass() ? "true" : "false");
}
return Out;
}
static raw_ostream &printJson(const DynamicCastInfo &DCI, raw_ostream &Out,
const char *NL, unsigned int Space, bool IsDot) {
return Out << "\"from\": \"" << DCI.from().getAsString() << "\", \"to\": \""
<< DCI.to().getAsString() << "\", \"kind\": \""
<< (DCI.succeeds() ? "success" : "fail") << "\"";
}
template <class T, class U>
static raw_ostream &printJson(const std::pair<T, U> &Pair, raw_ostream &Out,
const char *NL, unsigned int Space, bool IsDot) {
printJson(Pair.first, Out, NL, Space, IsDot) << ", ";
return printJson(Pair.second, Out, NL, Space, IsDot);
}
template <class ContainerTy>
static raw_ostream &printJsonContainer(const ContainerTy &Container,
raw_ostream &Out, const char *NL,
unsigned int Space, bool IsDot) {
if (Container.isEmpty()) {
return Out << "null";
}
++Space;
Out << '[' << NL;
for (auto I = Container.begin(); I != Container.end(); ++I) {
const auto &Element = *I;
Indent(Out, Space, IsDot) << "{ ";
printJson(Element, Out, NL, Space, IsDot) << " }";
if (std::next(I) != Container.end())
Out << ',';
Out << NL;
}
--Space;
return Indent(Out, Space, IsDot) << "]";
}
static raw_ostream &printJson(const CastSet &Set, raw_ostream &Out,
const char *NL, unsigned int Space, bool IsDot) {
Out << "\"casts\": ";
return printJsonContainer(Set, Out, NL, Space, IsDot);
}
template <class MapTy>
static void printJsonImpl(raw_ostream &Out, ProgramStateRef State,
const char *Name, const char *NL, unsigned int Space,
bool IsDot, bool PrintEvenIfEmpty = true) {
const auto &Map = State->get<MapTy>();
if (Map.isEmpty() && !PrintEvenIfEmpty)
return;
Indent(Out, Space, IsDot) << "\"" << Name << "\": ";
printJsonContainer(Map, Out, NL, Space, IsDot) << "," << NL;
} }
static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State,
const char *NL, unsigned int Space, const char *NL, unsigned int Space,
bool IsDot) { bool IsDot) {
Indent(Out, Space, IsDot) << "\"dynamic_types\": "; printJsonImpl<DynamicTypeMap>(Out, State, "dynamic_types", NL, Space, IsDot);
const DynamicTypeMapTy &Map = State->get<DynamicTypeMap>();
if (Map.isEmpty()) {
Out << "null," << NL;
return;
}
++Space;
Out << '[' << NL;
for (DynamicTypeMapTy::iterator I = Map.begin(); I != Map.end(); ++I) {
const MemRegion *MR = I->first;
const DynamicTypeInfo &DTI = I->second;
Indent(Out, Space, IsDot)
<< "{ \"region\": \"" << MR << "\", \"dyn_type\": ";
if (!DTI.isValid()) {
Out << "null";
} else {
Out << '\"' << DTI.getType()->getPointeeType().getAsString()
<< "\", \"sub_classable\": "
<< (DTI.canBeASubClass() ? "true" : "false");
}
Out << " }";
if (std::next(I) != Map.end())
Out << ',';
Out << NL;
}
--Space;
Indent(Out, Space, IsDot) << "]," << NL;
} }
static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State,
const char *NL, unsigned int Space, const char *NL, unsigned int Space,
bool IsDot) { bool IsDot) {
Indent(Out, Space, IsDot) << "\"dynamic_casts\": "; printJsonImpl<DynamicCastMap>(Out, State, "dynamic_casts", NL, Space, IsDot);
}
const DynamicCastMapTy &Map = State->get<DynamicCastMap>(); static void printClassObjectDynamicTypesJson(raw_ostream &Out,
if (Map.isEmpty()) { ProgramStateRef State,
Out << "null," << NL; const char *NL, unsigned int Space,
return; bool IsDot) {
} // Let's print Class object type information only if we have something
// meaningful to print.
++Space; printJsonImpl<DynamicClassObjectMap>(Out, State, "class_object_types", NL,
Out << '[' << NL; Space, IsDot,
for (DynamicCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { /*PrintEvenIfEmpty=*/false);
const MemRegion *MR = I->first;
const CastSet &Set = I->second;
Indent(Out, Space, IsDot) << "{ \"region\": \"" << MR << "\", \"casts\": ";
if (Set.isEmpty()) {
Out << "null ";
} else {
++Space;
Out << '[' << NL;
for (CastSet::iterator SI = Set.begin(); SI != Set.end(); ++SI) {
Indent(Out, Space, IsDot)
<< "{ \"from\": \"" << SI->from().getAsString() << "\", \"to\": \""
<< SI->to().getAsString() << "\", \"kind\": \""
<< (SI->succeeds() ? "success" : "fail") << "\" }";
if (std::next(SI) != Set.end())
Out << ',';
Out << NL;
}
--Space;
Indent(Out, Space, IsDot) << ']';
}
Out << '}';
if (std::next(I) != Map.end())
Out << ',';
Out << NL;
}
--Space;
Indent(Out, Space, IsDot) << "]," << NL;
} }
void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State,
const char *NL, unsigned int Space, bool IsDot) { const char *NL, unsigned int Space, bool IsDot) {
printDynamicTypesJson(Out, State, NL, Space, IsDot); printDynamicTypesJson(Out, State, NL, Space, IsDot);
printDynamicCastsJson(Out, State, NL, Space, IsDot); printDynamicCastsJson(Out, State, NL, Space, IsDot);
printClassObjectDynamicTypesJson(Out, State, NL, Space, IsDot);
} }
} // namespace ento } // namespace ento

View File

@ -240,6 +240,13 @@ ProgramState::enterStackFrame(const CallEvent &Call,
return makeWithStore(NewStore); return makeWithStore(NewStore);
} }
SVal ProgramState::getSelfSVal(const LocationContext *LCtx) const {
const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl();
if (!SelfDecl)
return SVal();
return getSVal(getRegion(SelfDecl, LCtx));
}
SVal ProgramState::getSValAsScalarOrLoc(const MemRegion *R) const { SVal ProgramState::getSValAsScalarOrLoc(const MemRegion *R) const {
// We only want to do fetches from regions that we can actually bind // We only want to do fetches from regions that we can actually bind
// values. For example, SymbolicRegions of type 'id<...>' cannot // values. For example, SymbolicRegions of type 'id<...>' cannot

View File

@ -37,7 +37,7 @@ void evalNonNullParamNonNullReturn(const Shape *S) {
// CHECK: { "region": "SymRegion{reg_$0<const struct clang::Shape * S>}", "casts": [ // CHECK: { "region": "SymRegion{reg_$0<const struct clang::Shape * S>}", "casts": [
// CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Circle *", "kind": "success" }, // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Circle *", "kind": "success" },
// CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Square *", "kind": "fail" } // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Square *", "kind": "fail" }
// CHECK-NEXT: ]} // CHECK-NEXT: ] }
(void)(1 / !C); (void)(1 / !C);
// expected-note@-1 {{'C' is non-null}} // expected-note@-1 {{'C' is non-null}}

View File

@ -0,0 +1,38 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \
// RUN: -verify %s 2>&1 | FileCheck %s
// expected-no-diagnostics
void clang_analyzer_printState();
@interface NSObject {
}
+ (id)alloc;
+ (Class)class;
- (id)init;
- (Class)class;
@end
@interface Parent : NSObject
@end
@interface Child : Parent
@end
void foo(id A, id B);
@implementation Child
+ (void)test {
id ClassAsID = [self class];
id Object = [[ClassAsID alloc] init];
Class TheSameClass = [Object class];
clang_analyzer_printState();
// CHECK: "class_object_types": [
// CHECK-NEXT: { "symbol": "conj_$[[#]]{Class, LC[[#]], S[[#]], #[[#]]}", "dyn_type": "Child", "sub_classable": true },
// CHECK-NEXT: { "symbol": "conj_$[[#]]{Class, LC[[#]], S[[#]], #[[#]]}", "dyn_type": "Child", "sub_classable": true }
// CHECK-NEXT: ]
// Let's make sure that the information is not GC'd away.
foo(ClassAsID, TheSameClass);
}
@end

View File

@ -6,18 +6,25 @@ void clang_analyzer_eval(int);
// Test inlining of ObjC class methods. // Test inlining of ObjC class methods.
typedef signed char BOOL; typedef signed char BOOL;
#define YES ((BOOL)1)
#define NO ((BOOL)0)
typedef struct objc_class *Class; typedef struct objc_class *Class;
typedef struct objc_object { typedef struct objc_object {
Class isa; Class isa;
} *id; } * id;
@protocol NSObject - (BOOL)isEqual:(id)object; @end @protocol NSObject
- (BOOL)isEqual:(id)object;
@end
@interface NSObject <NSObject> {} @interface NSObject <NSObject> {}
+(id)alloc; + (id)alloc;
-(id)init; + (Class)class;
-(id)autorelease; + (Class)superclass;
-(id)copy; - (id)init;
- (id)autorelease;
- (id)copy;
- (Class)class; - (Class)class;
-(id)retain; - (instancetype)self;
- (id)retain;
@end @end
// Vanila: ObjC class method is called by name. // Vanila: ObjC class method is called by name.
@ -133,10 +140,7 @@ int foo4() {
} }
@end @end
// False negative.
// ObjC class method call through a decl with a known type. // ObjC class method call through a decl with a known type.
// We should be able to track the type of currentClass and inline this call.
// Note, [self class] could be a subclass. Do we still want to inline here? // Note, [self class] could be a subclass. Do we still want to inline here?
@interface MyClassKT : NSObject @interface MyClassKT : NSObject
@end @end
@ -152,7 +156,7 @@ int foo4() {
- (int)testClassMethodByKnownVarDecl { - (int)testClassMethodByKnownVarDecl {
Class currentClass = [self class]; Class currentClass = [self class];
int y = [currentClass getInt]; int y = [currentClass getInt];
return 5/y; // Would be great to get a warning here. return 5 / y; // expected-warning{{Division by zero}}
} }
@end @end
@ -240,37 +244,124 @@ void rdar15037033() {
+(unsigned)returns30; +(unsigned)returns30;
@end @end
@implementation SelfClassTestParent @interface SelfClassTest : SelfClassTestParent
-(unsigned)returns10 { return 100; } - (unsigned)returns10;
+(unsigned)returns20 { return 100; } + (unsigned)returns20;
+(unsigned)returns30 { return 100; } + (unsigned)returns30;
@end @end
@interface SelfClassTest : SelfClassTestParent @implementation SelfClassTestParent
-(unsigned)returns10; - (unsigned)returns10 {
+(unsigned)returns20; return 100;
+(unsigned)returns30; }
+ (unsigned)returns20 {
return 100;
}
+ (unsigned)returns30 {
return 100;
}
- (void)testSelfReassignment {
// Check that we didn't hardcode type for self.
self = [[[SelfClassTest class] alloc] init];
Class actuallyChildClass = [self class];
unsigned result = [actuallyChildClass returns30];
clang_analyzer_eval(result == 30); // expected-warning{{TRUE}}
}
@end @end
@implementation SelfClassTest @implementation SelfClassTest
-(unsigned)returns10 { return 10; } - (unsigned)returns10 {
+(unsigned)returns20 { return 20; } return 10;
+(unsigned)returns30 { return 30; } }
+(void)classMethod { + (unsigned)returns20 {
return 20;
}
+ (unsigned)returns30 {
return 30;
}
+ (BOOL)isClass {
return YES;
}
- (BOOL)isClass {
return NO;
}
+ (SelfClassTest *)create {
return [[self alloc] init];
}
+ (void)classMethod {
unsigned result1 = [self returns20]; unsigned result1 = [self returns20];
clang_analyzer_eval(result1 == 20); // expected-warning{{TRUE}} clang_analyzer_eval(result1 == 20); // expected-warning{{TRUE}}
unsigned result2 = [[self class] returns30]; unsigned result2 = [[self class] returns30];
clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}} clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}}
unsigned result3 = [[super class] returns30]; unsigned result3 = [[super class] returns30];
clang_analyzer_eval(result3 == 100); // expected-warning{{UNKNOWN}} clang_analyzer_eval(result3 == 100); // expected-warning{{TRUE}}
// Check that class info is propagated with data
Class class41 = [self class];
Class class42 = class41;
unsigned result4 = [class42 returns30];
clang_analyzer_eval(result4 == 30); // expected-warning{{TRUE}}
Class class51 = [super class];
Class class52 = class51;
unsigned result5 = [class52 returns30];
clang_analyzer_eval(result5 == 100); // expected-warning{{TRUE}}
} }
-(void)instanceMethod { - (void)instanceMethod {
unsigned result0 = [self returns10]; unsigned result0 = [self returns10];
clang_analyzer_eval(result0 == 10); // expected-warning{{TRUE}} clang_analyzer_eval(result0 == 10); // expected-warning{{TRUE}}
unsigned result2 = [[self class] returns30]; unsigned result2 = [[self class] returns30];
clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}} clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}}
unsigned result3 = [[super class] returns30]; unsigned result3 = [[super class] returns30];
clang_analyzer_eval(result3 == 100); // expected-warning{{UNKNOWN}} clang_analyzer_eval(result3 == 100); // expected-warning{{TRUE}}
// Check that class info is propagated with data
Class class41 = [self class];
Class class42 = class41;
unsigned result4 = [class42 returns30];
clang_analyzer_eval(result4 == 30); // expected-warning{{TRUE}}
Class class51 = [super class];
Class class52 = class51;
unsigned result5 = [class52 returns30];
clang_analyzer_eval(result5 == 100); // expected-warning{{TRUE}}
// Check that we inline class methods when class object is a receiver
Class class6 = [self class];
BOOL calledClassMethod = [class6 isClass];
clang_analyzer_eval(calledClassMethod == YES); // expected-warning{{TRUE}}
// Check that class info is propagated through the 'self' method
Class class71 = [self class];
Class class72 = [class71 self];
unsigned result7 = [class72 returns30];
clang_analyzer_eval(result7 == 30); // expected-warning{{TRUE}}
// Check that 'class' and 'super' info from direct invocation of the
// corresponding class methods is propagated with data
Class class8 = [SelfClassTest class];
unsigned result8 = [class8 returns30];
clang_analyzer_eval(result8 == 30); // expected-warning{{TRUE}}
Class class9 = [SelfClassTest superclass];
unsigned result9 = [class9 returns30];
clang_analyzer_eval(result9 == 100); // expected-warning{{TRUE}}
// Check that we get class from a propagated type
SelfClassTestParent *selfAsParent10 = [[SelfClassTest alloc] init];
Class class10 = [selfAsParent10 class];
unsigned result10 = [class10 returns30];
clang_analyzer_eval(result10 == 30); // expected-warning{{TRUE}}
SelfClassTestParent *selfAsParent11 = [[[self class] alloc] init];
Class class11 = [selfAsParent11 class];
unsigned result11 = [class11 returns30];
clang_analyzer_eval(result11 == 30); // expected-warning{{TRUE}}
} }
@end @end

View File

@ -7,68 +7,67 @@ void clang_analyzer_eval(int);
PublicSubClass2 *getObj(); PublicSubClass2 *getObj();
@implementation PublicParent @implementation PublicParent
- (int) getZeroOverridden { - (int)getZeroOverridden {
return 1; return 1;
} }
- (int) getZero { - (int)getZero {
return 0; return 0;
} }
@end @end
@implementation PublicSubClass2 @implementation PublicSubClass2
- (int) getZeroOverridden { - (int)getZeroOverridden {
return 0; return 0;
} }
/* Test that we get the right type from call to alloc. */ /* Test that we get the right type from call to alloc. */
+ (void) testAllocSelf { + (void)testAllocSelf {
id a = [self alloc]; id a = [self alloc];
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}} clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}}
} }
+ (void)testAllocClass {
+ (void) testAllocClass {
id a = [PublicSubClass2 alloc]; id a = [PublicSubClass2 alloc];
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}} clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}}
} }
+ (void) testAllocSuperOverriden { + (void)testAllocSuperOverriden {
id a = [super alloc]; id a = [super alloc];
// Evaluates to 1 in the parent. // Evaluates to 1 in the parent.
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{FALSE}} clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{FALSE}}
} }
+ (void) testAllocSuper { + (void)testAllocSuper {
id a = [super alloc]; id a = [super alloc];
clang_analyzer_eval([a getZero] == 0); // expected-warning{{TRUE}} clang_analyzer_eval([a getZero] == 0); // expected-warning{{TRUE}}
} }
+ (void) testAllocInit { + (void)testAllocInit {
id a = [[self alloc] init]; id a = [[self alloc] init];
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}} clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}}
} }
+ (void) testNewSelf { + (void)testNewSelf {
id a = [self new]; id a = [self new];
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}} clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}}
} }
// Casting to parent should not pessimize the dynamic type. // Casting to parent should not pessimize the dynamic type.
+ (void) testCastToParent { + (void)testCastToParent {
id a = [[self alloc] init]; id a = [[self alloc] init];
PublicParent *p = a; PublicParent *p = a;
clang_analyzer_eval([p getZeroOverridden] == 0); // expected-warning{{TRUE}} clang_analyzer_eval([p getZeroOverridden] == 0); // expected-warning{{TRUE}}
} }
// The type of parameter gets used. // The type of parameter gets used.
+ (void)testTypeFromParam:(PublicParent*) p { + (void)testTypeFromParam:(PublicParent *)p {
clang_analyzer_eval([p getZero] == 0); // expected-warning{{TRUE}} clang_analyzer_eval([p getZero] == 0); // expected-warning{{TRUE}}
} }
// Test implicit cast. // Test implicit cast.
// Note, in this case, p could also be a subclass of MyParent. // Note, in this case, p could also be a subclass of MyParent.
+ (void) testCastFromId:(id) a { + (void)testCastFromId:(id)a {
PublicParent *p = a; PublicParent *p = a;
clang_analyzer_eval([p getZero] == 0); // expected-warning{{TRUE}} clang_analyzer_eval([p getZero] == 0); // expected-warning{{TRUE}}
} }
@end @end
@ -76,25 +75,27 @@ PublicSubClass2 *getObj();
// TODO: Would be nice to handle the case of dynamically obtained class info // TODO: Would be nice to handle the case of dynamically obtained class info
// as well. We need a MemRegion for class types for this. // as well. We need a MemRegion for class types for this.
int testDynamicClass(BOOL coin) { int testDynamicClass(BOOL coin) {
Class AllocClass = (coin ? [NSObject class] : [PublicSubClass2 class]); Class AllocClass = (coin ? [NSObject class] : [PublicSubClass2 class]);
id x = [[AllocClass alloc] init]; id x = [[AllocClass alloc] init];
if (coin) if (coin)
return [x getZero]; return [x getZero];
return 1; return 1;
} }
@interface UserClass : NSObject @interface UserClass : NSObject
- (PublicSubClass2 *) _newPublicSubClass2; - (PublicSubClass2 *)_newPublicSubClass2;
- (int) getZero; - (int)getZero;
- (void) callNew; - (void)callNew;
@end @end
@implementation UserClass @implementation UserClass
- (PublicSubClass2 *) _newPublicSubClass2 { - (PublicSubClass2 *)_newPublicSubClass2 {
return [[PublicSubClass2 alloc] init]; return [[PublicSubClass2 alloc] init];
} }
- (int) getZero { return 5; } - (int)getZero {
- (void) callNew { return 5;
}
- (void)callNew {
PublicSubClass2 *x = [self _newPublicSubClass2]; PublicSubClass2 *x = [self _newPublicSubClass2];
clang_analyzer_eval([x getZero] == 0); //expected-warning{{TRUE}} clang_analyzer_eval([x getZero] == 0); //expected-warning{{TRUE}}
} }

View File

@ -13,6 +13,7 @@
// It includes the basic definitions for the test cases below. // It includes the basic definitions for the test cases below.
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#define NULL 0 #define NULL 0
#define nil ((id)0)
typedef unsigned int __darwin_natural_t; typedef unsigned int __darwin_natural_t;
typedef unsigned long uintptr_t; typedef unsigned long uintptr_t;
typedef unsigned int uint32_t; typedef unsigned int uint32_t;
@ -21,14 +22,14 @@ typedef unsigned int UInt32;
typedef signed long CFIndex; typedef signed long CFIndex;
typedef CFIndex CFByteOrder; typedef CFIndex CFByteOrder;
typedef struct { typedef struct {
CFIndex location; CFIndex location;
CFIndex length; CFIndex length;
} CFRange; } CFRange;
static __inline__ __attribute__((always_inline)) CFRange CFRangeMake(CFIndex loc, CFIndex len) { static __inline__ __attribute__((always_inline)) CFRange CFRangeMake(CFIndex loc, CFIndex len) {
CFRange range; CFRange range;
range.location = loc; range.location = loc;
range.length = len; range.length = len;
return range; return range;
} }
typedef const void * CFTypeRef; typedef const void * CFTypeRef;
typedef const struct __CFString * CFStringRef; typedef const struct __CFString * CFStringRef;
@ -91,6 +92,7 @@ typedef struct _NSZone NSZone;
- (BOOL)isEqual:(id)object; - (BOOL)isEqual:(id)object;
- (id)retain; - (id)retain;
- (oneway void)release; - (oneway void)release;
- (Class)class;
- (id)autorelease; - (id)autorelease;
- (id)init; - (id)init;
@end @protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @end @protocol NSCopying - (id)copyWithZone:(NSZone *)zone;
@ -100,6 +102,7 @@ typedef struct _NSZone NSZone;
@interface NSObject <NSObject> {} @interface NSObject <NSObject> {}
+ (id)allocWithZone:(NSZone *)zone; + (id)allocWithZone:(NSZone *)zone;
+ (id)alloc; + (id)alloc;
+ (Class)class;
- (void)dealloc; - (void)dealloc;
@end @end
@interface NSObject (NSCoderMethods) @interface NSObject (NSCoderMethods)
@ -481,3 +484,33 @@ id returnInputParam(id x) {
[self test_inline_tiny_when_reanalyzing]; [self test_inline_tiny_when_reanalyzing];
} }
@end @end
// Original problem: rdar://problem/50739539
@interface MyClassThatLeaksDuringInit : NSObject
+ (MyClassThatLeaksDuringInit *)getAnInstance1;
+ (MyClassThatLeaksDuringInit *)getAnInstance2;
@end
@implementation MyClassThatLeaksDuringInit
+ (MyClassThatLeaksDuringInit *)getAnInstance1 {
return [[[MyClassThatLeaksDuringInit alloc] init] autorelease]; // expected-warning{{leak}}
}
+ (MyClassThatLeaksDuringInit *)getAnInstance2 {
return [[[[self class] alloc] init] autorelease]; // expected-warning{{leak}}
}
- (instancetype)init {
if (1) {
return nil;
}
if (nil != (self = [super init])) {
}
return self;
}
@end