BasicObjCFoundationChecks.cpp revision b805c8ff133ef0c62df032fa711d6b13c5afd7f4
1//== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- C++ -*-- 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This file defines BasicObjCFoundationChecks, a class that encapsulates 11// a set of simple checks to run on Objective-C code using Apple's Foundation 12// classes. 13// 14//===----------------------------------------------------------------------===// 15 16#include "ClangSACheckers.h" 17#include "clang/Analysis/DomainSpecific/CocoaConventions.h" 18#include "clang/StaticAnalyzer/Core/Checker.h" 19#include "clang/StaticAnalyzer/Core/CheckerManager.h" 20#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 21#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" 22#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" 23#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" 24#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 25#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 26#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" 27#include "clang/AST/DeclObjC.h" 28#include "clang/AST/Expr.h" 29#include "clang/AST/ExprObjC.h" 30#include "clang/AST/ASTContext.h" 31 32using namespace clang; 33using namespace ento; 34 35namespace { 36class APIMisuse : public BugType { 37public: 38 APIMisuse(const char* name) : BugType(name, "API Misuse (Apple)") {} 39}; 40} // end anonymous namespace 41 42//===----------------------------------------------------------------------===// 43// Utility functions. 44//===----------------------------------------------------------------------===// 45 46static const char* GetReceiverNameType(const ObjCMessage &msg) { 47 if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface()) 48 return ID->getIdentifier()->getNameStart(); 49 return 0; 50} 51 52static bool isReceiverClassOrSuperclass(const ObjCInterfaceDecl *ID, 53 StringRef ClassName) { 54 if (ID->getIdentifier()->getName() == ClassName) 55 return true; 56 57 if (const ObjCInterfaceDecl *Super = ID->getSuperClass()) 58 return isReceiverClassOrSuperclass(Super, ClassName); 59 60 return false; 61} 62 63static inline bool isNil(SVal X) { 64 return isa<loc::ConcreteInt>(X); 65} 66 67//===----------------------------------------------------------------------===// 68// NilArgChecker - Check for prohibited nil arguments to ObjC method calls. 69//===----------------------------------------------------------------------===// 70 71namespace { 72 class NilArgChecker : public Checker<check::PreObjCMessage> { 73 mutable llvm::OwningPtr<APIMisuse> BT; 74 75 void WarnNilArg(CheckerContext &C, 76 const ObjCMessage &msg, unsigned Arg) const; 77 78 public: 79 void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const; 80 }; 81} 82 83void NilArgChecker::WarnNilArg(CheckerContext &C, 84 const ObjCMessage &msg, 85 unsigned int Arg) const 86{ 87 if (!BT) 88 BT.reset(new APIMisuse("nil argument")); 89 90 if (ExplodedNode *N = C.generateSink()) { 91 llvm::SmallString<128> sbuf; 92 llvm::raw_svector_ostream os(sbuf); 93 os << "Argument to '" << GetReceiverNameType(msg) << "' method '" 94 << msg.getSelector().getAsString() << "' cannot be nil"; 95 96 BugReport *R = new BugReport(*BT, os.str(), N); 97 R->addRange(msg.getArgSourceRange(Arg)); 98 C.EmitReport(R); 99 } 100} 101 102void NilArgChecker::checkPreObjCMessage(ObjCMessage msg, 103 CheckerContext &C) const { 104 const ObjCInterfaceDecl *ID = msg.getReceiverInterface(); 105 if (!ID) 106 return; 107 108 if (isReceiverClassOrSuperclass(ID, "NSString")) { 109 Selector S = msg.getSelector(); 110 111 if (S.isUnarySelector()) 112 return; 113 114 // FIXME: This is going to be really slow doing these checks with 115 // lexical comparisons. 116 117 std::string NameStr = S.getAsString(); 118 StringRef Name(NameStr); 119 assert(!Name.empty()); 120 121 // FIXME: Checking for initWithFormat: will not work in most cases 122 // yet because [NSString alloc] returns id, not NSString*. We will 123 // need support for tracking expected-type information in the analyzer 124 // to find these errors. 125 if (Name == "caseInsensitiveCompare:" || 126 Name == "compare:" || 127 Name == "compare:options:" || 128 Name == "compare:options:range:" || 129 Name == "compare:options:range:locale:" || 130 Name == "componentsSeparatedByCharactersInSet:" || 131 Name == "initWithFormat:") { 132 if (isNil(msg.getArgSVal(0, C.getState()))) 133 WarnNilArg(C, msg, 0); 134 } 135 } 136} 137 138//===----------------------------------------------------------------------===// 139// Error reporting. 140//===----------------------------------------------------------------------===// 141 142namespace { 143class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > { 144 mutable llvm::OwningPtr<APIMisuse> BT; 145 mutable IdentifierInfo* II; 146public: 147 CFNumberCreateChecker() : II(0) {} 148 149 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 150 151private: 152 void EmitError(const TypedRegion* R, const Expr *Ex, 153 uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind); 154}; 155} // end anonymous namespace 156 157enum CFNumberType { 158 kCFNumberSInt8Type = 1, 159 kCFNumberSInt16Type = 2, 160 kCFNumberSInt32Type = 3, 161 kCFNumberSInt64Type = 4, 162 kCFNumberFloat32Type = 5, 163 kCFNumberFloat64Type = 6, 164 kCFNumberCharType = 7, 165 kCFNumberShortType = 8, 166 kCFNumberIntType = 9, 167 kCFNumberLongType = 10, 168 kCFNumberLongLongType = 11, 169 kCFNumberFloatType = 12, 170 kCFNumberDoubleType = 13, 171 kCFNumberCFIndexType = 14, 172 kCFNumberNSIntegerType = 15, 173 kCFNumberCGFloatType = 16 174}; 175 176namespace { 177 template<typename T> 178 class Optional { 179 bool IsKnown; 180 T Val; 181 public: 182 Optional() : IsKnown(false), Val(0) {} 183 Optional(const T& val) : IsKnown(true), Val(val) {} 184 185 bool isKnown() const { return IsKnown; } 186 187 const T& getValue() const { 188 assert (isKnown()); 189 return Val; 190 } 191 192 operator const T&() const { 193 return getValue(); 194 } 195 }; 196} 197 198static Optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) { 199 static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 }; 200 201 if (i < kCFNumberCharType) 202 return FixedSize[i-1]; 203 204 QualType T; 205 206 switch (i) { 207 case kCFNumberCharType: T = Ctx.CharTy; break; 208 case kCFNumberShortType: T = Ctx.ShortTy; break; 209 case kCFNumberIntType: T = Ctx.IntTy; break; 210 case kCFNumberLongType: T = Ctx.LongTy; break; 211 case kCFNumberLongLongType: T = Ctx.LongLongTy; break; 212 case kCFNumberFloatType: T = Ctx.FloatTy; break; 213 case kCFNumberDoubleType: T = Ctx.DoubleTy; break; 214 case kCFNumberCFIndexType: 215 case kCFNumberNSIntegerType: 216 case kCFNumberCGFloatType: 217 // FIXME: We need a way to map from names to Type*. 218 default: 219 return Optional<uint64_t>(); 220 } 221 222 return Ctx.getTypeSize(T); 223} 224 225#if 0 226static const char* GetCFNumberTypeStr(uint64_t i) { 227 static const char* Names[] = { 228 "kCFNumberSInt8Type", 229 "kCFNumberSInt16Type", 230 "kCFNumberSInt32Type", 231 "kCFNumberSInt64Type", 232 "kCFNumberFloat32Type", 233 "kCFNumberFloat64Type", 234 "kCFNumberCharType", 235 "kCFNumberShortType", 236 "kCFNumberIntType", 237 "kCFNumberLongType", 238 "kCFNumberLongLongType", 239 "kCFNumberFloatType", 240 "kCFNumberDoubleType", 241 "kCFNumberCFIndexType", 242 "kCFNumberNSIntegerType", 243 "kCFNumberCGFloatType" 244 }; 245 246 return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType"; 247} 248#endif 249 250void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, 251 CheckerContext &C) const { 252 const ProgramState *state = C.getState(); 253 const FunctionDecl *FD = C.getCalleeDecl(CE); 254 if (!FD) 255 return; 256 257 ASTContext &Ctx = C.getASTContext(); 258 if (!II) 259 II = &Ctx.Idents.get("CFNumberCreate"); 260 261 if (FD->getIdentifier() != II || CE->getNumArgs() != 3) 262 return; 263 264 // Get the value of the "theType" argument. 265 SVal TheTypeVal = state->getSVal(CE->getArg(1)); 266 267 // FIXME: We really should allow ranges of valid theType values, and 268 // bifurcate the state appropriately. 269 nonloc::ConcreteInt* V = dyn_cast<nonloc::ConcreteInt>(&TheTypeVal); 270 if (!V) 271 return; 272 273 uint64_t NumberKind = V->getValue().getLimitedValue(); 274 Optional<uint64_t> TargetSize = GetCFNumberSize(Ctx, NumberKind); 275 276 // FIXME: In some cases we can emit an error. 277 if (!TargetSize.isKnown()) 278 return; 279 280 // Look at the value of the integer being passed by reference. Essentially 281 // we want to catch cases where the value passed in is not equal to the 282 // size of the type being created. 283 SVal TheValueExpr = state->getSVal(CE->getArg(2)); 284 285 // FIXME: Eventually we should handle arbitrary locations. We can do this 286 // by having an enhanced memory model that does low-level typing. 287 loc::MemRegionVal* LV = dyn_cast<loc::MemRegionVal>(&TheValueExpr); 288 if (!LV) 289 return; 290 291 const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts()); 292 if (!R) 293 return; 294 295 QualType T = Ctx.getCanonicalType(R->getValueType()); 296 297 // FIXME: If the pointee isn't an integer type, should we flag a warning? 298 // People can do weird stuff with pointers. 299 300 if (!T->isIntegerType()) 301 return; 302 303 uint64_t SourceSize = Ctx.getTypeSize(T); 304 305 // CHECK: is SourceSize == TargetSize 306 if (SourceSize == TargetSize) 307 return; 308 309 // Generate an error. Only generate a sink if 'SourceSize < TargetSize'; 310 // otherwise generate a regular node. 311 // 312 // FIXME: We can actually create an abstract "CFNumber" object that has 313 // the bits initialized to the provided values. 314 // 315 if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink() 316 : C.addTransition()) { 317 llvm::SmallString<128> sbuf; 318 llvm::raw_svector_ostream os(sbuf); 319 320 os << (SourceSize == 8 ? "An " : "A ") 321 << SourceSize << " bit integer is used to initialize a CFNumber " 322 "object that represents " 323 << (TargetSize == 8 ? "an " : "a ") 324 << TargetSize << " bit integer. "; 325 326 if (SourceSize < TargetSize) 327 os << (TargetSize - SourceSize) 328 << " bits of the CFNumber value will be garbage." ; 329 else 330 os << (SourceSize - TargetSize) 331 << " bits of the input integer will be lost."; 332 333 if (!BT) 334 BT.reset(new APIMisuse("Bad use of CFNumberCreate")); 335 336 BugReport *report = new BugReport(*BT, os.str(), N); 337 report->addRange(CE->getArg(2)->getSourceRange()); 338 C.EmitReport(report); 339 } 340} 341 342//===----------------------------------------------------------------------===// 343// CFRetain/CFRelease checking for null arguments. 344//===----------------------------------------------------------------------===// 345 346namespace { 347class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > { 348 mutable llvm::OwningPtr<APIMisuse> BT; 349 mutable IdentifierInfo *Retain, *Release; 350public: 351 CFRetainReleaseChecker(): Retain(0), Release(0) {} 352 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 353}; 354} // end anonymous namespace 355 356 357void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, 358 CheckerContext &C) const { 359 // If the CallExpr doesn't have exactly 1 argument just give up checking. 360 if (CE->getNumArgs() != 1) 361 return; 362 363 const ProgramState *state = C.getState(); 364 const FunctionDecl *FD = C.getCalleeDecl(CE); 365 if (!FD) 366 return; 367 368 if (!BT) { 369 ASTContext &Ctx = C.getASTContext(); 370 Retain = &Ctx.Idents.get("CFRetain"); 371 Release = &Ctx.Idents.get("CFRelease"); 372 BT.reset(new APIMisuse("null passed to CFRetain/CFRelease")); 373 } 374 375 // Check if we called CFRetain/CFRelease. 376 const IdentifierInfo *FuncII = FD->getIdentifier(); 377 if (!(FuncII == Retain || FuncII == Release)) 378 return; 379 380 // FIXME: The rest of this just checks that the argument is non-null. 381 // It should probably be refactored and combined with AttrNonNullChecker. 382 383 // Get the argument's value. 384 const Expr *Arg = CE->getArg(0); 385 SVal ArgVal = state->getSVal(Arg); 386 DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal); 387 if (!DefArgVal) 388 return; 389 390 // Get a NULL value. 391 SValBuilder &svalBuilder = C.getSValBuilder(); 392 DefinedSVal zero = cast<DefinedSVal>(svalBuilder.makeZeroVal(Arg->getType())); 393 394 // Make an expression asserting that they're equal. 395 DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal); 396 397 // Are they equal? 398 const ProgramState *stateTrue, *stateFalse; 399 llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); 400 401 if (stateTrue && !stateFalse) { 402 ExplodedNode *N = C.generateSink(stateTrue); 403 if (!N) 404 return; 405 406 const char *description = (FuncII == Retain) 407 ? "Null pointer argument in call to CFRetain" 408 : "Null pointer argument in call to CFRelease"; 409 410 BugReport *report = new BugReport(*BT, description, N); 411 report->addRange(Arg->getSourceRange()); 412 report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Arg)); 413 C.EmitReport(report); 414 return; 415 } 416 417 // From here on, we know the argument is non-null. 418 C.addTransition(stateFalse); 419} 420 421//===----------------------------------------------------------------------===// 422// Check for sending 'retain', 'release', or 'autorelease' directly to a Class. 423//===----------------------------------------------------------------------===// 424 425namespace { 426class ClassReleaseChecker : public Checker<check::PreObjCMessage> { 427 mutable Selector releaseS; 428 mutable Selector retainS; 429 mutable Selector autoreleaseS; 430 mutable Selector drainS; 431 mutable llvm::OwningPtr<BugType> BT; 432 433public: 434 void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const; 435}; 436} 437 438void ClassReleaseChecker::checkPreObjCMessage(ObjCMessage msg, 439 CheckerContext &C) const { 440 441 if (!BT) { 442 BT.reset(new APIMisuse("message incorrectly sent to class instead of class " 443 "instance")); 444 445 ASTContext &Ctx = C.getASTContext(); 446 releaseS = GetNullarySelector("release", Ctx); 447 retainS = GetNullarySelector("retain", Ctx); 448 autoreleaseS = GetNullarySelector("autorelease", Ctx); 449 drainS = GetNullarySelector("drain", Ctx); 450 } 451 452 if (msg.isInstanceMessage()) 453 return; 454 const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 455 assert(Class); 456 457 Selector S = msg.getSelector(); 458 if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS)) 459 return; 460 461 if (ExplodedNode *N = C.addTransition()) { 462 llvm::SmallString<200> buf; 463 llvm::raw_svector_ostream os(buf); 464 465 os << "The '" << S.getAsString() << "' message should be sent to instances " 466 "of class '" << Class->getName() 467 << "' and not the class directly"; 468 469 BugReport *report = new BugReport(*BT, os.str(), N); 470 report->addRange(msg.getSourceRange()); 471 C.EmitReport(report); 472 } 473} 474 475//===----------------------------------------------------------------------===// 476// Check for passing non-Objective-C types to variadic methods that expect 477// only Objective-C types. 478//===----------------------------------------------------------------------===// 479 480namespace { 481class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> { 482 mutable Selector arrayWithObjectsS; 483 mutable Selector dictionaryWithObjectsAndKeysS; 484 mutable Selector setWithObjectsS; 485 mutable Selector initWithObjectsS; 486 mutable Selector initWithObjectsAndKeysS; 487 mutable llvm::OwningPtr<BugType> BT; 488 489 bool isVariadicMessage(const ObjCMessage &msg) const; 490 491public: 492 void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const; 493}; 494} 495 496/// isVariadicMessage - Returns whether the given message is a variadic message, 497/// where all arguments must be Objective-C types. 498bool 499VariadicMethodTypeChecker::isVariadicMessage(const ObjCMessage &msg) const { 500 const ObjCMethodDecl *MD = msg.getMethodDecl(); 501 502 if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext())) 503 return false; 504 505 Selector S = msg.getSelector(); 506 507 if (msg.isInstanceMessage()) { 508 // FIXME: Ideally we'd look at the receiver interface here, but that's not 509 // useful for init, because alloc returns 'id'. In theory, this could lead 510 // to false positives, for example if there existed a class that had an 511 // initWithObjects: implementation that does accept non-Objective-C pointer 512 // types, but the chance of that happening is pretty small compared to the 513 // gains that this analysis gives. 514 const ObjCInterfaceDecl *Class = MD->getClassInterface(); 515 516 // -[NSArray initWithObjects:] 517 if (isReceiverClassOrSuperclass(Class, "NSArray") && 518 S == initWithObjectsS) 519 return true; 520 521 // -[NSDictionary initWithObjectsAndKeys:] 522 if (isReceiverClassOrSuperclass(Class, "NSDictionary") && 523 S == initWithObjectsAndKeysS) 524 return true; 525 526 // -[NSSet initWithObjects:] 527 if (isReceiverClassOrSuperclass(Class, "NSSet") && 528 S == initWithObjectsS) 529 return true; 530 } else { 531 const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 532 533 // -[NSArray arrayWithObjects:] 534 if (isReceiverClassOrSuperclass(Class, "NSArray") && 535 S == arrayWithObjectsS) 536 return true; 537 538 // -[NSDictionary dictionaryWithObjectsAndKeys:] 539 if (isReceiverClassOrSuperclass(Class, "NSDictionary") && 540 S == dictionaryWithObjectsAndKeysS) 541 return true; 542 543 // -[NSSet setWithObjects:] 544 if (isReceiverClassOrSuperclass(Class, "NSSet") && 545 S == setWithObjectsS) 546 return true; 547 } 548 549 return false; 550} 551 552void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg, 553 CheckerContext &C) const { 554 if (!BT) { 555 BT.reset(new APIMisuse("Arguments passed to variadic method aren't all " 556 "Objective-C pointer types")); 557 558 ASTContext &Ctx = C.getASTContext(); 559 arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx); 560 dictionaryWithObjectsAndKeysS = 561 GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx); 562 setWithObjectsS = GetUnarySelector("setWithObjects", Ctx); 563 564 initWithObjectsS = GetUnarySelector("initWithObjects", Ctx); 565 initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx); 566 } 567 568 if (!isVariadicMessage(msg)) 569 return; 570 571 // We are not interested in the selector arguments since they have 572 // well-defined types, so the compiler will issue a warning for them. 573 unsigned variadicArgsBegin = msg.getSelector().getNumArgs(); 574 575 // We're not interested in the last argument since it has to be nil or the 576 // compiler would have issued a warning for it elsewhere. 577 unsigned variadicArgsEnd = msg.getNumArgs() - 1; 578 579 if (variadicArgsEnd <= variadicArgsBegin) 580 return; 581 582 // Verify that all arguments have Objective-C types. 583 llvm::Optional<ExplodedNode*> errorNode; 584 const ProgramState *state = C.getState(); 585 586 for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) { 587 QualType ArgTy = msg.getArgType(I); 588 if (ArgTy->isObjCObjectPointerType()) 589 continue; 590 591 // Block pointers are treaded as Objective-C pointers. 592 if (ArgTy->isBlockPointerType()) 593 continue; 594 595 // Ignore pointer constants. 596 if (isa<loc::ConcreteInt>(msg.getArgSVal(I, state))) 597 continue; 598 599 // Ignore pointer types annotated with 'NSObject' attribute. 600 if (C.getASTContext().isObjCNSObjectType(ArgTy)) 601 continue; 602 603 // Ignore CF references, which can be toll-free bridged. 604 if (coreFoundation::isCFObjectRef(ArgTy)) 605 continue; 606 607 // Generate only one error node to use for all bug reports. 608 if (!errorNode.hasValue()) { 609 errorNode = C.addTransition(); 610 } 611 612 if (!errorNode.getValue()) 613 continue; 614 615 llvm::SmallString<128> sbuf; 616 llvm::raw_svector_ostream os(sbuf); 617 618 if (const char *TypeName = GetReceiverNameType(msg)) 619 os << "Argument to '" << TypeName << "' method '"; 620 else 621 os << "Argument to method '"; 622 623 os << msg.getSelector().getAsString() 624 << "' should be an Objective-C pointer type, not '" 625 << ArgTy.getAsString() << "'"; 626 627 BugReport *R = new BugReport(*BT, os.str(), 628 errorNode.getValue()); 629 R->addRange(msg.getArgSourceRange(I)); 630 C.EmitReport(R); 631 } 632} 633 634//===----------------------------------------------------------------------===// 635// Check registration. 636//===----------------------------------------------------------------------===// 637 638void ento::registerNilArgChecker(CheckerManager &mgr) { 639 mgr.registerChecker<NilArgChecker>(); 640} 641 642void ento::registerCFNumberCreateChecker(CheckerManager &mgr) { 643 mgr.registerChecker<CFNumberCreateChecker>(); 644} 645 646void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) { 647 mgr.registerChecker<CFRetainReleaseChecker>(); 648} 649 650void ento::registerClassReleaseChecker(CheckerManager &mgr) { 651 mgr.registerChecker<ClassReleaseChecker>(); 652} 653 654void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) { 655 mgr.registerChecker<VariadicMethodTypeChecker>(); 656} 657