diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index 8b4be2784f384..bc562a4ca6f10 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 356401d77561c..2679339537e89 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 6262c4a1ce378..6d2b495dc0f50 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 bdd12a3ffe330..ecb61bffe3d95 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 85e2b71e53ea1..1ef70b650414e 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 { +class CastValueChecker : public Checker { enum class CallKind { Function, Method, InstanceOf }; using CastCheck = @@ -51,6 +51,7 @@ class CastValueChecker : public Checker { // 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(); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index 71681594a0a14..1502c0f9d6567 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(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(), + /*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(), + /*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()) + 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(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(C.getStackFrame()->getDecl())) + if (const ObjCObjectType *ObjTy = dyn_cast( + 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(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(); @@ -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()) - return ObjTy; - } - - if (MsgE->getReceiverKind() == ObjCMessageExpr::SuperClass) { - if (const ObjCObjectType *ObjTy - = MsgE->getSuperType()->getAs()) - return ObjTy; - } - - const Expr *RecE = MsgE->getInstanceReceiver(); - if (!RecE) - return nullptr; - - RecE= RecE->IgnoreParenImpCasts(); - if (const DeclRefExpr *DRE = dyn_cast(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(SFCtx->getDecl())) - if (const ObjCObjectType *ObjTy = - dyn_cast(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(); - if (!ReceiverClassType->isSpecialized()) - return; - QualType ReceiverClassPointerType = - C.getASTContext().getObjCObjectPointerType( - QualType(ReceiverClassType, 0)); - const auto *InferredType = - ReceiverClassPointerType->castAs(); + // 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(); + State = State->set(RetSym, InferredType); + } + + // Constrain the resulting class object to the inferred type. + State = setClassObjectDynamicTypeInfo(State, RetSym, ReceiverClassType, + !ReceiverRuntimeType.Precise); - State = State->set(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 39ffbca8d393a..3547b7bb61a24 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(ReceiverSymbol); + State = State->add(SelfSymbol); C.addTransition(State); } diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index ea366d0994b56..55d3430483456 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(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 { + using InterfaceInfo = DenseMapInfo; + using SelectorInfo = DenseMapInfo; - // 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>; + + static PrivateMethodCache PMC; + Optional &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(SupersType); + ReceiverT = cast(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(DynType.getCanonicalType()); - if (ReceiverT && CanBeSubClassed) - if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) - if (!canBeOverridenInSubclass(IDecl, Sel)) - CanBeSubClassed = false; - } + const auto *ReceiverDynT = + dyn_cast(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(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(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(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; - using PrivateMethodCache = - llvm::DenseMap>; - - static PrivateMethodCache PMC; - Optional &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(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 a78e0e05e903b..e9b64fd79614d 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(Sym); + return DTI ? *DTI : DynamicTypeInfo{}; +} + ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, DynamicTypeInfo NewTy) { State = State->set(MR->StripCasts(), NewTy); @@ -118,111 +128,165 @@ ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, return State; } +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, + DynamicTypeInfo NewTy) { + State = State->set(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 -ProgramStateRef removeDead(ProgramStateRef State, const MapTy &Map, - SymbolReaper &SR) { +static ProgramStateRef removeDeadImpl(ProgramStateRef State, SymbolReaper &SR) { + const auto &Map = State->get(); + for (const auto &Elem : Map) - if (!SR.isLiveRegion(Elem.first)) - State = State->remove(Elem.first); + if (!isLive(SR, Elem.first)) + State = State->remove(Elem.first); return State; } ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR) { - return removeDead(State, State->get(), SR); + return removeDeadImpl(State, SR); } ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) { - return removeDead(State, State->get(), SR); + return removeDeadImpl(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(State, SR); +} - const DynamicTypeMapTy &Map = State->get(); - 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 +static raw_ostream &printJson(const std::pair &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(); - if (Map.isEmpty()) { - Out << "null," << NL; - return; +template +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 +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(); + 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(Out, State, "dynamic_types", NL, Space, IsDot); +} + +static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + printJsonImpl(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(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 14006f79fd0f6..3ecee758c676c 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 9abdaae0d4592..3dffb78767cf4 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}", "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@-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 0000000000000..66519b82adb1d --- /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(); + +@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 diff --git a/clang/test/Analysis/inlining/InlineObjCClassMethod.m b/clang/test/Analysis/inlining/InlineObjCClassMethod.m index 11b2d63afa592..48f5ac211657c 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; -@protocol NSObject - (BOOL)isEqual:(id)object; @end + Class isa; +} * id; +@protocol NSObject +- (BOOL)isEqual:(id)object; +@end @interface 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 -@implementation SelfClassTestParent --(unsigned)returns10 { return 100; } -+(unsigned)returns20 { return 100; } -+(unsigned)returns30 { return 100; } +@interface SelfClassTest : SelfClassTestParent +- (unsigned)returns10; ++ (unsigned)returns20; ++ (unsigned)returns30; @end -@interface SelfClassTest : SelfClassTestParent --(unsigned)returns10; -+(unsigned)returns20; -+(unsigned)returns30; +@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 0c1d4f2a31cbc..b5e70229c7c89 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 4fe6bca4a44a4..ea2acbd7f602e 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 {} + (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 +@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