[clang] 239c53b - [analyzer] Track runtime types represented by Obj-C Class objects
Valeriy Savchenko via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 29 03:37:17 PDT 2020
Author: Valeriy Savchenko
Date: 2020-04-29T13:35:53+03:00
New Revision: 239c53b72b18d6fd6c5ad9a6d27cd09b950dc97a
URL: https://github.com/llvm/llvm-project/commit/239c53b72b18d6fd6c5ad9a6d27cd09b950dc97a
DIFF: https://github.com/llvm/llvm-project/commit/239c53b72b18d6fd6c5ad9a6d27cd09b950dc97a.diff
LOG: [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
Added:
clang/test/Analysis/class-object-state-dump.m
Modified:
clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h
clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h
clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp
clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
clang/lib/StaticAnalyzer/Core/CallEvent.cpp
clang/lib/StaticAnalyzer/Core/DynamicType.cpp
clang/lib/StaticAnalyzer/Core/ProgramState.cpp
clang/test/Analysis/cast-value-state-dump.cpp
clang/test/Analysis/inlining/InlineObjCClassMethod.m
clang/test/Analysis/inlining/ObjCDynTypePopagation.m
clang/test/Analysis/retain-release-inline.m
Removed:
################################################################################
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
index 8b4be2784f38..bc562a4ca6f1 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -1115,9 +1115,6 @@ class ObjCMethodCall : public CallEvent {
/// Returns the value of the receiver at the time of this call.
SVal getReceiverSVal() const;
- /// Return the value of 'self' if available.
- SVal getSelfSVal() const;
-
/// Get the interface for the receiver.
///
/// This works whether this is an instance message or a class message.
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h
index 356401d77561..2679339537e8 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h
@@ -36,6 +36,10 @@ DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR);
const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State,
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.
const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State,
const MemRegion *MR,
@@ -50,6 +54,16 @@ ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR,
ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR,
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.
ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State,
const MemRegion *MR,
@@ -63,6 +77,10 @@ ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR);
/// Removes the dead cast informations from \p State.
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,
const char *NL = "\n", unsigned int Space = 0,
bool IsDot = false);
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h
index 6262c4a1ce37..6d2b495dc0f5 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h
@@ -33,6 +33,8 @@ class DynamicTypeInfo {
/// Returns the currently inferred upper bound on the runtime type.
QualType getType() const { return DynTy; }
+ operator bool() const { return isValid(); }
+
bool operator==(const DynamicTypeInfo &RHS) const {
return DynTy == RHS.DynTy && CanBeASubClass == RHS.CanBeASubClass;
}
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
index bdd12a3ffe33..ecb61bffe3d9 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
@@ -298,6 +298,9 @@ class ProgramState : public llvm::FoldingSetNode {
LLVM_NODISCARD ProgramStateRef enterStackFrame(
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.
Loc getLValue(const CXXBaseSpecifier &BaseSpec, const SubRegion *Super) const;
diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp
index 85e2b71e53ea..1ef70b650414 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp
@@ -30,7 +30,7 @@ using namespace clang;
using namespace ento;
namespace {
-class CastValueChecker : public Checker<eval::Call> {
+class CastValueChecker : public Checker<check::DeadSymbols, eval::Call> {
enum class CallKind { Function, Method, InstanceOf };
using CastCheck =
@@ -51,6 +51,7 @@ class CastValueChecker : public Checker<eval::Call> {
// 1) isa: The parameter is 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;
+ void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
private:
// 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;
}
+void CastValueChecker::checkDeadSymbols(SymbolReaper &SR,
+ CheckerContext &C) const {
+ C.addTransition(removeDeadCasts(C.getState(), SR));
+}
+
void ento::registerCastValueChecker(CheckerManager &Mgr) {
Mgr.registerChecker<CastValueChecker>();
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
index 71681594a0a1..1502c0f9d656 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
@@ -109,11 +109,127 @@ class DynamicTypePropagation:
/// This value is set to true, when the Generics checker is turned on.
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
void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR,
CheckerContext &C) const {
ProgramStateRef State = removeDeadTypes(C.getState(), SR);
+ State = removeDeadClassObjectTypes(State, SR);
MostSpecializedTypeArgsMapTy TyArgMap =
State->get<MostSpecializedTypeArgsMap>();
@@ -209,12 +325,21 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call,
case OMF_alloc:
case OMF_new: {
// Get the type of object that will get created.
- const ObjCMessageExpr *MsgE = Msg->getOriginExpr();
- const ObjCObjectType *ObjTy = getObjectTypeForAllocAndNew(MsgE, C);
+ RuntimeType ObjTy = inferReceiverType(*Msg, C);
+
if (!ObjTy)
return;
+
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));
break;
}
@@ -303,40 +428,6 @@ void DynamicTypePropagation::checkPostStmt(const CXXNewExpr *NewE,
/*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.
// 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.
@@ -821,25 +912,56 @@ void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M,
Selector Sel = MessageExpr->getSelector();
ProgramStateRef State = C.getState();
- // Inference for class variables.
- // We are only interested in cases where the class method is invoked on a
- // class. This method is provided by the runtime and available on all classes.
- if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class &&
- Sel.getAsString() == "class") {
- QualType ReceiverType = MessageExpr->getClassReceiver();
- const auto *ReceiverClassType = ReceiverType->castAs<ObjCObjectType>();
- if (!ReceiverClassType->isSpecialized())
- return;
- QualType ReceiverClassPointerType =
- C.getASTContext().getObjCObjectPointerType(
- QualType(ReceiverClassType, 0));
- const auto *InferredType =
- ReceiverClassPointerType->castAs<ObjCObjectPointerType>();
+ // Here we try to propagate information on Class objects.
+ if (Sel.getAsString() == "class") {
+ // We try to figure out the type from the receiver of the 'class' message.
+ if (RuntimeType ReceiverRuntimeType = inferReceiverType(M, C)) {
+
+ ReceiverRuntimeType.Type->getSuperClassType();
+ 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);
- State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType);
- C.addTransition(State);
- return;
+ C.addTransition(State);
+ return;
+ }
+ }
+
+ if (Sel.getAsString() == "superclass") {
+ // We try to figure out the type from the receiver of the 'superclass'
+ // message.
+ if (RuntimeType ReceiverRuntimeType = inferReceiverType(M, C)) {
+
+ // Result type would be a super class of the receiver's type.
+ QualType ReceiversSuperClass =
+ 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.
diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
index 39ffbca8d393..3547b7bb61a2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
@@ -116,13 +116,14 @@ void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
return;
ProgramStateRef State = C.getState();
- SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol();
- assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?");
+ const LocationContext *LC = C.getLocationContext();
+ 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 inline a call to [super dealloc] where the inlined call itself
// calls [super dealloc].
- State = State->add<CalledSuperDealloc>(ReceiverSymbol);
+ State = State->add<CalledSuperDealloc>(SelfSymbol);
C.addTransition(State);
}
diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index ea366d0994b5..55d343048345 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -972,14 +972,6 @@ void ObjCMethodCall::getExtraInvalidatedValues(
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 {
// FIXME: Is this the best way to handle class receivers?
if (!isInstanceMessage())
@@ -991,7 +983,7 @@ SVal ObjCMethodCall::getReceiverSVal() const {
// An instance message with no expression means we are sending to super.
// In this case the object reference is the same as 'self'.
assert(getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance);
- SVal SelfVal = getSelfSVal();
+ SVal SelfVal = getState()->getSelfSVal(getLocationContext());
assert(SelfVal.isValid() && "Calling super but not in ObjC method");
return SelfVal;
}
@@ -1005,8 +997,9 @@ bool ObjCMethodCall::isReceiverSelfOrSuper() const {
return false;
SVal RecVal = getSVal(getOriginExpr()->getInstanceReceiver());
+ SVal SelfVal = getState()->getSelfSVal(getLocationContext());
- return (RecVal == getSelfSVal());
+ return (RecVal == SelfVal);
}
SourceRange ObjCMethodCall::getSourceRange() const {
@@ -1173,23 +1166,75 @@ static const ObjCMethodDecl *findDefiningRedecl(const ObjCMethodDecl *MD) {
return MD;
}
-static bool isCallToSelfClass(const ObjCMessageExpr *ME) {
- const Expr* InstRec = ME->getInstanceReceiver();
- if (!InstRec)
- return false;
- const auto *InstRecIg = dyn_cast<DeclRefExpr>(InstRec->IgnoreParenImpCasts());
+struct PrivateMethodKey {
+ const ObjCInterfaceDecl *Interface;
+ Selector LookupSelector;
+ bool IsClassMethod;
+};
- // Check that receiver is called 'self'.
- if (!InstRecIg || !InstRecIg->getFoundDecl() ||
- !InstRecIg->getFoundDecl()->getName().equals("self"))
- return false;
+template <> struct llvm::DenseMapInfo<PrivateMethodKey> {
+ using InterfaceInfo = DenseMapInfo<const ObjCInterfaceDecl *>;
+ using SelectorInfo = DenseMapInfo<Selector>;
- // Check that the method name is 'class'.
- if (ME->getSelector().getNumArgs() != 0 ||
- !ME->getSelector().getNameForSlot(0).equals("class"))
- return false;
+ static inline PrivateMethodKey getEmptyKey() {
+ return {InterfaceInfo::getEmptyKey(), SelectorInfo::getEmptyKey(), 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 {
@@ -1199,8 +1244,9 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
if (E->isInstanceMessage()) {
// Find the receiver type.
- const ObjCObjectPointerType *ReceiverT = nullptr;
+ const ObjCObjectType *ReceiverT = nullptr;
bool CanBeSubClassed = false;
+ bool LookingForInstanceMethod = true;
QualType SupersType = E->getSuperType();
const MemRegion *Receiver = nullptr;
@@ -1208,7 +1254,7 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
// The receiver is guaranteed to be 'super' in this case.
// Super always means the type of immediate predecessor to the method
// where the call occurs.
- ReceiverT = cast<ObjCObjectPointerType>(SupersType);
+ ReceiverT = cast<ObjCObjectPointerType>(SupersType)->getObjectType();
} else {
Receiver = getReceiverSVal().getAsRegion();
if (!Receiver)
@@ -1223,100 +1269,59 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
QualType DynType = DTI.getType();
CanBeSubClassed = DTI.canBeASubClass();
- ReceiverT = dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType());
- if (ReceiverT && CanBeSubClassed)
- if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl())
- if (!canBeOverridenInSubclass(IDecl, Sel))
- CanBeSubClassed = false;
- }
+ const auto *ReceiverDynT =
+ dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType());
+
+ if (ReceiverDynT) {
+ ReceiverT = ReceiverDynT->getObjectType();
+
+ // It can be actually class methods called with Class object as a
+ // receiver. This type of messages is treated by the compiler as
+ // instance (not class).
+ if (ReceiverT->isObjCClass()) {
- // Handle special cases of '[self classMethod]' and
- // '[[self class] classMethod]', which are treated by the compiler as
- // 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].
- // TODO: We are currently doing a syntactic match for this pattern with is
- // limiting as the test cases in Analysis/inlining/InlineObjCClassMethod.m
- // shows. A better way would be to associate the meta type with the symbol
- // 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
- // come from. Then 'self' in a class method would be filled in with
- // something meaningful in ObjCMethodCall::getReceiverSVal() and we could
- // 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))
+ SVal SelfVal = getState()->getSelfSVal(getLocationContext());
+ // For [self classMethod], return compiler visible declaration.
+ if (Receiver == SelfVal.getAsRegion()) {
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.
if (ReceiverT)
- if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) {
- // 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 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);
- }
- }
+ if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) {
+ const ObjCMethodDecl *MD =
+ lookupRuntimeDefinition(IDecl, Sel, LookingForInstanceMethod);
- const ObjCMethodDecl *MD = Val.getValue();
if (MD && !MD->hasBody())
MD = MD->getCanonicalDecl();
+
if (CanBeSubClassed)
return RuntimeDefinition(MD, Receiver);
else
diff --git a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp
index a78e0e05e903..e9b64fd79614 100644
--- a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp
+++ b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp
@@ -34,6 +34,10 @@ REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(CastSet, clang::ento::DynamicCastInfo)
REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *,
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 ento {
@@ -76,6 +80,12 @@ const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State,
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,
DynamicTypeInfo NewTy) {
State = State->set<DynamicTypeMap>(MR->StripCasts(), NewTy);
@@ -118,111 +128,165 @@ ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef 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>
-ProgramStateRef removeDead(ProgramStateRef State, const MapTy &Map,
- SymbolReaper &SR) {
+static ProgramStateRef removeDeadImpl(ProgramStateRef State, SymbolReaper &SR) {
+ const auto &Map = State->get<MapTy>();
+
for (const auto &Elem : Map)
- if (!SR.isLiveRegion(Elem.first))
- State = State->remove<DynamicCastMap>(Elem.first);
+ if (!isLive(SR, Elem.first))
+ State = State->remove<MapTy>(Elem.first);
return State;
}
ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR) {
- return removeDead(State, State->get<DynamicTypeMap>(), SR);
+ return removeDeadImpl<DynamicTypeMap>(State, SR);
}
ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) {
- return removeDead(State, State->get<DynamicCastMap>(), SR);
+ return removeDeadImpl<DynamicCastMap>(State, SR);
}
-static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State,
- const char *NL, unsigned int Space,
- bool IsDot) {
- Indent(Out, Space, IsDot) << "\"dynamic_types\": ";
+ProgramStateRef removeDeadClassObjectTypes(ProgramStateRef State,
+ SymbolReaper &SR) {
+ return removeDeadImpl<DynamicClassObjectMap>(State, SR);
+}
- const DynamicTypeMapTy &Map = State->get<DynamicTypeMap>();
- if (Map.isEmpty()) {
- Out << "null," << NL;
- return;
- }
+//===----------------------------------------------------------------------===//
+// Implementation of the 'printer-to-JSON' function
+//===----------------------------------------------------------------------===//
- ++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;
+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;
+}
- --Space;
- Indent(Out, Space, IsDot) << "]," << NL;
+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") << "\"";
}
-static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State,
- const char *NL, unsigned int Space,
- bool IsDot) {
- Indent(Out, Space, IsDot) << "\"dynamic_casts\": ";
+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);
+}
- const DynamicCastMapTy &Map = State->get<DynamicCastMap>();
- if (Map.isEmpty()) {
- Out << "null," << NL;
- return;
+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 (DynamicCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) {
- 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())
+ 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;
- Indent(Out, Space, IsDot) << "]," << NL;
+ 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,
+ const char *NL, unsigned int Space,
+ bool IsDot) {
+ printJsonImpl<DynamicTypeMap>(Out, State, "dynamic_types", NL, Space, IsDot);
+}
+
+static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, unsigned int Space,
+ bool IsDot) {
+ printJsonImpl<DynamicCastMap>(Out, State, "dynamic_casts", NL, Space, IsDot);
+}
+
+static void printClassObjectDynamicTypesJson(raw_ostream &Out,
+ ProgramStateRef State,
+ const char *NL, unsigned int Space,
+ bool IsDot) {
+ // Let's print Class object type information only if we have something
+ // meaningful to print.
+ printJsonImpl<DynamicClassObjectMap>(Out, State, "class_object_types", NL,
+ Space, IsDot,
+ /*PrintEvenIfEmpty=*/false);
}
void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State,
const char *NL, unsigned int Space, bool IsDot) {
printDynamicTypesJson(Out, State, NL, Space, IsDot);
printDynamicCastsJson(Out, State, NL, Space, IsDot);
+ printClassObjectDynamicTypesJson(Out, State, NL, Space, IsDot);
}
} // namespace ento
diff --git a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
index 14006f79fd0f..3ecee758c676 100644
--- a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
@@ -240,6 +240,13 @@ ProgramState::enterStackFrame(const CallEvent &Call,
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 {
// We only want to do fetches from regions that we can actually bind
// values. For example, SymbolicRegions of type 'id<...>' cannot
diff --git a/clang/test/Analysis/cast-value-state-dump.cpp b/clang/test/Analysis/cast-value-state-dump.cpp
index 9abdaae0d459..3dffb78767cf 100644
--- a/clang/test/Analysis/cast-value-state-dump.cpp
+++ b/clang/test/Analysis/cast-value-state-dump.cpp
@@ -37,7 +37,7 @@ void evalNonNullParamNonNullReturn(const Shape *S) {
// 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::Square *", "kind": "fail" }
- // CHECK-NEXT: ]}
+ // CHECK-NEXT: ] }
(void)(1 / !C);
// expected-note at -1 {{'C' is non-null}}
diff --git a/clang/test/Analysis/class-object-state-dump.m b/clang/test/Analysis/class-object-state-dump.m
new file mode 100644
index 000000000000..66519b82adb1
--- /dev/null
+++ b/clang/test/Analysis/class-object-state-dump.m
@@ -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();
+
+ at interface NSObject {
+}
++ (id)alloc;
++ (Class)class;
+- (id)init;
+- (Class)class;
+ at end
+
+ at interface Parent : NSObject
+ at end
+ at interface Child : Parent
+ at end
+
+void foo(id A, id B);
+
+ at 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);
+}
+ at end
diff --git a/clang/test/Analysis/inlining/InlineObjCClassMethod.m b/clang/test/Analysis/inlining/InlineObjCClassMethod.m
index 11b2d63afa59..48f5ac211657 100644
--- a/clang/test/Analysis/inlining/InlineObjCClassMethod.m
+++ b/clang/test/Analysis/inlining/InlineObjCClassMethod.m
@@ -6,18 +6,25 @@
// Test inlining of ObjC class methods.
typedef signed char BOOL;
+#define YES ((BOOL)1)
+#define NO ((BOOL)0)
typedef struct objc_class *Class;
typedef struct objc_object {
- Class isa;
-} *id;
- at protocol NSObject - (BOOL)isEqual:(id)object; @end
+ Class isa;
+} * id;
+ at protocol NSObject
+- (BOOL)isEqual:(id)object;
+ at end
@interface NSObject <NSObject> {}
-+(id)alloc;
--(id)init;
--(id)autorelease;
--(id)copy;
++ (id)alloc;
++ (Class)class;
++ (Class)superclass;
+- (id)init;
+- (id)autorelease;
+- (id)copy;
- (Class)class;
--(id)retain;
+- (instancetype)self;
+- (id)retain;
@end
// Vanila: ObjC class method is called by name.
@@ -133,10 +140,7 @@ + (int)getInt {
}
@end
-
-// False negative.
// 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?
@interface MyClassKT : NSObject
@end
@@ -152,7 +156,7 @@ @implementation MyClassKT
- (int)testClassMethodByKnownVarDecl {
Class currentClass = [self class];
int y = [currentClass getInt];
- return 5/y; // Would be great to get a warning here.
+ return 5 / y; // expected-warning{{Division by zero}}
}
@end
@@ -240,37 +244,124 @@ +(unsigned)returns20;
+(unsigned)returns30;
@end
- at implementation SelfClassTestParent
--(unsigned)returns10 { return 100; }
-+(unsigned)returns20 { return 100; }
-+(unsigned)returns30 { return 100; }
+ at interface SelfClassTest : SelfClassTestParent
+- (unsigned)returns10;
++ (unsigned)returns20;
++ (unsigned)returns30;
@end
- at interface SelfClassTest : SelfClassTestParent
--(unsigned)returns10;
-+(unsigned)returns20;
-+(unsigned)returns30;
+ at implementation SelfClassTestParent
+- (unsigned)returns10 {
+ return 100;
+}
++ (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
@implementation SelfClassTest
--(unsigned)returns10 { return 10; }
-+(unsigned)returns20 { return 20; }
-+(unsigned)returns30 { return 30; }
-+(void)classMethod {
+- (unsigned)returns10 {
+ return 10;
+}
++ (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];
clang_analyzer_eval(result1 == 20); // expected-warning{{TRUE}}
+
unsigned result2 = [[self class] returns30];
clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}}
+
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];
clang_analyzer_eval(result0 == 10); // expected-warning{{TRUE}}
+
unsigned result2 = [[self class] returns30];
clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}}
+
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
diff --git a/clang/test/Analysis/inlining/ObjCDynTypePopagation.m b/clang/test/Analysis/inlining/ObjCDynTypePopagation.m
index 0c1d4f2a31cb..b5e70229c7c8 100644
--- a/clang/test/Analysis/inlining/ObjCDynTypePopagation.m
+++ b/clang/test/Analysis/inlining/ObjCDynTypePopagation.m
@@ -7,68 +7,67 @@
PublicSubClass2 *getObj();
@implementation PublicParent
-- (int) getZeroOverridden {
- return 1;
+- (int)getZeroOverridden {
+ return 1;
}
-- (int) getZero {
- return 0;
+- (int)getZero {
+ return 0;
}
@end
@implementation PublicSubClass2
-- (int) getZeroOverridden {
- return 0;
+- (int)getZeroOverridden {
+ return 0;
}
/* Test that we get the right type from call to alloc. */
-+ (void) testAllocSelf {
++ (void)testAllocSelf {
id a = [self alloc];
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}}
}
-
-+ (void) testAllocClass {
++ (void)testAllocClass {
id a = [PublicSubClass2 alloc];
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}}
}
-+ (void) testAllocSuperOverriden {
++ (void)testAllocSuperOverriden {
id a = [super alloc];
// 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];
clang_analyzer_eval([a getZero] == 0); // expected-warning{{TRUE}}
}
-+ (void) testAllocInit {
++ (void)testAllocInit {
id a = [[self alloc] init];
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}}
}
-+ (void) testNewSelf {
++ (void)testNewSelf {
id a = [self new];
clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}}
}
-// Casting to parent should not pessimize the dynamic type.
-+ (void) testCastToParent {
- id a = [[self alloc] init];
- PublicParent *p = a;
+// Casting to parent should not pessimize the dynamic type.
++ (void)testCastToParent {
+ id a = [[self alloc] init];
+ PublicParent *p = a;
clang_analyzer_eval([p getZeroOverridden] == 0); // expected-warning{{TRUE}}
}
// The type of parameter gets used.
-+ (void)testTypeFromParam:(PublicParent*) p {
++ (void)testTypeFromParam:(PublicParent *)p {
clang_analyzer_eval([p getZero] == 0); // expected-warning{{TRUE}}
}
// Test implicit cast.
// Note, in this case, p could also be a subclass of MyParent.
-+ (void) testCastFromId:(id) a {
- PublicParent *p = a;
++ (void)testCastFromId:(id)a {
+ PublicParent *p = a;
clang_analyzer_eval([p getZero] == 0); // expected-warning{{TRUE}}
}
@end
@@ -76,25 +75,27 @@ + (void) testCastFromId:(id) a {
// TODO: Would be nice to handle the case of dynamically obtained class info
// as well. We need a MemRegion for class types for this.
int testDynamicClass(BOOL coin) {
- Class AllocClass = (coin ? [NSObject class] : [PublicSubClass2 class]);
- id x = [[AllocClass alloc] init];
- if (coin)
- return [x getZero];
- return 1;
+ Class AllocClass = (coin ? [NSObject class] : [PublicSubClass2 class]);
+ id x = [[AllocClass alloc] init];
+ if (coin)
+ return [x getZero];
+ return 1;
}
@interface UserClass : NSObject
-- (PublicSubClass2 *) _newPublicSubClass2;
-- (int) getZero;
-- (void) callNew;
+- (PublicSubClass2 *)_newPublicSubClass2;
+- (int)getZero;
+- (void)callNew;
@end
@implementation UserClass
-- (PublicSubClass2 *) _newPublicSubClass2 {
+- (PublicSubClass2 *)_newPublicSubClass2 {
return [[PublicSubClass2 alloc] init];
}
-- (int) getZero { return 5; }
-- (void) callNew {
+- (int)getZero {
+ return 5;
+}
+- (void)callNew {
PublicSubClass2 *x = [self _newPublicSubClass2];
clang_analyzer_eval([x getZero] == 0); //expected-warning{{TRUE}}
}
diff --git a/clang/test/Analysis/retain-release-inline.m b/clang/test/Analysis/retain-release-inline.m
index 4fe6bca4a44a..ea2acbd7f602 100644
--- a/clang/test/Analysis/retain-release-inline.m
+++ b/clang/test/Analysis/retain-release-inline.m
@@ -13,6 +13,7 @@
// It includes the basic definitions for the test cases below.
//===----------------------------------------------------------------------===//
#define NULL 0
+#define nil ((id)0)
typedef unsigned int __darwin_natural_t;
typedef unsigned long uintptr_t;
typedef unsigned int uint32_t;
@@ -21,14 +22,14 @@
typedef signed long CFIndex;
typedef CFIndex CFByteOrder;
typedef struct {
- CFIndex location;
- CFIndex length;
+ CFIndex location;
+ CFIndex length;
} CFRange;
static __inline__ __attribute__((always_inline)) CFRange CFRangeMake(CFIndex loc, CFIndex len) {
- CFRange range;
- range.location = loc;
- range.length = len;
- return range;
+ CFRange range;
+ range.location = loc;
+ range.length = len;
+ return range;
}
typedef const void * CFTypeRef;
typedef const struct __CFString * CFStringRef;
@@ -91,6 +92,7 @@ @protocol NSObject
- (BOOL)isEqual:(id)object;
- (id)retain;
- (oneway void)release;
+- (Class)class;
- (id)autorelease;
- (id)init;
@end @protocol NSCopying - (id)copyWithZone:(NSZone *)zone;
@@ -100,6 +102,7 @@ - (id)init;
@interface NSObject <NSObject> {}
+ (id)allocWithZone:(NSZone *)zone;
+ (id)alloc;
++ (Class)class;
- (void)dealloc;
@end
@interface NSObject (NSCoderMethods)
@@ -481,3 +484,33 @@ - (void)inline_test_reanalyze_as_top_level {
[self test_inline_tiny_when_reanalyzing];
}
@end
+
+// Original problem: rdar://problem/50739539
+ at interface MyClassThatLeaksDuringInit : NSObject
+
++ (MyClassThatLeaksDuringInit *)getAnInstance1;
++ (MyClassThatLeaksDuringInit *)getAnInstance2;
+
+ at end
+
+ at 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;
+}
+
+ at end
More information about the cfe-commits
mailing list