BasicObjCFoundationChecks.cpp revision 243830
1218887Sdim//== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- C++ -*-- 2218887Sdim// 3218887Sdim// The LLVM Compiler Infrastructure 4218887Sdim// 5218887Sdim// This file is distributed under the University of Illinois Open Source 6218887Sdim// License. See LICENSE.TXT for details. 7218887Sdim// 8218887Sdim//===----------------------------------------------------------------------===// 9218887Sdim// 10218887Sdim// This file defines BasicObjCFoundationChecks, a class that encapsulates 11218887Sdim// a set of simple checks to run on Objective-C code using Apple's Foundation 12218887Sdim// classes. 13218887Sdim// 14218887Sdim//===----------------------------------------------------------------------===// 15218887Sdim 16218887Sdim#include "ClangSACheckers.h" 17221345Sdim#include "clang/Analysis/DomainSpecific/CocoaConventions.h" 18221345Sdim#include "clang/StaticAnalyzer/Core/Checker.h" 19218887Sdim#include "clang/StaticAnalyzer/Core/CheckerManager.h" 20239462Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 21219077Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 22218887Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" 23218887Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" 24226633Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 25218887Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 26218887Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" 27218887Sdim#include "clang/AST/DeclObjC.h" 28218887Sdim#include "clang/AST/Expr.h" 29218887Sdim#include "clang/AST/ExprObjC.h" 30239462Sdim#include "clang/AST/StmtObjC.h" 31218887Sdim#include "clang/AST/ASTContext.h" 32234353Sdim#include "llvm/ADT/SmallString.h" 33239462Sdim#include "llvm/ADT/StringMap.h" 34218887Sdim 35218887Sdimusing namespace clang; 36218887Sdimusing namespace ento; 37218887Sdim 38218887Sdimnamespace { 39218887Sdimclass APIMisuse : public BugType { 40218887Sdimpublic: 41218887Sdim APIMisuse(const char* name) : BugType(name, "API Misuse (Apple)") {} 42218887Sdim}; 43218887Sdim} // end anonymous namespace 44218887Sdim 45218887Sdim//===----------------------------------------------------------------------===// 46218887Sdim// Utility functions. 47218887Sdim//===----------------------------------------------------------------------===// 48218887Sdim 49239462Sdimstatic StringRef GetReceiverInterfaceName(const ObjCMethodCall &msg) { 50218887Sdim if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface()) 51239462Sdim return ID->getIdentifier()->getName(); 52239462Sdim return StringRef(); 53218887Sdim} 54218887Sdim 55239462Sdimenum FoundationClass { 56239462Sdim FC_None, 57239462Sdim FC_NSArray, 58239462Sdim FC_NSDictionary, 59239462Sdim FC_NSEnumerator, 60239462Sdim FC_NSOrderedSet, 61239462Sdim FC_NSSet, 62239462Sdim FC_NSString 63239462Sdim}; 64218887Sdim 65239462Sdimstatic FoundationClass findKnownClass(const ObjCInterfaceDecl *ID) { 66239462Sdim static llvm::StringMap<FoundationClass> Classes; 67239462Sdim if (Classes.empty()) { 68239462Sdim Classes["NSArray"] = FC_NSArray; 69239462Sdim Classes["NSDictionary"] = FC_NSDictionary; 70239462Sdim Classes["NSEnumerator"] = FC_NSEnumerator; 71239462Sdim Classes["NSOrderedSet"] = FC_NSOrderedSet; 72239462Sdim Classes["NSSet"] = FC_NSSet; 73239462Sdim Classes["NSString"] = FC_NSString; 74239462Sdim } 75221345Sdim 76239462Sdim // FIXME: Should we cache this at all? 77239462Sdim FoundationClass result = Classes.lookup(ID->getIdentifier()->getName()); 78239462Sdim if (result == FC_None) 79239462Sdim if (const ObjCInterfaceDecl *Super = ID->getSuperClass()) 80239462Sdim return findKnownClass(Super); 81239462Sdim 82239462Sdim return result; 83218887Sdim} 84218887Sdim 85218887Sdimstatic inline bool isNil(SVal X) { 86218887Sdim return isa<loc::ConcreteInt>(X); 87218887Sdim} 88218887Sdim 89218887Sdim//===----------------------------------------------------------------------===// 90218887Sdim// NilArgChecker - Check for prohibited nil arguments to ObjC method calls. 91218887Sdim//===----------------------------------------------------------------------===// 92218887Sdim 93218887Sdimnamespace { 94221345Sdim class NilArgChecker : public Checker<check::PreObjCMessage> { 95234353Sdim mutable OwningPtr<APIMisuse> BT; 96219077Sdim 97219077Sdim void WarnNilArg(CheckerContext &C, 98239462Sdim const ObjCMethodCall &msg, unsigned Arg) const; 99219077Sdim 100218887Sdim public: 101239462Sdim void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; 102218887Sdim }; 103218887Sdim} 104218887Sdim 105218887Sdimvoid NilArgChecker::WarnNilArg(CheckerContext &C, 106239462Sdim const ObjCMethodCall &msg, 107219077Sdim unsigned int Arg) const 108218887Sdim{ 109218887Sdim if (!BT) 110219077Sdim BT.reset(new APIMisuse("nil argument")); 111218887Sdim 112218887Sdim if (ExplodedNode *N = C.generateSink()) { 113234353Sdim SmallString<128> sbuf; 114218887Sdim llvm::raw_svector_ostream os(sbuf); 115239462Sdim os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '" 116218887Sdim << msg.getSelector().getAsString() << "' cannot be nil"; 117218887Sdim 118226633Sdim BugReport *R = new BugReport(*BT, os.str(), N); 119218887Sdim R->addRange(msg.getArgSourceRange(Arg)); 120243830Sdim C.emitReport(R); 121218887Sdim } 122218887Sdim} 123218887Sdim 124239462Sdimvoid NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg, 125219077Sdim CheckerContext &C) const { 126221345Sdim const ObjCInterfaceDecl *ID = msg.getReceiverInterface(); 127221345Sdim if (!ID) 128218887Sdim return; 129218887Sdim 130239462Sdim if (findKnownClass(ID) == FC_NSString) { 131218887Sdim Selector S = msg.getSelector(); 132218887Sdim 133218887Sdim if (S.isUnarySelector()) 134218887Sdim return; 135218887Sdim 136218887Sdim // FIXME: This is going to be really slow doing these checks with 137218887Sdim // lexical comparisons. 138218887Sdim 139218887Sdim std::string NameStr = S.getAsString(); 140226633Sdim StringRef Name(NameStr); 141218887Sdim assert(!Name.empty()); 142218887Sdim 143218887Sdim // FIXME: Checking for initWithFormat: will not work in most cases 144218887Sdim // yet because [NSString alloc] returns id, not NSString*. We will 145218887Sdim // need support for tracking expected-type information in the analyzer 146218887Sdim // to find these errors. 147218887Sdim if (Name == "caseInsensitiveCompare:" || 148218887Sdim Name == "compare:" || 149218887Sdim Name == "compare:options:" || 150218887Sdim Name == "compare:options:range:" || 151218887Sdim Name == "compare:options:range:locale:" || 152218887Sdim Name == "componentsSeparatedByCharactersInSet:" || 153218887Sdim Name == "initWithFormat:") { 154239462Sdim if (isNil(msg.getArgSVal(0))) 155218887Sdim WarnNilArg(C, msg, 0); 156218887Sdim } 157218887Sdim } 158218887Sdim} 159218887Sdim 160218887Sdim//===----------------------------------------------------------------------===// 161218887Sdim// Error reporting. 162218887Sdim//===----------------------------------------------------------------------===// 163218887Sdim 164218887Sdimnamespace { 165221345Sdimclass CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > { 166234353Sdim mutable OwningPtr<APIMisuse> BT; 167219077Sdim mutable IdentifierInfo* II; 168218887Sdimpublic: 169219077Sdim CFNumberCreateChecker() : II(0) {} 170219077Sdim 171219077Sdim void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 172219077Sdim 173218887Sdimprivate: 174226633Sdim void EmitError(const TypedRegion* R, const Expr *Ex, 175218887Sdim uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind); 176218887Sdim}; 177218887Sdim} // end anonymous namespace 178218887Sdim 179218887Sdimenum CFNumberType { 180218887Sdim kCFNumberSInt8Type = 1, 181218887Sdim kCFNumberSInt16Type = 2, 182218887Sdim kCFNumberSInt32Type = 3, 183218887Sdim kCFNumberSInt64Type = 4, 184218887Sdim kCFNumberFloat32Type = 5, 185218887Sdim kCFNumberFloat64Type = 6, 186218887Sdim kCFNumberCharType = 7, 187218887Sdim kCFNumberShortType = 8, 188218887Sdim kCFNumberIntType = 9, 189218887Sdim kCFNumberLongType = 10, 190218887Sdim kCFNumberLongLongType = 11, 191218887Sdim kCFNumberFloatType = 12, 192218887Sdim kCFNumberDoubleType = 13, 193218887Sdim kCFNumberCFIndexType = 14, 194218887Sdim kCFNumberNSIntegerType = 15, 195218887Sdim kCFNumberCGFloatType = 16 196218887Sdim}; 197218887Sdim 198218887Sdimnamespace { 199218887Sdim template<typename T> 200218887Sdim class Optional { 201218887Sdim bool IsKnown; 202218887Sdim T Val; 203218887Sdim public: 204218887Sdim Optional() : IsKnown(false), Val(0) {} 205218887Sdim Optional(const T& val) : IsKnown(true), Val(val) {} 206218887Sdim 207218887Sdim bool isKnown() const { return IsKnown; } 208218887Sdim 209218887Sdim const T& getValue() const { 210218887Sdim assert (isKnown()); 211218887Sdim return Val; 212218887Sdim } 213218887Sdim 214218887Sdim operator const T&() const { 215218887Sdim return getValue(); 216218887Sdim } 217218887Sdim }; 218218887Sdim} 219218887Sdim 220226633Sdimstatic Optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) { 221218887Sdim static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 }; 222218887Sdim 223218887Sdim if (i < kCFNumberCharType) 224218887Sdim return FixedSize[i-1]; 225218887Sdim 226218887Sdim QualType T; 227218887Sdim 228218887Sdim switch (i) { 229218887Sdim case kCFNumberCharType: T = Ctx.CharTy; break; 230218887Sdim case kCFNumberShortType: T = Ctx.ShortTy; break; 231218887Sdim case kCFNumberIntType: T = Ctx.IntTy; break; 232218887Sdim case kCFNumberLongType: T = Ctx.LongTy; break; 233218887Sdim case kCFNumberLongLongType: T = Ctx.LongLongTy; break; 234218887Sdim case kCFNumberFloatType: T = Ctx.FloatTy; break; 235218887Sdim case kCFNumberDoubleType: T = Ctx.DoubleTy; break; 236218887Sdim case kCFNumberCFIndexType: 237218887Sdim case kCFNumberNSIntegerType: 238218887Sdim case kCFNumberCGFloatType: 239218887Sdim // FIXME: We need a way to map from names to Type*. 240218887Sdim default: 241218887Sdim return Optional<uint64_t>(); 242218887Sdim } 243218887Sdim 244218887Sdim return Ctx.getTypeSize(T); 245218887Sdim} 246218887Sdim 247218887Sdim#if 0 248218887Sdimstatic const char* GetCFNumberTypeStr(uint64_t i) { 249218887Sdim static const char* Names[] = { 250218887Sdim "kCFNumberSInt8Type", 251218887Sdim "kCFNumberSInt16Type", 252218887Sdim "kCFNumberSInt32Type", 253218887Sdim "kCFNumberSInt64Type", 254218887Sdim "kCFNumberFloat32Type", 255218887Sdim "kCFNumberFloat64Type", 256218887Sdim "kCFNumberCharType", 257218887Sdim "kCFNumberShortType", 258218887Sdim "kCFNumberIntType", 259218887Sdim "kCFNumberLongType", 260218887Sdim "kCFNumberLongLongType", 261218887Sdim "kCFNumberFloatType", 262218887Sdim "kCFNumberDoubleType", 263218887Sdim "kCFNumberCFIndexType", 264218887Sdim "kCFNumberNSIntegerType", 265218887Sdim "kCFNumberCGFloatType" 266218887Sdim }; 267218887Sdim 268218887Sdim return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType"; 269218887Sdim} 270218887Sdim#endif 271218887Sdim 272219077Sdimvoid CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, 273219077Sdim CheckerContext &C) const { 274234353Sdim ProgramStateRef state = C.getState(); 275234353Sdim const FunctionDecl *FD = C.getCalleeDecl(CE); 276218887Sdim if (!FD) 277218887Sdim return; 278218887Sdim 279218887Sdim ASTContext &Ctx = C.getASTContext(); 280218887Sdim if (!II) 281218887Sdim II = &Ctx.Idents.get("CFNumberCreate"); 282218887Sdim 283218887Sdim if (FD->getIdentifier() != II || CE->getNumArgs() != 3) 284218887Sdim return; 285218887Sdim 286218887Sdim // Get the value of the "theType" argument. 287234353Sdim const LocationContext *LCtx = C.getLocationContext(); 288234353Sdim SVal TheTypeVal = state->getSVal(CE->getArg(1), LCtx); 289218887Sdim 290218887Sdim // FIXME: We really should allow ranges of valid theType values, and 291218887Sdim // bifurcate the state appropriately. 292218887Sdim nonloc::ConcreteInt* V = dyn_cast<nonloc::ConcreteInt>(&TheTypeVal); 293218887Sdim if (!V) 294218887Sdim return; 295218887Sdim 296218887Sdim uint64_t NumberKind = V->getValue().getLimitedValue(); 297218887Sdim Optional<uint64_t> TargetSize = GetCFNumberSize(Ctx, NumberKind); 298218887Sdim 299218887Sdim // FIXME: In some cases we can emit an error. 300218887Sdim if (!TargetSize.isKnown()) 301218887Sdim return; 302218887Sdim 303218887Sdim // Look at the value of the integer being passed by reference. Essentially 304218887Sdim // we want to catch cases where the value passed in is not equal to the 305218887Sdim // size of the type being created. 306234353Sdim SVal TheValueExpr = state->getSVal(CE->getArg(2), LCtx); 307218887Sdim 308218887Sdim // FIXME: Eventually we should handle arbitrary locations. We can do this 309218887Sdim // by having an enhanced memory model that does low-level typing. 310218887Sdim loc::MemRegionVal* LV = dyn_cast<loc::MemRegionVal>(&TheValueExpr); 311218887Sdim if (!LV) 312218887Sdim return; 313218887Sdim 314226633Sdim const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts()); 315218887Sdim if (!R) 316218887Sdim return; 317218887Sdim 318218887Sdim QualType T = Ctx.getCanonicalType(R->getValueType()); 319218887Sdim 320218887Sdim // FIXME: If the pointee isn't an integer type, should we flag a warning? 321218887Sdim // People can do weird stuff with pointers. 322218887Sdim 323218887Sdim if (!T->isIntegerType()) 324218887Sdim return; 325218887Sdim 326218887Sdim uint64_t SourceSize = Ctx.getTypeSize(T); 327218887Sdim 328218887Sdim // CHECK: is SourceSize == TargetSize 329218887Sdim if (SourceSize == TargetSize) 330218887Sdim return; 331218887Sdim 332218887Sdim // Generate an error. Only generate a sink if 'SourceSize < TargetSize'; 333218887Sdim // otherwise generate a regular node. 334218887Sdim // 335218887Sdim // FIXME: We can actually create an abstract "CFNumber" object that has 336218887Sdim // the bits initialized to the provided values. 337218887Sdim // 338218887Sdim if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink() 339234353Sdim : C.addTransition()) { 340234353Sdim SmallString<128> sbuf; 341218887Sdim llvm::raw_svector_ostream os(sbuf); 342218887Sdim 343218887Sdim os << (SourceSize == 8 ? "An " : "A ") 344218887Sdim << SourceSize << " bit integer is used to initialize a CFNumber " 345218887Sdim "object that represents " 346218887Sdim << (TargetSize == 8 ? "an " : "a ") 347218887Sdim << TargetSize << " bit integer. "; 348218887Sdim 349218887Sdim if (SourceSize < TargetSize) 350218887Sdim os << (TargetSize - SourceSize) 351218887Sdim << " bits of the CFNumber value will be garbage." ; 352218887Sdim else 353218887Sdim os << (SourceSize - TargetSize) 354218887Sdim << " bits of the input integer will be lost."; 355218887Sdim 356218887Sdim if (!BT) 357219077Sdim BT.reset(new APIMisuse("Bad use of CFNumberCreate")); 358218887Sdim 359226633Sdim BugReport *report = new BugReport(*BT, os.str(), N); 360218887Sdim report->addRange(CE->getArg(2)->getSourceRange()); 361243830Sdim C.emitReport(report); 362218887Sdim } 363218887Sdim} 364218887Sdim 365218887Sdim//===----------------------------------------------------------------------===// 366243830Sdim// CFRetain/CFRelease/CFMakeCollectable checking for null arguments. 367218887Sdim//===----------------------------------------------------------------------===// 368218887Sdim 369218887Sdimnamespace { 370221345Sdimclass CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > { 371234353Sdim mutable OwningPtr<APIMisuse> BT; 372243830Sdim mutable IdentifierInfo *Retain, *Release, *MakeCollectable; 373218887Sdimpublic: 374243830Sdim CFRetainReleaseChecker(): Retain(0), Release(0), MakeCollectable(0) {} 375226633Sdim void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 376218887Sdim}; 377218887Sdim} // end anonymous namespace 378218887Sdim 379218887Sdim 380226633Sdimvoid CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, 381226633Sdim CheckerContext &C) const { 382218887Sdim // If the CallExpr doesn't have exactly 1 argument just give up checking. 383218887Sdim if (CE->getNumArgs() != 1) 384218887Sdim return; 385218887Sdim 386234353Sdim ProgramStateRef state = C.getState(); 387234353Sdim const FunctionDecl *FD = C.getCalleeDecl(CE); 388218887Sdim if (!FD) 389218887Sdim return; 390218887Sdim 391218887Sdim if (!BT) { 392218887Sdim ASTContext &Ctx = C.getASTContext(); 393218887Sdim Retain = &Ctx.Idents.get("CFRetain"); 394218887Sdim Release = &Ctx.Idents.get("CFRelease"); 395243830Sdim MakeCollectable = &Ctx.Idents.get("CFMakeCollectable"); 396243830Sdim BT.reset( 397243830Sdim new APIMisuse("null passed to CFRetain/CFRelease/CFMakeCollectable")); 398218887Sdim } 399218887Sdim 400243830Sdim // Check if we called CFRetain/CFRelease/CFMakeCollectable. 401218887Sdim const IdentifierInfo *FuncII = FD->getIdentifier(); 402243830Sdim if (!(FuncII == Retain || FuncII == Release || FuncII == MakeCollectable)) 403218887Sdim return; 404218887Sdim 405218887Sdim // FIXME: The rest of this just checks that the argument is non-null. 406218887Sdim // It should probably be refactored and combined with AttrNonNullChecker. 407218887Sdim 408218887Sdim // Get the argument's value. 409218887Sdim const Expr *Arg = CE->getArg(0); 410234353Sdim SVal ArgVal = state->getSVal(Arg, C.getLocationContext()); 411218887Sdim DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal); 412218887Sdim if (!DefArgVal) 413218887Sdim return; 414218887Sdim 415218887Sdim // Get a NULL value. 416218887Sdim SValBuilder &svalBuilder = C.getSValBuilder(); 417218887Sdim DefinedSVal zero = cast<DefinedSVal>(svalBuilder.makeZeroVal(Arg->getType())); 418218887Sdim 419218887Sdim // Make an expression asserting that they're equal. 420218887Sdim DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal); 421218887Sdim 422218887Sdim // Are they equal? 423234353Sdim ProgramStateRef stateTrue, stateFalse; 424218887Sdim llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); 425218887Sdim 426218887Sdim if (stateTrue && !stateFalse) { 427218887Sdim ExplodedNode *N = C.generateSink(stateTrue); 428218887Sdim if (!N) 429218887Sdim return; 430218887Sdim 431243830Sdim const char *description; 432243830Sdim if (FuncII == Retain) 433243830Sdim description = "Null pointer argument in call to CFRetain"; 434243830Sdim else if (FuncII == Release) 435243830Sdim description = "Null pointer argument in call to CFRelease"; 436243830Sdim else if (FuncII == MakeCollectable) 437243830Sdim description = "Null pointer argument in call to CFMakeCollectable"; 438243830Sdim else 439243830Sdim llvm_unreachable("impossible case"); 440218887Sdim 441226633Sdim BugReport *report = new BugReport(*BT, description, N); 442218887Sdim report->addRange(Arg->getSourceRange()); 443243830Sdim bugreporter::trackNullOrUndefValue(N, Arg, *report); 444243830Sdim C.emitReport(report); 445218887Sdim return; 446218887Sdim } 447218887Sdim 448218887Sdim // From here on, we know the argument is non-null. 449218887Sdim C.addTransition(stateFalse); 450218887Sdim} 451218887Sdim 452218887Sdim//===----------------------------------------------------------------------===// 453218887Sdim// Check for sending 'retain', 'release', or 'autorelease' directly to a Class. 454218887Sdim//===----------------------------------------------------------------------===// 455218887Sdim 456218887Sdimnamespace { 457221345Sdimclass ClassReleaseChecker : public Checker<check::PreObjCMessage> { 458219077Sdim mutable Selector releaseS; 459219077Sdim mutable Selector retainS; 460219077Sdim mutable Selector autoreleaseS; 461219077Sdim mutable Selector drainS; 462234353Sdim mutable OwningPtr<BugType> BT; 463219077Sdim 464218887Sdimpublic: 465239462Sdim void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; 466218887Sdim}; 467218887Sdim} 468218887Sdim 469239462Sdimvoid ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, 470219077Sdim CheckerContext &C) const { 471218887Sdim 472218887Sdim if (!BT) { 473219077Sdim BT.reset(new APIMisuse("message incorrectly sent to class instead of class " 474219077Sdim "instance")); 475218887Sdim 476218887Sdim ASTContext &Ctx = C.getASTContext(); 477218887Sdim releaseS = GetNullarySelector("release", Ctx); 478218887Sdim retainS = GetNullarySelector("retain", Ctx); 479218887Sdim autoreleaseS = GetNullarySelector("autorelease", Ctx); 480218887Sdim drainS = GetNullarySelector("drain", Ctx); 481218887Sdim } 482218887Sdim 483218887Sdim if (msg.isInstanceMessage()) 484218887Sdim return; 485218887Sdim const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 486218887Sdim assert(Class); 487218887Sdim 488218887Sdim Selector S = msg.getSelector(); 489218887Sdim if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS)) 490218887Sdim return; 491218887Sdim 492234353Sdim if (ExplodedNode *N = C.addTransition()) { 493234353Sdim SmallString<200> buf; 494218887Sdim llvm::raw_svector_ostream os(buf); 495218887Sdim 496218887Sdim os << "The '" << S.getAsString() << "' message should be sent to instances " 497218887Sdim "of class '" << Class->getName() 498218887Sdim << "' and not the class directly"; 499218887Sdim 500226633Sdim BugReport *report = new BugReport(*BT, os.str(), N); 501218887Sdim report->addRange(msg.getSourceRange()); 502243830Sdim C.emitReport(report); 503218887Sdim } 504218887Sdim} 505218887Sdim 506218887Sdim//===----------------------------------------------------------------------===// 507221345Sdim// Check for passing non-Objective-C types to variadic methods that expect 508221345Sdim// only Objective-C types. 509221345Sdim//===----------------------------------------------------------------------===// 510221345Sdim 511221345Sdimnamespace { 512221345Sdimclass VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> { 513221345Sdim mutable Selector arrayWithObjectsS; 514221345Sdim mutable Selector dictionaryWithObjectsAndKeysS; 515221345Sdim mutable Selector setWithObjectsS; 516234353Sdim mutable Selector orderedSetWithObjectsS; 517221345Sdim mutable Selector initWithObjectsS; 518221345Sdim mutable Selector initWithObjectsAndKeysS; 519234353Sdim mutable OwningPtr<BugType> BT; 520221345Sdim 521239462Sdim bool isVariadicMessage(const ObjCMethodCall &msg) const; 522221345Sdim 523221345Sdimpublic: 524239462Sdim void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; 525221345Sdim}; 526221345Sdim} 527221345Sdim 528221345Sdim/// isVariadicMessage - Returns whether the given message is a variadic message, 529221345Sdim/// where all arguments must be Objective-C types. 530221345Sdimbool 531239462SdimVariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const { 532239462Sdim const ObjCMethodDecl *MD = msg.getDecl(); 533221345Sdim 534221345Sdim if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext())) 535221345Sdim return false; 536221345Sdim 537221345Sdim Selector S = msg.getSelector(); 538221345Sdim 539221345Sdim if (msg.isInstanceMessage()) { 540221345Sdim // FIXME: Ideally we'd look at the receiver interface here, but that's not 541221345Sdim // useful for init, because alloc returns 'id'. In theory, this could lead 542221345Sdim // to false positives, for example if there existed a class that had an 543221345Sdim // initWithObjects: implementation that does accept non-Objective-C pointer 544221345Sdim // types, but the chance of that happening is pretty small compared to the 545221345Sdim // gains that this analysis gives. 546221345Sdim const ObjCInterfaceDecl *Class = MD->getClassInterface(); 547221345Sdim 548239462Sdim switch (findKnownClass(Class)) { 549239462Sdim case FC_NSArray: 550239462Sdim case FC_NSOrderedSet: 551239462Sdim case FC_NSSet: 552239462Sdim return S == initWithObjectsS; 553239462Sdim case FC_NSDictionary: 554239462Sdim return S == initWithObjectsAndKeysS; 555239462Sdim default: 556239462Sdim return false; 557239462Sdim } 558221345Sdim } else { 559221345Sdim const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 560221345Sdim 561239462Sdim switch (findKnownClass(Class)) { 562239462Sdim case FC_NSArray: 563239462Sdim return S == arrayWithObjectsS; 564239462Sdim case FC_NSOrderedSet: 565239462Sdim return S == orderedSetWithObjectsS; 566239462Sdim case FC_NSSet: 567239462Sdim return S == setWithObjectsS; 568239462Sdim case FC_NSDictionary: 569239462Sdim return S == dictionaryWithObjectsAndKeysS; 570239462Sdim default: 571239462Sdim return false; 572239462Sdim } 573221345Sdim } 574221345Sdim} 575221345Sdim 576239462Sdimvoid VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, 577221345Sdim CheckerContext &C) const { 578221345Sdim if (!BT) { 579221345Sdim BT.reset(new APIMisuse("Arguments passed to variadic method aren't all " 580221345Sdim "Objective-C pointer types")); 581221345Sdim 582221345Sdim ASTContext &Ctx = C.getASTContext(); 583221345Sdim arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx); 584221345Sdim dictionaryWithObjectsAndKeysS = 585221345Sdim GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx); 586221345Sdim setWithObjectsS = GetUnarySelector("setWithObjects", Ctx); 587234353Sdim orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx); 588221345Sdim 589221345Sdim initWithObjectsS = GetUnarySelector("initWithObjects", Ctx); 590221345Sdim initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx); 591221345Sdim } 592221345Sdim 593221345Sdim if (!isVariadicMessage(msg)) 594221345Sdim return; 595221345Sdim 596221345Sdim // We are not interested in the selector arguments since they have 597221345Sdim // well-defined types, so the compiler will issue a warning for them. 598221345Sdim unsigned variadicArgsBegin = msg.getSelector().getNumArgs(); 599221345Sdim 600221345Sdim // We're not interested in the last argument since it has to be nil or the 601221345Sdim // compiler would have issued a warning for it elsewhere. 602221345Sdim unsigned variadicArgsEnd = msg.getNumArgs() - 1; 603221345Sdim 604221345Sdim if (variadicArgsEnd <= variadicArgsBegin) 605221345Sdim return; 606221345Sdim 607221345Sdim // Verify that all arguments have Objective-C types. 608221345Sdim llvm::Optional<ExplodedNode*> errorNode; 609234353Sdim ProgramStateRef state = C.getState(); 610221345Sdim 611221345Sdim for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) { 612239462Sdim QualType ArgTy = msg.getArgExpr(I)->getType(); 613221345Sdim if (ArgTy->isObjCObjectPointerType()) 614221345Sdim continue; 615221345Sdim 616221345Sdim // Block pointers are treaded as Objective-C pointers. 617221345Sdim if (ArgTy->isBlockPointerType()) 618221345Sdim continue; 619221345Sdim 620221345Sdim // Ignore pointer constants. 621239462Sdim if (isa<loc::ConcreteInt>(msg.getArgSVal(I))) 622221345Sdim continue; 623221345Sdim 624221345Sdim // Ignore pointer types annotated with 'NSObject' attribute. 625221345Sdim if (C.getASTContext().isObjCNSObjectType(ArgTy)) 626221345Sdim continue; 627221345Sdim 628221345Sdim // Ignore CF references, which can be toll-free bridged. 629224145Sdim if (coreFoundation::isCFObjectRef(ArgTy)) 630221345Sdim continue; 631221345Sdim 632221345Sdim // Generate only one error node to use for all bug reports. 633239462Sdim if (!errorNode.hasValue()) 634234353Sdim errorNode = C.addTransition(); 635221345Sdim 636221345Sdim if (!errorNode.getValue()) 637221345Sdim continue; 638221345Sdim 639234353Sdim SmallString<128> sbuf; 640221345Sdim llvm::raw_svector_ostream os(sbuf); 641221345Sdim 642239462Sdim StringRef TypeName = GetReceiverInterfaceName(msg); 643239462Sdim if (!TypeName.empty()) 644221345Sdim os << "Argument to '" << TypeName << "' method '"; 645221345Sdim else 646221345Sdim os << "Argument to method '"; 647221345Sdim 648221345Sdim os << msg.getSelector().getAsString() 649239462Sdim << "' should be an Objective-C pointer type, not '"; 650239462Sdim ArgTy.print(os, C.getLangOpts()); 651239462Sdim os << "'"; 652221345Sdim 653239462Sdim BugReport *R = new BugReport(*BT, os.str(), errorNode.getValue()); 654221345Sdim R->addRange(msg.getArgSourceRange(I)); 655243830Sdim C.emitReport(R); 656221345Sdim } 657221345Sdim} 658221345Sdim 659221345Sdim//===----------------------------------------------------------------------===// 660239462Sdim// Improves the modeling of loops over Cocoa collections. 661239462Sdim//===----------------------------------------------------------------------===// 662239462Sdim 663239462Sdimnamespace { 664239462Sdimclass ObjCLoopChecker 665239462Sdim : public Checker<check::PostStmt<ObjCForCollectionStmt> > { 666239462Sdim 667239462Sdimpublic: 668239462Sdim void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const; 669239462Sdim}; 670239462Sdim} 671239462Sdim 672239462Sdimstatic bool isKnownNonNilCollectionType(QualType T) { 673239462Sdim const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>(); 674239462Sdim if (!PT) 675239462Sdim return false; 676239462Sdim 677239462Sdim const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); 678239462Sdim if (!ID) 679239462Sdim return false; 680239462Sdim 681239462Sdim switch (findKnownClass(ID)) { 682239462Sdim case FC_NSArray: 683239462Sdim case FC_NSDictionary: 684239462Sdim case FC_NSEnumerator: 685239462Sdim case FC_NSOrderedSet: 686239462Sdim case FC_NSSet: 687239462Sdim return true; 688239462Sdim default: 689239462Sdim return false; 690239462Sdim } 691239462Sdim} 692239462Sdim 693239462Sdimvoid ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS, 694239462Sdim CheckerContext &C) const { 695239462Sdim ProgramStateRef State = C.getState(); 696239462Sdim 697239462Sdim // Check if this is the branch for the end of the loop. 698239462Sdim SVal CollectionSentinel = State->getSVal(FCS, C.getLocationContext()); 699239462Sdim if (CollectionSentinel.isZeroConstant()) 700239462Sdim return; 701239462Sdim 702239462Sdim // See if the collection is one where we /know/ the elements are non-nil. 703239462Sdim const Expr *Collection = FCS->getCollection(); 704239462Sdim if (!isKnownNonNilCollectionType(Collection->getType())) 705239462Sdim return; 706239462Sdim 707239462Sdim // FIXME: Copied from ExprEngineObjC. 708239462Sdim const Stmt *Element = FCS->getElement(); 709239462Sdim SVal ElementVar; 710239462Sdim if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) { 711239462Sdim const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl()); 712239462Sdim assert(ElemDecl->getInit() == 0); 713239462Sdim ElementVar = State->getLValue(ElemDecl, C.getLocationContext()); 714239462Sdim } else { 715239462Sdim ElementVar = State->getSVal(Element, C.getLocationContext()); 716239462Sdim } 717239462Sdim 718239462Sdim if (!isa<Loc>(ElementVar)) 719239462Sdim return; 720239462Sdim 721239462Sdim // Go ahead and assume the value is non-nil. 722239462Sdim SVal Val = State->getSVal(cast<Loc>(ElementVar)); 723239462Sdim State = State->assume(cast<DefinedOrUnknownSVal>(Val), true); 724239462Sdim C.addTransition(State); 725239462Sdim} 726239462Sdim 727243830Sdimnamespace { 728243830Sdim/// \class ObjCNonNilReturnValueChecker 729243830Sdim/// \brief The checker restricts the return values of APIs known to 730243830Sdim/// never (or almost never) return 'nil'. 731243830Sdimclass ObjCNonNilReturnValueChecker 732243830Sdim : public Checker<check::PostObjCMessage> { 733243830Sdim mutable bool Initialized; 734243830Sdim mutable Selector ObjectAtIndex; 735243830Sdim mutable Selector ObjectAtIndexedSubscript; 736239462Sdim 737243830Sdimpublic: 738243830Sdim ObjCNonNilReturnValueChecker() : Initialized(false) {} 739243830Sdim void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; 740243830Sdim}; 741243830Sdim} 742243830Sdim 743243830Sdimstatic ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr, 744243830Sdim ProgramStateRef State, 745243830Sdim CheckerContext &C) { 746243830Sdim SVal Val = State->getSVal(NonNullExpr, C.getLocationContext()); 747243830Sdim if (DefinedOrUnknownSVal *DV = dyn_cast<DefinedOrUnknownSVal>(&Val)) 748243830Sdim return State->assume(*DV, true); 749243830Sdim return State; 750243830Sdim} 751243830Sdim 752243830Sdimvoid ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M, 753243830Sdim CheckerContext &C) 754243830Sdim const { 755243830Sdim ProgramStateRef State = C.getState(); 756243830Sdim 757243830Sdim if (!Initialized) { 758243830Sdim ASTContext &Ctx = C.getASTContext(); 759243830Sdim ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx); 760243830Sdim ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx); 761243830Sdim } 762243830Sdim 763243830Sdim // Check the receiver type. 764243830Sdim if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) { 765243830Sdim 766243830Sdim // Assume that object returned from '[self init]' or '[super init]' is not 767243830Sdim // 'nil' if we are processing an inlined function/method. 768243830Sdim // 769243830Sdim // A defensive callee will (and should) check if the object returned by 770243830Sdim // '[super init]' is 'nil' before doing it's own initialization. However, 771243830Sdim // since 'nil' is rarely returned in practice, we should not warn when the 772243830Sdim // caller to the defensive constructor uses the object in contexts where 773243830Sdim // 'nil' is not accepted. 774243830Sdim if (!C.inTopFrame() && M.getDecl() && 775243830Sdim M.getDecl()->getMethodFamily() == OMF_init && 776243830Sdim M.isReceiverSelfOrSuper()) { 777243830Sdim State = assumeExprIsNonNull(M.getOriginExpr(), State, C); 778243830Sdim } 779243830Sdim 780243830Sdim // Objects returned from 781243830Sdim // [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript] 782243830Sdim // are never 'nil'. 783243830Sdim FoundationClass Cl = findKnownClass(Interface); 784243830Sdim if (Cl == FC_NSArray || Cl == FC_NSOrderedSet) { 785243830Sdim Selector Sel = M.getSelector(); 786243830Sdim if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript) { 787243830Sdim // Go ahead and assume the value is non-nil. 788243830Sdim State = assumeExprIsNonNull(M.getOriginExpr(), State, C); 789243830Sdim } 790243830Sdim } 791243830Sdim } 792243830Sdim C.addTransition(State); 793243830Sdim} 794243830Sdim 795239462Sdim//===----------------------------------------------------------------------===// 796218887Sdim// Check registration. 797218887Sdim//===----------------------------------------------------------------------===// 798218887Sdim 799218887Sdimvoid ento::registerNilArgChecker(CheckerManager &mgr) { 800219077Sdim mgr.registerChecker<NilArgChecker>(); 801218887Sdim} 802218887Sdim 803218887Sdimvoid ento::registerCFNumberCreateChecker(CheckerManager &mgr) { 804219077Sdim mgr.registerChecker<CFNumberCreateChecker>(); 805218887Sdim} 806218887Sdim 807218887Sdimvoid ento::registerCFRetainReleaseChecker(CheckerManager &mgr) { 808219077Sdim mgr.registerChecker<CFRetainReleaseChecker>(); 809218887Sdim} 810218887Sdim 811218887Sdimvoid ento::registerClassReleaseChecker(CheckerManager &mgr) { 812219077Sdim mgr.registerChecker<ClassReleaseChecker>(); 813218887Sdim} 814221345Sdim 815221345Sdimvoid ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) { 816221345Sdim mgr.registerChecker<VariadicMethodTypeChecker>(); 817221345Sdim} 818239462Sdim 819239462Sdimvoid ento::registerObjCLoopChecker(CheckerManager &mgr) { 820239462Sdim mgr.registerChecker<ObjCLoopChecker>(); 821239462Sdim} 822243830Sdim 823243830Sdimvoid ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) { 824243830Sdim mgr.registerChecker<ObjCNonNilReturnValueChecker>(); 825243830Sdim} 826