1308 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			1308 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C++
		
	
	
	
| //== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- C++ -*--
 | |
| //
 | |
| //                     The LLVM Compiler Infrastructure
 | |
| //
 | |
| // This file is distributed under the University of Illinois Open Source
 | |
| // License. See LICENSE.TXT for details.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| //
 | |
| //  This file defines BasicObjCFoundationChecks, a class that encapsulates
 | |
| //  a set of simple checks to run on Objective-C code using Apple's Foundation
 | |
| //  classes.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "ClangSACheckers.h"
 | |
| #include "SelectorExtras.h"
 | |
| #include "clang/AST/ASTContext.h"
 | |
| #include "clang/AST/DeclObjC.h"
 | |
| #include "clang/AST/Expr.h"
 | |
| #include "clang/AST/ExprObjC.h"
 | |
| #include "clang/AST/StmtObjC.h"
 | |
| #include "clang/Analysis/DomainSpecific/CocoaConventions.h"
 | |
| #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
 | |
| #include "clang/StaticAnalyzer/Core/Checker.h"
 | |
| #include "clang/StaticAnalyzer/Core/CheckerManager.h"
 | |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 | |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
 | |
| #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
 | |
| #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
 | |
| #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
 | |
| #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
 | |
| #include "llvm/ADT/SmallString.h"
 | |
| #include "llvm/ADT/StringMap.h"
 | |
| #include "llvm/Support/raw_ostream.h"
 | |
| 
 | |
| using namespace clang;
 | |
| using namespace ento;
 | |
| 
 | |
| namespace {
 | |
| class APIMisuse : public BugType {
 | |
| public:
 | |
|   APIMisuse(const CheckerBase *checker, const char *name)
 | |
|       : BugType(checker, name, "API Misuse (Apple)") {}
 | |
| };
 | |
| } // end anonymous namespace
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // Utility functions.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| static StringRef GetReceiverInterfaceName(const ObjCMethodCall &msg) {
 | |
|   if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())
 | |
|     return ID->getIdentifier()->getName();
 | |
|   return StringRef();
 | |
| }
 | |
| 
 | |
| enum FoundationClass {
 | |
|   FC_None,
 | |
|   FC_NSArray,
 | |
|   FC_NSDictionary,
 | |
|   FC_NSEnumerator,
 | |
|   FC_NSNull,
 | |
|   FC_NSOrderedSet,
 | |
|   FC_NSSet,
 | |
|   FC_NSString
 | |
| };
 | |
| 
 | |
| static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID,
 | |
|                                       bool IncludeSuperclasses = true) {
 | |
|   static llvm::StringMap<FoundationClass> Classes;
 | |
|   if (Classes.empty()) {
 | |
|     Classes["NSArray"] = FC_NSArray;
 | |
|     Classes["NSDictionary"] = FC_NSDictionary;
 | |
|     Classes["NSEnumerator"] = FC_NSEnumerator;
 | |
|     Classes["NSNull"] = FC_NSNull;
 | |
|     Classes["NSOrderedSet"] = FC_NSOrderedSet;
 | |
|     Classes["NSSet"] = FC_NSSet;
 | |
|     Classes["NSString"] = FC_NSString;
 | |
|   }
 | |
| 
 | |
|   // FIXME: Should we cache this at all?
 | |
|   FoundationClass result = Classes.lookup(ID->getIdentifier()->getName());
 | |
|   if (result == FC_None && IncludeSuperclasses)
 | |
|     if (const ObjCInterfaceDecl *Super = ID->getSuperClass())
 | |
|       return findKnownClass(Super);
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // NilArgChecker - Check for prohibited nil arguments to ObjC method calls.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| namespace {
 | |
|   class NilArgChecker : public Checker<check::PreObjCMessage,
 | |
|                                        check::PostStmt<ObjCDictionaryLiteral>,
 | |
|                                        check::PostStmt<ObjCArrayLiteral> > {
 | |
|     mutable std::unique_ptr<APIMisuse> BT;
 | |
| 
 | |
|     mutable llvm::SmallDenseMap<Selector, unsigned, 16> StringSelectors;
 | |
|     mutable Selector ArrayWithObjectSel;
 | |
|     mutable Selector AddObjectSel;
 | |
|     mutable Selector InsertObjectAtIndexSel;
 | |
|     mutable Selector ReplaceObjectAtIndexWithObjectSel;
 | |
|     mutable Selector SetObjectAtIndexedSubscriptSel;
 | |
|     mutable Selector ArrayByAddingObjectSel;
 | |
|     mutable Selector DictionaryWithObjectForKeySel;
 | |
|     mutable Selector SetObjectForKeySel;
 | |
|     mutable Selector SetObjectForKeyedSubscriptSel;
 | |
|     mutable Selector RemoveObjectForKeySel;
 | |
| 
 | |
|     void warnIfNilExpr(const Expr *E,
 | |
|                        const char *Msg,
 | |
|                        CheckerContext &C) const;
 | |
| 
 | |
|     void warnIfNilArg(CheckerContext &C,
 | |
|                       const ObjCMethodCall &msg, unsigned Arg,
 | |
|                       FoundationClass Class,
 | |
|                       bool CanBeSubscript = false) const;
 | |
| 
 | |
|     void generateBugReport(ExplodedNode *N,
 | |
|                            StringRef Msg,
 | |
|                            SourceRange Range,
 | |
|                            const Expr *Expr,
 | |
|                            CheckerContext &C) const;
 | |
| 
 | |
|   public:
 | |
|     void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
 | |
|     void checkPostStmt(const ObjCDictionaryLiteral *DL,
 | |
|                        CheckerContext &C) const;
 | |
|     void checkPostStmt(const ObjCArrayLiteral *AL,
 | |
|                        CheckerContext &C) const;
 | |
|   };
 | |
| }
 | |
| 
 | |
| void NilArgChecker::warnIfNilExpr(const Expr *E,
 | |
|                                   const char *Msg,
 | |
|                                   CheckerContext &C) const {
 | |
|   ProgramStateRef State = C.getState();
 | |
|   if (State->isNull(C.getSVal(E)).isConstrainedTrue()) {
 | |
| 
 | |
|     if (ExplodedNode *N = C.generateErrorNode()) {
 | |
|       generateBugReport(N, Msg, E->getSourceRange(), E, C);
 | |
|     }
 | |
| 
 | |
|   }
 | |
| }
 | |
| 
 | |
| void NilArgChecker::warnIfNilArg(CheckerContext &C,
 | |
|                                  const ObjCMethodCall &msg,
 | |
|                                  unsigned int Arg,
 | |
|                                  FoundationClass Class,
 | |
|                                  bool CanBeSubscript) const {
 | |
|   // Check if the argument is nil.
 | |
|   ProgramStateRef State = C.getState();
 | |
|   if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue())
 | |
|       return;
 | |
| 
 | |
|   if (ExplodedNode *N = C.generateErrorNode()) {
 | |
|     SmallString<128> sbuf;
 | |
|     llvm::raw_svector_ostream os(sbuf);
 | |
| 
 | |
|     if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) {
 | |
| 
 | |
|       if (Class == FC_NSArray) {
 | |
|         os << "Array element cannot be nil";
 | |
|       } else if (Class == FC_NSDictionary) {
 | |
|         if (Arg == 0) {
 | |
|           os << "Value stored into '";
 | |
|           os << GetReceiverInterfaceName(msg) << "' cannot be nil";
 | |
|         } else {
 | |
|           assert(Arg == 1);
 | |
|           os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil";
 | |
|         }
 | |
|       } else
 | |
|         llvm_unreachable("Missing foundation class for the subscript expr");
 | |
| 
 | |
|     } else {
 | |
|       if (Class == FC_NSDictionary) {
 | |
|         if (Arg == 0)
 | |
|           os << "Value argument ";
 | |
|         else {
 | |
|           assert(Arg == 1);
 | |
|           os << "Key argument ";
 | |
|         }
 | |
|         os << "to '";
 | |
|         msg.getSelector().print(os);
 | |
|         os << "' cannot be nil";
 | |
|       } else {
 | |
|         os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '";
 | |
|         msg.getSelector().print(os);
 | |
|         os << "' cannot be nil";
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     generateBugReport(N, os.str(), msg.getArgSourceRange(Arg),
 | |
|                       msg.getArgExpr(Arg), C);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void NilArgChecker::generateBugReport(ExplodedNode *N,
 | |
|                                       StringRef Msg,
 | |
|                                       SourceRange Range,
 | |
|                                       const Expr *E,
 | |
|                                       CheckerContext &C) const {
 | |
|   if (!BT)
 | |
|     BT.reset(new APIMisuse(this, "nil argument"));
 | |
| 
 | |
|   auto R = llvm::make_unique<BugReport>(*BT, Msg, N);
 | |
|   R->addRange(Range);
 | |
|   bugreporter::trackNullOrUndefValue(N, E, *R);
 | |
|   C.emitReport(std::move(R));
 | |
| }
 | |
| 
 | |
| void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
 | |
|                                         CheckerContext &C) const {
 | |
|   const ObjCInterfaceDecl *ID = msg.getReceiverInterface();
 | |
|   if (!ID)
 | |
|     return;
 | |
| 
 | |
|   FoundationClass Class = findKnownClass(ID);
 | |
| 
 | |
|   static const unsigned InvalidArgIndex = UINT_MAX;
 | |
|   unsigned Arg = InvalidArgIndex;
 | |
|   bool CanBeSubscript = false;
 | |
| 
 | |
|   if (Class == FC_NSString) {
 | |
|     Selector S = msg.getSelector();
 | |
| 
 | |
|     if (S.isUnarySelector())
 | |
|       return;
 | |
| 
 | |
|     if (StringSelectors.empty()) {
 | |
|       ASTContext &Ctx = C.getASTContext();
 | |
|       Selector Sels[] = {
 | |
|         getKeywordSelector(Ctx, "caseInsensitiveCompare", nullptr),
 | |
|         getKeywordSelector(Ctx, "compare", nullptr),
 | |
|         getKeywordSelector(Ctx, "compare", "options", nullptr),
 | |
|         getKeywordSelector(Ctx, "compare", "options", "range", nullptr),
 | |
|         getKeywordSelector(Ctx, "compare", "options", "range", "locale",
 | |
|                            nullptr),
 | |
|         getKeywordSelector(Ctx, "componentsSeparatedByCharactersInSet",
 | |
|                            nullptr),
 | |
|         getKeywordSelector(Ctx, "initWithFormat",
 | |
|                            nullptr),
 | |
|         getKeywordSelector(Ctx, "localizedCaseInsensitiveCompare", nullptr),
 | |
|         getKeywordSelector(Ctx, "localizedCompare", nullptr),
 | |
|         getKeywordSelector(Ctx, "localizedStandardCompare", nullptr),
 | |
|       };
 | |
|       for (Selector KnownSel : Sels)
 | |
|         StringSelectors[KnownSel] = 0;
 | |
|     }
 | |
|     auto I = StringSelectors.find(S);
 | |
|     if (I == StringSelectors.end())
 | |
|       return;
 | |
|     Arg = I->second;
 | |
|   } else if (Class == FC_NSArray) {
 | |
|     Selector S = msg.getSelector();
 | |
| 
 | |
|     if (S.isUnarySelector())
 | |
|       return;
 | |
| 
 | |
|     if (ArrayWithObjectSel.isNull()) {
 | |
|       ASTContext &Ctx = C.getASTContext();
 | |
|       ArrayWithObjectSel = getKeywordSelector(Ctx, "arrayWithObject", nullptr);
 | |
|       AddObjectSel = getKeywordSelector(Ctx, "addObject", nullptr);
 | |
|       InsertObjectAtIndexSel =
 | |
|         getKeywordSelector(Ctx, "insertObject", "atIndex", nullptr);
 | |
|       ReplaceObjectAtIndexWithObjectSel =
 | |
|         getKeywordSelector(Ctx, "replaceObjectAtIndex", "withObject", nullptr);
 | |
|       SetObjectAtIndexedSubscriptSel =
 | |
|         getKeywordSelector(Ctx, "setObject", "atIndexedSubscript", nullptr);
 | |
|       ArrayByAddingObjectSel =
 | |
|         getKeywordSelector(Ctx, "arrayByAddingObject", nullptr);
 | |
|     }
 | |
| 
 | |
|     if (S == ArrayWithObjectSel || S == AddObjectSel ||
 | |
|         S == InsertObjectAtIndexSel || S == ArrayByAddingObjectSel) {
 | |
|       Arg = 0;
 | |
|     } else if (S == SetObjectAtIndexedSubscriptSel) {
 | |
|       Arg = 0;
 | |
|       CanBeSubscript = true;
 | |
|     } else if (S == ReplaceObjectAtIndexWithObjectSel) {
 | |
|       Arg = 1;
 | |
|     }
 | |
|   } else if (Class == FC_NSDictionary) {
 | |
|     Selector S = msg.getSelector();
 | |
| 
 | |
|     if (S.isUnarySelector())
 | |
|       return;
 | |
| 
 | |
|     if (DictionaryWithObjectForKeySel.isNull()) {
 | |
|       ASTContext &Ctx = C.getASTContext();
 | |
|       DictionaryWithObjectForKeySel =
 | |
|         getKeywordSelector(Ctx, "dictionaryWithObject", "forKey", nullptr);
 | |
|       SetObjectForKeySel =
 | |
|         getKeywordSelector(Ctx, "setObject", "forKey", nullptr);
 | |
|       SetObjectForKeyedSubscriptSel =
 | |
|         getKeywordSelector(Ctx, "setObject", "forKeyedSubscript", nullptr);
 | |
|       RemoveObjectForKeySel =
 | |
|         getKeywordSelector(Ctx, "removeObjectForKey", nullptr);
 | |
|     }
 | |
| 
 | |
|     if (S == DictionaryWithObjectForKeySel || S == SetObjectForKeySel) {
 | |
|       Arg = 0;
 | |
|       warnIfNilArg(C, msg, /* Arg */1, Class);
 | |
|     } else if (S == SetObjectForKeyedSubscriptSel) {
 | |
|       CanBeSubscript = true;
 | |
|       Arg = 1;
 | |
|     } else if (S == RemoveObjectForKeySel) {
 | |
|       Arg = 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If argument is '0', report a warning.
 | |
|   if ((Arg != InvalidArgIndex))
 | |
|     warnIfNilArg(C, msg, Arg, Class, CanBeSubscript);
 | |
| }
 | |
| 
 | |
| void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL,
 | |
|                                   CheckerContext &C) const {
 | |
|   unsigned NumOfElements = AL->getNumElements();
 | |
|   for (unsigned i = 0; i < NumOfElements; ++i) {
 | |
|     warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
 | |
|                                   CheckerContext &C) const {
 | |
|   unsigned NumOfElements = DL->getNumElements();
 | |
|   for (unsigned i = 0; i < NumOfElements; ++i) {
 | |
|     ObjCDictionaryElement Element = DL->getKeyValueElement(i);
 | |
|     warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C);
 | |
|     warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C);
 | |
|   }
 | |
| }
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // Error reporting.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| namespace {
 | |
| class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > {
 | |
|   mutable std::unique_ptr<APIMisuse> BT;
 | |
|   mutable IdentifierInfo* II;
 | |
| public:
 | |
|   CFNumberCreateChecker() : II(nullptr) {}
 | |
| 
 | |
|   void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
 | |
| 
 | |
| private:
 | |
|   void EmitError(const TypedRegion* R, const Expr *Ex,
 | |
|                 uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind);
 | |
| };
 | |
| } // end anonymous namespace
 | |
| 
 | |
| enum CFNumberType {
 | |
|   kCFNumberSInt8Type = 1,
 | |
|   kCFNumberSInt16Type = 2,
 | |
|   kCFNumberSInt32Type = 3,
 | |
|   kCFNumberSInt64Type = 4,
 | |
|   kCFNumberFloat32Type = 5,
 | |
|   kCFNumberFloat64Type = 6,
 | |
|   kCFNumberCharType = 7,
 | |
|   kCFNumberShortType = 8,
 | |
|   kCFNumberIntType = 9,
 | |
|   kCFNumberLongType = 10,
 | |
|   kCFNumberLongLongType = 11,
 | |
|   kCFNumberFloatType = 12,
 | |
|   kCFNumberDoubleType = 13,
 | |
|   kCFNumberCFIndexType = 14,
 | |
|   kCFNumberNSIntegerType = 15,
 | |
|   kCFNumberCGFloatType = 16
 | |
| };
 | |
| 
 | |
| static Optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) {
 | |
|   static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 };
 | |
| 
 | |
|   if (i < kCFNumberCharType)
 | |
|     return FixedSize[i-1];
 | |
| 
 | |
|   QualType T;
 | |
| 
 | |
|   switch (i) {
 | |
|     case kCFNumberCharType:     T = Ctx.CharTy;     break;
 | |
|     case kCFNumberShortType:    T = Ctx.ShortTy;    break;
 | |
|     case kCFNumberIntType:      T = Ctx.IntTy;      break;
 | |
|     case kCFNumberLongType:     T = Ctx.LongTy;     break;
 | |
|     case kCFNumberLongLongType: T = Ctx.LongLongTy; break;
 | |
|     case kCFNumberFloatType:    T = Ctx.FloatTy;    break;
 | |
|     case kCFNumberDoubleType:   T = Ctx.DoubleTy;   break;
 | |
|     case kCFNumberCFIndexType:
 | |
|     case kCFNumberNSIntegerType:
 | |
|     case kCFNumberCGFloatType:
 | |
|       // FIXME: We need a way to map from names to Type*.
 | |
|     default:
 | |
|       return None;
 | |
|   }
 | |
| 
 | |
|   return Ctx.getTypeSize(T);
 | |
| }
 | |
| 
 | |
| #if 0
 | |
| static const char* GetCFNumberTypeStr(uint64_t i) {
 | |
|   static const char* Names[] = {
 | |
|     "kCFNumberSInt8Type",
 | |
|     "kCFNumberSInt16Type",
 | |
|     "kCFNumberSInt32Type",
 | |
|     "kCFNumberSInt64Type",
 | |
|     "kCFNumberFloat32Type",
 | |
|     "kCFNumberFloat64Type",
 | |
|     "kCFNumberCharType",
 | |
|     "kCFNumberShortType",
 | |
|     "kCFNumberIntType",
 | |
|     "kCFNumberLongType",
 | |
|     "kCFNumberLongLongType",
 | |
|     "kCFNumberFloatType",
 | |
|     "kCFNumberDoubleType",
 | |
|     "kCFNumberCFIndexType",
 | |
|     "kCFNumberNSIntegerType",
 | |
|     "kCFNumberCGFloatType"
 | |
|   };
 | |
| 
 | |
|   return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType";
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE,
 | |
|                                          CheckerContext &C) const {
 | |
|   ProgramStateRef state = C.getState();
 | |
|   const FunctionDecl *FD = C.getCalleeDecl(CE);
 | |
|   if (!FD)
 | |
|     return;
 | |
| 
 | |
|   ASTContext &Ctx = C.getASTContext();
 | |
|   if (!II)
 | |
|     II = &Ctx.Idents.get("CFNumberCreate");
 | |
| 
 | |
|   if (FD->getIdentifier() != II || CE->getNumArgs() != 3)
 | |
|     return;
 | |
| 
 | |
|   // Get the value of the "theType" argument.
 | |
|   const LocationContext *LCtx = C.getLocationContext();
 | |
|   SVal TheTypeVal = state->getSVal(CE->getArg(1), LCtx);
 | |
| 
 | |
|   // FIXME: We really should allow ranges of valid theType values, and
 | |
|   //   bifurcate the state appropriately.
 | |
|   Optional<nonloc::ConcreteInt> V = TheTypeVal.getAs<nonloc::ConcreteInt>();
 | |
|   if (!V)
 | |
|     return;
 | |
| 
 | |
|   uint64_t NumberKind = V->getValue().getLimitedValue();
 | |
|   Optional<uint64_t> OptTargetSize = GetCFNumberSize(Ctx, NumberKind);
 | |
| 
 | |
|   // FIXME: In some cases we can emit an error.
 | |
|   if (!OptTargetSize)
 | |
|     return;
 | |
| 
 | |
|   uint64_t TargetSize = *OptTargetSize;
 | |
| 
 | |
|   // Look at the value of the integer being passed by reference.  Essentially
 | |
|   // we want to catch cases where the value passed in is not equal to the
 | |
|   // size of the type being created.
 | |
|   SVal TheValueExpr = state->getSVal(CE->getArg(2), LCtx);
 | |
| 
 | |
|   // FIXME: Eventually we should handle arbitrary locations.  We can do this
 | |
|   //  by having an enhanced memory model that does low-level typing.
 | |
|   Optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>();
 | |
|   if (!LV)
 | |
|     return;
 | |
| 
 | |
|   const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts());
 | |
|   if (!R)
 | |
|     return;
 | |
| 
 | |
|   QualType T = Ctx.getCanonicalType(R->getValueType());
 | |
| 
 | |
|   // FIXME: If the pointee isn't an integer type, should we flag a warning?
 | |
|   //  People can do weird stuff with pointers.
 | |
| 
 | |
|   if (!T->isIntegralOrEnumerationType())
 | |
|     return;
 | |
| 
 | |
|   uint64_t SourceSize = Ctx.getTypeSize(T);
 | |
| 
 | |
|   // CHECK: is SourceSize == TargetSize
 | |
|   if (SourceSize == TargetSize)
 | |
|     return;
 | |
| 
 | |
|   // Generate an error.  Only generate a sink error node
 | |
|   // if 'SourceSize < TargetSize'; otherwise generate a non-fatal error node.
 | |
|   //
 | |
|   // FIXME: We can actually create an abstract "CFNumber" object that has
 | |
|   //  the bits initialized to the provided values.
 | |
|   //
 | |
|   ExplodedNode *N = SourceSize < TargetSize ? C.generateErrorNode()
 | |
|                                             : C.generateNonFatalErrorNode();
 | |
|   if (N) {
 | |
|     SmallString<128> sbuf;
 | |
|     llvm::raw_svector_ostream os(sbuf);
 | |
| 
 | |
|     os << (SourceSize == 8 ? "An " : "A ")
 | |
|        << SourceSize << " bit integer is used to initialize a CFNumber "
 | |
|                         "object that represents "
 | |
|        << (TargetSize == 8 ? "an " : "a ")
 | |
|        << TargetSize << " bit integer. ";
 | |
| 
 | |
|     if (SourceSize < TargetSize)
 | |
|       os << (TargetSize - SourceSize)
 | |
|       << " bits of the CFNumber value will be garbage." ;
 | |
|     else
 | |
|       os << (SourceSize - TargetSize)
 | |
|       << " bits of the input integer will be lost.";
 | |
| 
 | |
|     if (!BT)
 | |
|       BT.reset(new APIMisuse(this, "Bad use of CFNumberCreate"));
 | |
| 
 | |
|     auto report = llvm::make_unique<BugReport>(*BT, os.str(), N);
 | |
|     report->addRange(CE->getArg(2)->getSourceRange());
 | |
|     C.emitReport(std::move(report));
 | |
|   }
 | |
| }
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // CFRetain/CFRelease/CFMakeCollectable/CFAutorelease checking for null arguments.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| namespace {
 | |
| class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > {
 | |
|   mutable std::unique_ptr<APIMisuse> BT;
 | |
|   mutable IdentifierInfo *Retain, *Release, *MakeCollectable, *Autorelease;
 | |
| public:
 | |
|   CFRetainReleaseChecker()
 | |
|       : Retain(nullptr), Release(nullptr), MakeCollectable(nullptr),
 | |
|         Autorelease(nullptr) {}
 | |
|   void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
 | |
| };
 | |
| } // end anonymous namespace
 | |
| 
 | |
| 
 | |
| void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE,
 | |
|                                           CheckerContext &C) const {
 | |
|   // If the CallExpr doesn't have exactly 1 argument just give up checking.
 | |
|   if (CE->getNumArgs() != 1)
 | |
|     return;
 | |
| 
 | |
|   ProgramStateRef state = C.getState();
 | |
|   const FunctionDecl *FD = C.getCalleeDecl(CE);
 | |
|   if (!FD)
 | |
|     return;
 | |
| 
 | |
|   if (!BT) {
 | |
|     ASTContext &Ctx = C.getASTContext();
 | |
|     Retain = &Ctx.Idents.get("CFRetain");
 | |
|     Release = &Ctx.Idents.get("CFRelease");
 | |
|     MakeCollectable = &Ctx.Idents.get("CFMakeCollectable");
 | |
|     Autorelease = &Ctx.Idents.get("CFAutorelease");
 | |
|     BT.reset(new APIMisuse(
 | |
|         this, "null passed to CF memory management function"));
 | |
|   }
 | |
| 
 | |
|   // Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease.
 | |
|   const IdentifierInfo *FuncII = FD->getIdentifier();
 | |
|   if (!(FuncII == Retain || FuncII == Release || FuncII == MakeCollectable ||
 | |
|         FuncII == Autorelease))
 | |
|     return;
 | |
| 
 | |
|   // FIXME: The rest of this just checks that the argument is non-null.
 | |
|   // It should probably be refactored and combined with NonNullParamChecker.
 | |
| 
 | |
|   // Get the argument's value.
 | |
|   const Expr *Arg = CE->getArg(0);
 | |
|   SVal ArgVal = state->getSVal(Arg, C.getLocationContext());
 | |
|   Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>();
 | |
|   if (!DefArgVal)
 | |
|     return;
 | |
| 
 | |
|   // Get a NULL value.
 | |
|   SValBuilder &svalBuilder = C.getSValBuilder();
 | |
|   DefinedSVal zero =
 | |
|       svalBuilder.makeZeroVal(Arg->getType()).castAs<DefinedSVal>();
 | |
| 
 | |
|   // Make an expression asserting that they're equal.
 | |
|   DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal);
 | |
| 
 | |
|   // Are they equal?
 | |
|   ProgramStateRef stateTrue, stateFalse;
 | |
|   std::tie(stateTrue, stateFalse) = state->assume(ArgIsNull);
 | |
| 
 | |
|   if (stateTrue && !stateFalse) {
 | |
|     ExplodedNode *N = C.generateErrorNode(stateTrue);
 | |
|     if (!N)
 | |
|       return;
 | |
| 
 | |
|     const char *description;
 | |
|     if (FuncII == Retain)
 | |
|       description = "Null pointer argument in call to CFRetain";
 | |
|     else if (FuncII == Release)
 | |
|       description = "Null pointer argument in call to CFRelease";
 | |
|     else if (FuncII == MakeCollectable)
 | |
|       description = "Null pointer argument in call to CFMakeCollectable";
 | |
|     else if (FuncII == Autorelease)
 | |
|       description = "Null pointer argument in call to CFAutorelease";
 | |
|     else
 | |
|       llvm_unreachable("impossible case");
 | |
| 
 | |
|     auto report = llvm::make_unique<BugReport>(*BT, description, N);
 | |
|     report->addRange(Arg->getSourceRange());
 | |
|     bugreporter::trackNullOrUndefValue(N, Arg, *report);
 | |
|     C.emitReport(std::move(report));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // From here on, we know the argument is non-null.
 | |
|   C.addTransition(stateFalse);
 | |
| }
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| namespace {
 | |
| class ClassReleaseChecker : public Checker<check::PreObjCMessage> {
 | |
|   mutable Selector releaseS;
 | |
|   mutable Selector retainS;
 | |
|   mutable Selector autoreleaseS;
 | |
|   mutable Selector drainS;
 | |
|   mutable std::unique_ptr<BugType> BT;
 | |
| 
 | |
| public:
 | |
|   void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
 | |
| };
 | |
| }
 | |
| 
 | |
| void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
 | |
|                                               CheckerContext &C) const {
 | |
| 
 | |
|   if (!BT) {
 | |
|     BT.reset(new APIMisuse(
 | |
|         this, "message incorrectly sent to class instead of class instance"));
 | |
| 
 | |
|     ASTContext &Ctx = C.getASTContext();
 | |
|     releaseS = GetNullarySelector("release", Ctx);
 | |
|     retainS = GetNullarySelector("retain", Ctx);
 | |
|     autoreleaseS = GetNullarySelector("autorelease", Ctx);
 | |
|     drainS = GetNullarySelector("drain", Ctx);
 | |
|   }
 | |
| 
 | |
|   if (msg.isInstanceMessage())
 | |
|     return;
 | |
|   const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
 | |
|   assert(Class);
 | |
| 
 | |
|   Selector S = msg.getSelector();
 | |
|   if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
 | |
|     return;
 | |
| 
 | |
|   if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
 | |
|     SmallString<200> buf;
 | |
|     llvm::raw_svector_ostream os(buf);
 | |
| 
 | |
|     os << "The '";
 | |
|     S.print(os);
 | |
|     os << "' message should be sent to instances "
 | |
|           "of class '" << Class->getName()
 | |
|        << "' and not the class directly";
 | |
| 
 | |
|     auto report = llvm::make_unique<BugReport>(*BT, os.str(), N);
 | |
|     report->addRange(msg.getSourceRange());
 | |
|     C.emitReport(std::move(report));
 | |
|   }
 | |
| }
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // Check for passing non-Objective-C types to variadic methods that expect
 | |
| // only Objective-C types.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| namespace {
 | |
| class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> {
 | |
|   mutable Selector arrayWithObjectsS;
 | |
|   mutable Selector dictionaryWithObjectsAndKeysS;
 | |
|   mutable Selector setWithObjectsS;
 | |
|   mutable Selector orderedSetWithObjectsS;
 | |
|   mutable Selector initWithObjectsS;
 | |
|   mutable Selector initWithObjectsAndKeysS;
 | |
|   mutable std::unique_ptr<BugType> BT;
 | |
| 
 | |
|   bool isVariadicMessage(const ObjCMethodCall &msg) const;
 | |
| 
 | |
| public:
 | |
|   void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
 | |
| };
 | |
| }
 | |
| 
 | |
| /// isVariadicMessage - Returns whether the given message is a variadic message,
 | |
| /// where all arguments must be Objective-C types.
 | |
| bool
 | |
| VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const {
 | |
|   const ObjCMethodDecl *MD = msg.getDecl();
 | |
| 
 | |
|   if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))
 | |
|     return false;
 | |
| 
 | |
|   Selector S = msg.getSelector();
 | |
| 
 | |
|   if (msg.isInstanceMessage()) {
 | |
|     // FIXME: Ideally we'd look at the receiver interface here, but that's not
 | |
|     // useful for init, because alloc returns 'id'. In theory, this could lead
 | |
|     // to false positives, for example if there existed a class that had an
 | |
|     // initWithObjects: implementation that does accept non-Objective-C pointer
 | |
|     // types, but the chance of that happening is pretty small compared to the
 | |
|     // gains that this analysis gives.
 | |
|     const ObjCInterfaceDecl *Class = MD->getClassInterface();
 | |
| 
 | |
|     switch (findKnownClass(Class)) {
 | |
|     case FC_NSArray:
 | |
|     case FC_NSOrderedSet:
 | |
|     case FC_NSSet:
 | |
|       return S == initWithObjectsS;
 | |
|     case FC_NSDictionary:
 | |
|       return S == initWithObjectsAndKeysS;
 | |
|     default:
 | |
|       return false;
 | |
|     }
 | |
|   } else {
 | |
|     const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
 | |
| 
 | |
|     switch (findKnownClass(Class)) {
 | |
|       case FC_NSArray:
 | |
|         return S == arrayWithObjectsS;
 | |
|       case FC_NSOrderedSet:
 | |
|         return S == orderedSetWithObjectsS;
 | |
|       case FC_NSSet:
 | |
|         return S == setWithObjectsS;
 | |
|       case FC_NSDictionary:
 | |
|         return S == dictionaryWithObjectsAndKeysS;
 | |
|       default:
 | |
|         return false;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
 | |
|                                                     CheckerContext &C) const {
 | |
|   if (!BT) {
 | |
|     BT.reset(new APIMisuse(this,
 | |
|                            "Arguments passed to variadic method aren't all "
 | |
|                            "Objective-C pointer types"));
 | |
| 
 | |
|     ASTContext &Ctx = C.getASTContext();
 | |
|     arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);
 | |
|     dictionaryWithObjectsAndKeysS =
 | |
|       GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);
 | |
|     setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);
 | |
|     orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx);
 | |
| 
 | |
|     initWithObjectsS = GetUnarySelector("initWithObjects", Ctx);
 | |
|     initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx);
 | |
|   }
 | |
| 
 | |
|   if (!isVariadicMessage(msg))
 | |
|       return;
 | |
| 
 | |
|   // We are not interested in the selector arguments since they have
 | |
|   // well-defined types, so the compiler will issue a warning for them.
 | |
|   unsigned variadicArgsBegin = msg.getSelector().getNumArgs();
 | |
| 
 | |
|   // We're not interested in the last argument since it has to be nil or the
 | |
|   // compiler would have issued a warning for it elsewhere.
 | |
|   unsigned variadicArgsEnd = msg.getNumArgs() - 1;
 | |
| 
 | |
|   if (variadicArgsEnd <= variadicArgsBegin)
 | |
|     return;
 | |
| 
 | |
|   // Verify that all arguments have Objective-C types.
 | |
|   Optional<ExplodedNode*> errorNode;
 | |
| 
 | |
|   for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {
 | |
|     QualType ArgTy = msg.getArgExpr(I)->getType();
 | |
|     if (ArgTy->isObjCObjectPointerType())
 | |
|       continue;
 | |
| 
 | |
|     // Block pointers are treaded as Objective-C pointers.
 | |
|     if (ArgTy->isBlockPointerType())
 | |
|       continue;
 | |
| 
 | |
|     // Ignore pointer constants.
 | |
|     if (msg.getArgSVal(I).getAs<loc::ConcreteInt>())
 | |
|       continue;
 | |
| 
 | |
|     // Ignore pointer types annotated with 'NSObject' attribute.
 | |
|     if (C.getASTContext().isObjCNSObjectType(ArgTy))
 | |
|       continue;
 | |
| 
 | |
|     // Ignore CF references, which can be toll-free bridged.
 | |
|     if (coreFoundation::isCFObjectRef(ArgTy))
 | |
|       continue;
 | |
| 
 | |
|     // Generate only one error node to use for all bug reports.
 | |
|     if (!errorNode.hasValue())
 | |
|       errorNode = C.generateNonFatalErrorNode();
 | |
| 
 | |
|     if (!errorNode.getValue())
 | |
|       continue;
 | |
| 
 | |
|     SmallString<128> sbuf;
 | |
|     llvm::raw_svector_ostream os(sbuf);
 | |
| 
 | |
|     StringRef TypeName = GetReceiverInterfaceName(msg);
 | |
|     if (!TypeName.empty())
 | |
|       os << "Argument to '" << TypeName << "' method '";
 | |
|     else
 | |
|       os << "Argument to method '";
 | |
| 
 | |
|     msg.getSelector().print(os);
 | |
|     os << "' should be an Objective-C pointer type, not '";
 | |
|     ArgTy.print(os, C.getLangOpts());
 | |
|     os << "'";
 | |
| 
 | |
|     auto R = llvm::make_unique<BugReport>(*BT, os.str(), errorNode.getValue());
 | |
|     R->addRange(msg.getArgSourceRange(I));
 | |
|     C.emitReport(std::move(R));
 | |
|   }
 | |
| }
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // Improves the modeling of loops over Cocoa collections.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| // The map from container symbol to the container count symbol.
 | |
| // We currently will remember the last countainer count symbol encountered.
 | |
| REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef)
 | |
| REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool)
 | |
| 
 | |
| namespace {
 | |
| class ObjCLoopChecker
 | |
|   : public Checker<check::PostStmt<ObjCForCollectionStmt>,
 | |
|                    check::PostObjCMessage,
 | |
|                    check::DeadSymbols,
 | |
|                    check::PointerEscape > {
 | |
|   mutable IdentifierInfo *CountSelectorII;
 | |
| 
 | |
|   bool isCollectionCountMethod(const ObjCMethodCall &M,
 | |
|                                CheckerContext &C) const;
 | |
| 
 | |
| public:
 | |
|   ObjCLoopChecker() : CountSelectorII(nullptr) {}
 | |
|   void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const;
 | |
|   void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
 | |
|   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
 | |
|   ProgramStateRef checkPointerEscape(ProgramStateRef State,
 | |
|                                      const InvalidatedSymbols &Escaped,
 | |
|                                      const CallEvent *Call,
 | |
|                                      PointerEscapeKind Kind) const;
 | |
| };
 | |
| }
 | |
| 
 | |
| static bool isKnownNonNilCollectionType(QualType T) {
 | |
|   const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();
 | |
|   if (!PT)
 | |
|     return false;
 | |
| 
 | |
|   const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();
 | |
|   if (!ID)
 | |
|     return false;
 | |
| 
 | |
|   switch (findKnownClass(ID)) {
 | |
|   case FC_NSArray:
 | |
|   case FC_NSDictionary:
 | |
|   case FC_NSEnumerator:
 | |
|   case FC_NSOrderedSet:
 | |
|   case FC_NSSet:
 | |
|     return true;
 | |
|   default:
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /// Assumes that the collection is non-nil.
 | |
| ///
 | |
| /// If the collection is known to be nil, returns NULL to indicate an infeasible
 | |
| /// path.
 | |
| static ProgramStateRef checkCollectionNonNil(CheckerContext &C,
 | |
|                                              ProgramStateRef State,
 | |
|                                              const ObjCForCollectionStmt *FCS) {
 | |
|   if (!State)
 | |
|     return nullptr;
 | |
| 
 | |
|   SVal CollectionVal = C.getSVal(FCS->getCollection());
 | |
|   Optional<DefinedSVal> KnownCollection = CollectionVal.getAs<DefinedSVal>();
 | |
|   if (!KnownCollection)
 | |
|     return State;
 | |
| 
 | |
|   ProgramStateRef StNonNil, StNil;
 | |
|   std::tie(StNonNil, StNil) = State->assume(*KnownCollection);
 | |
|   if (StNil && !StNonNil) {
 | |
|     // The collection is nil. This path is infeasible.
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return StNonNil;
 | |
| }
 | |
| 
 | |
| /// Assumes that the collection elements are non-nil.
 | |
| ///
 | |
| /// This only applies if the collection is one of those known not to contain
 | |
| /// nil values.
 | |
| static ProgramStateRef checkElementNonNil(CheckerContext &C,
 | |
|                                           ProgramStateRef State,
 | |
|                                           const ObjCForCollectionStmt *FCS) {
 | |
|   if (!State)
 | |
|     return nullptr;
 | |
| 
 | |
|   // See if the collection is one where we /know/ the elements are non-nil.
 | |
|   if (!isKnownNonNilCollectionType(FCS->getCollection()->getType()))
 | |
|     return State;
 | |
| 
 | |
|   const LocationContext *LCtx = C.getLocationContext();
 | |
|   const Stmt *Element = FCS->getElement();
 | |
| 
 | |
|   // FIXME: Copied from ExprEngineObjC.
 | |
|   Optional<Loc> ElementLoc;
 | |
|   if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) {
 | |
|     const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl());
 | |
|     assert(ElemDecl->getInit() == nullptr);
 | |
|     ElementLoc = State->getLValue(ElemDecl, LCtx);
 | |
|   } else {
 | |
|     ElementLoc = State->getSVal(Element, LCtx).getAs<Loc>();
 | |
|   }
 | |
| 
 | |
|   if (!ElementLoc)
 | |
|     return State;
 | |
| 
 | |
|   // Go ahead and assume the value is non-nil.
 | |
|   SVal Val = State->getSVal(*ElementLoc);
 | |
|   return State->assume(Val.castAs<DefinedOrUnknownSVal>(), true);
 | |
| }
 | |
| 
 | |
| /// Returns NULL state if the collection is known to contain elements
 | |
| /// (or is known not to contain elements if the Assumption parameter is false.)
 | |
| static ProgramStateRef
 | |
| assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,
 | |
|                          SymbolRef CollectionS, bool Assumption) {
 | |
|   if (!State || !CollectionS)
 | |
|     return State;
 | |
| 
 | |
|   const SymbolRef *CountS = State->get<ContainerCountMap>(CollectionS);
 | |
|   if (!CountS) {
 | |
|     const bool *KnownNonEmpty = State->get<ContainerNonEmptyMap>(CollectionS);
 | |
|     if (!KnownNonEmpty)
 | |
|       return State->set<ContainerNonEmptyMap>(CollectionS, Assumption);
 | |
|     return (Assumption == *KnownNonEmpty) ? State : nullptr;
 | |
|   }
 | |
| 
 | |
|   SValBuilder &SvalBuilder = C.getSValBuilder();
 | |
|   SVal CountGreaterThanZeroVal =
 | |
|     SvalBuilder.evalBinOp(State, BO_GT,
 | |
|                           nonloc::SymbolVal(*CountS),
 | |
|                           SvalBuilder.makeIntVal(0, (*CountS)->getType()),
 | |
|                           SvalBuilder.getConditionType());
 | |
|   Optional<DefinedSVal> CountGreaterThanZero =
 | |
|     CountGreaterThanZeroVal.getAs<DefinedSVal>();
 | |
|   if (!CountGreaterThanZero) {
 | |
|     // The SValBuilder cannot construct a valid SVal for this condition.
 | |
|     // This means we cannot properly reason about it.
 | |
|     return State;
 | |
|   }
 | |
| 
 | |
|   return State->assume(*CountGreaterThanZero, Assumption);
 | |
| }
 | |
| 
 | |
| static ProgramStateRef
 | |
| assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,
 | |
|                          const ObjCForCollectionStmt *FCS,
 | |
|                          bool Assumption) {
 | |
|   if (!State)
 | |
|     return nullptr;
 | |
| 
 | |
|   SymbolRef CollectionS =
 | |
|     State->getSVal(FCS->getCollection(), C.getLocationContext()).getAsSymbol();
 | |
|   return assumeCollectionNonEmpty(C, State, CollectionS, Assumption);
 | |
| }
 | |
| 
 | |
| 
 | |
| /// If the fist block edge is a back edge, we are reentering the loop.
 | |
| static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N,
 | |
|                                              const ObjCForCollectionStmt *FCS) {
 | |
|   if (!N)
 | |
|     return false;
 | |
| 
 | |
|   ProgramPoint P = N->getLocation();
 | |
|   if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) {
 | |
|     if (BE->getSrc()->getLoopTarget() == FCS)
 | |
|       return true;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Keep looking for a block edge.
 | |
|   for (ExplodedNode::const_pred_iterator I = N->pred_begin(),
 | |
|                                          E = N->pred_end(); I != E; ++I) {
 | |
|     if (alreadyExecutedAtLeastOneLoopIteration(*I, FCS))
 | |
|       return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS,
 | |
|                                     CheckerContext &C) const {
 | |
|   ProgramStateRef State = C.getState();
 | |
| 
 | |
|   // Check if this is the branch for the end of the loop.
 | |
|   SVal CollectionSentinel = C.getSVal(FCS);
 | |
|   if (CollectionSentinel.isZeroConstant()) {
 | |
|     if (!alreadyExecutedAtLeastOneLoopIteration(C.getPredecessor(), FCS))
 | |
|       State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/false);
 | |
| 
 | |
|   // Otherwise, this is a branch that goes through the loop body.
 | |
|   } else {
 | |
|     State = checkCollectionNonNil(C, State, FCS);
 | |
|     State = checkElementNonNil(C, State, FCS);
 | |
|     State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true);
 | |
|   }
 | |
| 
 | |
|   if (!State)
 | |
|     C.generateSink(C.getState(), C.getPredecessor());
 | |
|   else if (State != C.getState())
 | |
|     C.addTransition(State);
 | |
| }
 | |
| 
 | |
| bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M,
 | |
|                                               CheckerContext &C) const {
 | |
|   Selector S = M.getSelector();
 | |
|   // Initialize the identifiers on first use.
 | |
|   if (!CountSelectorII)
 | |
|     CountSelectorII = &C.getASTContext().Idents.get("count");
 | |
| 
 | |
|   // If the method returns collection count, record the value.
 | |
|   if (S.isUnarySelector() &&
 | |
|       (S.getIdentifierInfoForSlot(0) == CountSelectorII))
 | |
|     return true;
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M,
 | |
|                                            CheckerContext &C) const {
 | |
|   if (!M.isInstanceMessage())
 | |
|     return;
 | |
| 
 | |
|   const ObjCInterfaceDecl *ClassID = M.getReceiverInterface();
 | |
|   if (!ClassID)
 | |
|     return;
 | |
| 
 | |
|   FoundationClass Class = findKnownClass(ClassID);
 | |
|   if (Class != FC_NSDictionary &&
 | |
|       Class != FC_NSArray &&
 | |
|       Class != FC_NSSet &&
 | |
|       Class != FC_NSOrderedSet)
 | |
|     return;
 | |
| 
 | |
|   SymbolRef ContainerS = M.getReceiverSVal().getAsSymbol();
 | |
|   if (!ContainerS)
 | |
|     return;
 | |
| 
 | |
|   // If we are processing a call to "count", get the symbolic value returned by
 | |
|   // a call to "count" and add it to the map.
 | |
|   if (!isCollectionCountMethod(M, C))
 | |
|     return;
 | |
| 
 | |
|   const Expr *MsgExpr = M.getOriginExpr();
 | |
|   SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol();
 | |
|   if (CountS) {
 | |
|     ProgramStateRef State = C.getState();
 | |
| 
 | |
|     C.getSymbolManager().addSymbolDependency(ContainerS, CountS);
 | |
|     State = State->set<ContainerCountMap>(ContainerS, CountS);
 | |
| 
 | |
|     if (const bool *NonEmpty = State->get<ContainerNonEmptyMap>(ContainerS)) {
 | |
|       State = State->remove<ContainerNonEmptyMap>(ContainerS);
 | |
|       State = assumeCollectionNonEmpty(C, State, ContainerS, *NonEmpty);
 | |
|     }
 | |
| 
 | |
|     C.addTransition(State);
 | |
|   }
 | |
|   return;
 | |
| }
 | |
| 
 | |
| static SymbolRef getMethodReceiverIfKnownImmutable(const CallEvent *Call) {
 | |
|   const ObjCMethodCall *Message = dyn_cast_or_null<ObjCMethodCall>(Call);
 | |
|   if (!Message)
 | |
|     return nullptr;
 | |
| 
 | |
|   const ObjCMethodDecl *MD = Message->getDecl();
 | |
|   if (!MD)
 | |
|     return nullptr;
 | |
| 
 | |
|   const ObjCInterfaceDecl *StaticClass;
 | |
|   if (isa<ObjCProtocolDecl>(MD->getDeclContext())) {
 | |
|     // We can't find out where the method was declared without doing more work.
 | |
|     // Instead, see if the receiver is statically typed as a known immutable
 | |
|     // collection.
 | |
|     StaticClass = Message->getOriginExpr()->getReceiverInterface();
 | |
|   } else {
 | |
|     StaticClass = MD->getClassInterface();
 | |
|   }
 | |
| 
 | |
|   if (!StaticClass)
 | |
|     return nullptr;
 | |
| 
 | |
|   switch (findKnownClass(StaticClass, /*IncludeSuper=*/false)) {
 | |
|   case FC_None:
 | |
|     return nullptr;
 | |
|   case FC_NSArray:
 | |
|   case FC_NSDictionary:
 | |
|   case FC_NSEnumerator:
 | |
|   case FC_NSNull:
 | |
|   case FC_NSOrderedSet:
 | |
|   case FC_NSSet:
 | |
|   case FC_NSString:
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   return Message->getReceiverSVal().getAsSymbol();
 | |
| }
 | |
| 
 | |
| ProgramStateRef
 | |
| ObjCLoopChecker::checkPointerEscape(ProgramStateRef State,
 | |
|                                     const InvalidatedSymbols &Escaped,
 | |
|                                     const CallEvent *Call,
 | |
|                                     PointerEscapeKind Kind) const {
 | |
|   SymbolRef ImmutableReceiver = getMethodReceiverIfKnownImmutable(Call);
 | |
| 
 | |
|   // Remove the invalidated symbols form the collection count map.
 | |
|   for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
 | |
|        E = Escaped.end();
 | |
|        I != E; ++I) {
 | |
|     SymbolRef Sym = *I;
 | |
| 
 | |
|     // Don't invalidate this symbol's count if we know the method being called
 | |
|     // is declared on an immutable class. This isn't completely correct if the
 | |
|     // receiver is also passed as an argument, but in most uses of NSArray,
 | |
|     // NSDictionary, etc. this isn't likely to happen in a dangerous way.
 | |
|     if (Sym == ImmutableReceiver)
 | |
|       continue;
 | |
| 
 | |
|     // The symbol escaped. Pessimistically, assume that the count could have
 | |
|     // changed.
 | |
|     State = State->remove<ContainerCountMap>(Sym);
 | |
|     State = State->remove<ContainerNonEmptyMap>(Sym);
 | |
|   }
 | |
|   return State;
 | |
| }
 | |
| 
 | |
| void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper,
 | |
|                                        CheckerContext &C) const {
 | |
|   ProgramStateRef State = C.getState();
 | |
| 
 | |
|   // Remove the dead symbols from the collection count map.
 | |
|   ContainerCountMapTy Tracked = State->get<ContainerCountMap>();
 | |
|   for (ContainerCountMapTy::iterator I = Tracked.begin(),
 | |
|                                      E = Tracked.end(); I != E; ++I) {
 | |
|     SymbolRef Sym = I->first;
 | |
|     if (SymReaper.isDead(Sym)) {
 | |
|       State = State->remove<ContainerCountMap>(Sym);
 | |
|       State = State->remove<ContainerNonEmptyMap>(Sym);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   C.addTransition(State);
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| /// \class ObjCNonNilReturnValueChecker
 | |
| /// \brief The checker restricts the return values of APIs known to
 | |
| /// never (or almost never) return 'nil'.
 | |
| class ObjCNonNilReturnValueChecker
 | |
|   : public Checker<check::PostObjCMessage,
 | |
|                    check::PostStmt<ObjCArrayLiteral>,
 | |
|                    check::PostStmt<ObjCDictionaryLiteral>,
 | |
|                    check::PostStmt<ObjCBoxedExpr> > {
 | |
|     mutable bool Initialized;
 | |
|     mutable Selector ObjectAtIndex;
 | |
|     mutable Selector ObjectAtIndexedSubscript;
 | |
|     mutable Selector NullSelector;
 | |
| 
 | |
| public:
 | |
|   ObjCNonNilReturnValueChecker() : Initialized(false) {}
 | |
| 
 | |
|   ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr,
 | |
|                                       ProgramStateRef State,
 | |
|                                       CheckerContext &C) const;
 | |
|   void assumeExprIsNonNull(const Expr *E, CheckerContext &C) const {
 | |
|     C.addTransition(assumeExprIsNonNull(E, C.getState(), C));
 | |
|   }
 | |
| 
 | |
|   void checkPostStmt(const ObjCArrayLiteral *E, CheckerContext &C) const {
 | |
|     assumeExprIsNonNull(E, C);
 | |
|   }
 | |
|   void checkPostStmt(const ObjCDictionaryLiteral *E, CheckerContext &C) const {
 | |
|     assumeExprIsNonNull(E, C);
 | |
|   }
 | |
|   void checkPostStmt(const ObjCBoxedExpr *E, CheckerContext &C) const {
 | |
|     assumeExprIsNonNull(E, C);
 | |
|   }
 | |
| 
 | |
|   void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
 | |
| };
 | |
| }
 | |
| 
 | |
| ProgramStateRef
 | |
| ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr,
 | |
|                                                   ProgramStateRef State,
 | |
|                                                   CheckerContext &C) const {
 | |
|   SVal Val = State->getSVal(NonNullExpr, C.getLocationContext());
 | |
|   if (Optional<DefinedOrUnknownSVal> DV = Val.getAs<DefinedOrUnknownSVal>())
 | |
|     return State->assume(*DV, true);
 | |
|   return State;
 | |
| }
 | |
| 
 | |
| void ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M,
 | |
|                                                         CheckerContext &C)
 | |
|                                                         const {
 | |
|   ProgramStateRef State = C.getState();
 | |
| 
 | |
|   if (!Initialized) {
 | |
|     ASTContext &Ctx = C.getASTContext();
 | |
|     ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx);
 | |
|     ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx);
 | |
|     NullSelector = GetNullarySelector("null", Ctx);
 | |
|   }
 | |
| 
 | |
|   // Check the receiver type.
 | |
|   if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) {
 | |
| 
 | |
|     // Assume that object returned from '[self init]' or '[super init]' is not
 | |
|     // 'nil' if we are processing an inlined function/method.
 | |
|     //
 | |
|     // A defensive callee will (and should) check if the object returned by
 | |
|     // '[super init]' is 'nil' before doing it's own initialization. However,
 | |
|     // since 'nil' is rarely returned in practice, we should not warn when the
 | |
|     // caller to the defensive constructor uses the object in contexts where
 | |
|     // 'nil' is not accepted.
 | |
|     if (!C.inTopFrame() && M.getDecl() &&
 | |
|         M.getDecl()->getMethodFamily() == OMF_init &&
 | |
|         M.isReceiverSelfOrSuper()) {
 | |
|       State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
 | |
|     }
 | |
| 
 | |
|     FoundationClass Cl = findKnownClass(Interface);
 | |
| 
 | |
|     // Objects returned from
 | |
|     // [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript]
 | |
|     // are never 'nil'.
 | |
|     if (Cl == FC_NSArray || Cl == FC_NSOrderedSet) {
 | |
|       Selector Sel = M.getSelector();
 | |
|       if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript) {
 | |
|         // Go ahead and assume the value is non-nil.
 | |
|         State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Objects returned from [NSNull null] are not nil.
 | |
|     if (Cl == FC_NSNull) {
 | |
|       if (M.getSelector() == NullSelector) {
 | |
|         // Go ahead and assume the value is non-nil.
 | |
|         State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   C.addTransition(State);
 | |
| }
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // Check registration.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| void ento::registerNilArgChecker(CheckerManager &mgr) {
 | |
|   mgr.registerChecker<NilArgChecker>();
 | |
| }
 | |
| 
 | |
| void ento::registerCFNumberCreateChecker(CheckerManager &mgr) {
 | |
|   mgr.registerChecker<CFNumberCreateChecker>();
 | |
| }
 | |
| 
 | |
| void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
 | |
|   mgr.registerChecker<CFRetainReleaseChecker>();
 | |
| }
 | |
| 
 | |
| void ento::registerClassReleaseChecker(CheckerManager &mgr) {
 | |
|   mgr.registerChecker<ClassReleaseChecker>();
 | |
| }
 | |
| 
 | |
| void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
 | |
|   mgr.registerChecker<VariadicMethodTypeChecker>();
 | |
| }
 | |
| 
 | |
| void ento::registerObjCLoopChecker(CheckerManager &mgr) {
 | |
|   mgr.registerChecker<ObjCLoopChecker>();
 | |
| }
 | |
| 
 | |
| void
 | |
| ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) {
 | |
|   mgr.registerChecker<ObjCNonNilReturnValueChecker>();
 | |
| }
 |