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/AST/ASTContext.h" 18 #include "clang/AST/DeclObjC.h" 19 #include "clang/AST/Expr.h" 20 #include "clang/AST/ExprObjC.h" 21 #include "clang/AST/StmtObjC.h" 22 #include "clang/Analysis/DomainSpecific/CocoaConventions.h" 23 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 24 #include "clang/StaticAnalyzer/Core/Checker.h" 25 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 26 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 27 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 28 #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" 29 #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" 30 #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" 31 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 32 #include "llvm/ADT/SmallString.h" 33 #include "llvm/ADT/StringMap.h" 34 #include "llvm/Support/raw_ostream.h" 35 36 using namespace clang; 37 using namespace ento; 38 39 namespace { 40 class APIMisuse : public BugType { 41 public: 42 APIMisuse(const CheckerBase *checker, const char *name) 43 : BugType(checker, name, "API Misuse (Apple)") {} 44 }; 45 } // end anonymous namespace 46 47 //===----------------------------------------------------------------------===// 48 // Utility functions. 49 //===----------------------------------------------------------------------===// 50 51 static StringRef GetReceiverInterfaceName(const ObjCMethodCall &msg) { 52 if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface()) 53 return ID->getIdentifier()->getName(); 54 return StringRef(); 55 } 56 57 enum FoundationClass { 58 FC_None, 59 FC_NSArray, 60 FC_NSDictionary, 61 FC_NSEnumerator, 62 FC_NSNull, 63 FC_NSOrderedSet, 64 FC_NSSet, 65 FC_NSString 66 }; 67 68 static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID, 69 bool IncludeSuperclasses = true) { 70 static llvm::StringMap<FoundationClass> Classes; 71 if (Classes.empty()) { 72 Classes["NSArray"] = FC_NSArray; 73 Classes["NSDictionary"] = FC_NSDictionary; 74 Classes["NSEnumerator"] = FC_NSEnumerator; 75 Classes["NSNull"] = FC_NSNull; 76 Classes["NSOrderedSet"] = FC_NSOrderedSet; 77 Classes["NSSet"] = FC_NSSet; 78 Classes["NSString"] = FC_NSString; 79 } 80 81 // FIXME: Should we cache this at all? 82 FoundationClass result = Classes.lookup(ID->getIdentifier()->getName()); 83 if (result == FC_None && IncludeSuperclasses) 84 if (const ObjCInterfaceDecl *Super = ID->getSuperClass()) 85 return findKnownClass(Super); 86 87 return result; 88 } 89 90 //===----------------------------------------------------------------------===// 91 // NilArgChecker - Check for prohibited nil arguments to ObjC method calls. 92 //===----------------------------------------------------------------------===// 93 94 namespace { 95 class NilArgChecker : public Checker<check::PreObjCMessage, 96 check::PostStmt<ObjCDictionaryLiteral>, 97 check::PostStmt<ObjCArrayLiteral> > { 98 mutable OwningPtr<APIMisuse> BT; 99 100 void warnIfNilExpr(const Expr *E, 101 const char *Msg, 102 CheckerContext &C) const; 103 104 void warnIfNilArg(CheckerContext &C, 105 const ObjCMethodCall &msg, unsigned Arg, 106 FoundationClass Class, 107 bool CanBeSubscript = false) const; 108 109 void generateBugReport(ExplodedNode *N, 110 StringRef Msg, 111 SourceRange Range, 112 const Expr *Expr, 113 CheckerContext &C) const; 114 115 public: 116 void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; 117 void checkPostStmt(const ObjCDictionaryLiteral *DL, 118 CheckerContext &C) const; 119 void checkPostStmt(const ObjCArrayLiteral *AL, 120 CheckerContext &C) const; 121 }; 122 } 123 124 void NilArgChecker::warnIfNilExpr(const Expr *E, 125 const char *Msg, 126 CheckerContext &C) const { 127 ProgramStateRef State = C.getState(); 128 if (State->isNull(C.getSVal(E)).isConstrainedTrue()) { 129 130 if (ExplodedNode *N = C.generateSink()) { 131 generateBugReport(N, Msg, E->getSourceRange(), E, C); 132 } 133 134 } 135 } 136 137 void NilArgChecker::warnIfNilArg(CheckerContext &C, 138 const ObjCMethodCall &msg, 139 unsigned int Arg, 140 FoundationClass Class, 141 bool CanBeSubscript) const { 142 // Check if the argument is nil. 143 ProgramStateRef State = C.getState(); 144 if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) 145 return; 146 147 if (ExplodedNode *N = C.generateSink()) { 148 SmallString<128> sbuf; 149 llvm::raw_svector_ostream os(sbuf); 150 151 if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) { 152 153 if (Class == FC_NSArray) { 154 os << "Array element cannot be nil"; 155 } else if (Class == FC_NSDictionary) { 156 if (Arg == 0) { 157 os << "Value stored into '"; 158 os << GetReceiverInterfaceName(msg) << "' cannot be nil"; 159 } else { 160 assert(Arg == 1); 161 os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil"; 162 } 163 } else 164 llvm_unreachable("Missing foundation class for the subscript expr"); 165 166 } else { 167 if (Class == FC_NSDictionary) { 168 if (Arg == 0) 169 os << "Value argument "; 170 else { 171 assert(Arg == 1); 172 os << "Key argument "; 173 } 174 os << "to '"; 175 msg.getSelector().print(os); 176 os << "' cannot be nil"; 177 } else { 178 os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '"; 179 msg.getSelector().print(os); 180 os << "' cannot be nil"; 181 } 182 } 183 184 generateBugReport(N, os.str(), msg.getArgSourceRange(Arg), 185 msg.getArgExpr(Arg), C); 186 } 187 } 188 189 void NilArgChecker::generateBugReport(ExplodedNode *N, 190 StringRef Msg, 191 SourceRange Range, 192 const Expr *E, 193 CheckerContext &C) const { 194 if (!BT) 195 BT.reset(new APIMisuse(this, "nil argument")); 196 197 BugReport *R = new BugReport(*BT, Msg, N); 198 R->addRange(Range); 199 bugreporter::trackNullOrUndefValue(N, E, *R); 200 C.emitReport(R); 201 } 202 203 void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg, 204 CheckerContext &C) const { 205 const ObjCInterfaceDecl *ID = msg.getReceiverInterface(); 206 if (!ID) 207 return; 208 209 FoundationClass Class = findKnownClass(ID); 210 211 static const unsigned InvalidArgIndex = UINT_MAX; 212 unsigned Arg = InvalidArgIndex; 213 bool CanBeSubscript = false; 214 215 if (Class == FC_NSString) { 216 Selector S = msg.getSelector(); 217 218 if (S.isUnarySelector()) 219 return; 220 221 // FIXME: This is going to be really slow doing these checks with 222 // lexical comparisons. 223 224 std::string NameStr = S.getAsString(); 225 StringRef Name(NameStr); 226 assert(!Name.empty()); 227 228 // FIXME: Checking for initWithFormat: will not work in most cases 229 // yet because [NSString alloc] returns id, not NSString*. We will 230 // need support for tracking expected-type information in the analyzer 231 // to find these errors. 232 if (Name == "caseInsensitiveCompare:" || 233 Name == "compare:" || 234 Name == "compare:options:" || 235 Name == "compare:options:range:" || 236 Name == "compare:options:range:locale:" || 237 Name == "componentsSeparatedByCharactersInSet:" || 238 Name == "initWithFormat:") { 239 Arg = 0; 240 } 241 } else if (Class == FC_NSArray) { 242 Selector S = msg.getSelector(); 243 244 if (S.isUnarySelector()) 245 return; 246 247 if (S.getNameForSlot(0).equals("addObject")) { 248 Arg = 0; 249 } else if (S.getNameForSlot(0).equals("insertObject") && 250 S.getNameForSlot(1).equals("atIndex")) { 251 Arg = 0; 252 } else if (S.getNameForSlot(0).equals("replaceObjectAtIndex") && 253 S.getNameForSlot(1).equals("withObject")) { 254 Arg = 1; 255 } else if (S.getNameForSlot(0).equals("setObject") && 256 S.getNameForSlot(1).equals("atIndexedSubscript")) { 257 Arg = 0; 258 CanBeSubscript = true; 259 } else if (S.getNameForSlot(0).equals("arrayByAddingObject")) { 260 Arg = 0; 261 } 262 } else if (Class == FC_NSDictionary) { 263 Selector S = msg.getSelector(); 264 265 if (S.isUnarySelector()) 266 return; 267 268 if (S.getNameForSlot(0).equals("dictionaryWithObject") && 269 S.getNameForSlot(1).equals("forKey")) { 270 Arg = 0; 271 warnIfNilArg(C, msg, /* Arg */1, Class); 272 } else if (S.getNameForSlot(0).equals("setObject") && 273 S.getNameForSlot(1).equals("forKey")) { 274 Arg = 0; 275 warnIfNilArg(C, msg, /* Arg */1, Class); 276 } else if (S.getNameForSlot(0).equals("setObject") && 277 S.getNameForSlot(1).equals("forKeyedSubscript")) { 278 CanBeSubscript = true; 279 Arg = 0; 280 warnIfNilArg(C, msg, /* Arg */1, Class, CanBeSubscript); 281 } else if (S.getNameForSlot(0).equals("removeObjectForKey")) { 282 Arg = 0; 283 } 284 } 285 286 // If argument is '0', report a warning. 287 if ((Arg != InvalidArgIndex)) 288 warnIfNilArg(C, msg, Arg, Class, CanBeSubscript); 289 290 } 291 292 void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL, 293 CheckerContext &C) const { 294 unsigned NumOfElements = AL->getNumElements(); 295 for (unsigned i = 0; i < NumOfElements; ++i) { 296 warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C); 297 } 298 } 299 300 void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, 301 CheckerContext &C) const { 302 unsigned NumOfElements = DL->getNumElements(); 303 for (unsigned i = 0; i < NumOfElements; ++i) { 304 ObjCDictionaryElement Element = DL->getKeyValueElement(i); 305 warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C); 306 warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C); 307 } 308 } 309 310 //===----------------------------------------------------------------------===// 311 // Error reporting. 312 //===----------------------------------------------------------------------===// 313 314 namespace { 315 class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > { 316 mutable OwningPtr<APIMisuse> BT; 317 mutable IdentifierInfo* II; 318 public: 319 CFNumberCreateChecker() : II(0) {} 320 321 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 322 323 private: 324 void EmitError(const TypedRegion* R, const Expr *Ex, 325 uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind); 326 }; 327 } // end anonymous namespace 328 329 enum CFNumberType { 330 kCFNumberSInt8Type = 1, 331 kCFNumberSInt16Type = 2, 332 kCFNumberSInt32Type = 3, 333 kCFNumberSInt64Type = 4, 334 kCFNumberFloat32Type = 5, 335 kCFNumberFloat64Type = 6, 336 kCFNumberCharType = 7, 337 kCFNumberShortType = 8, 338 kCFNumberIntType = 9, 339 kCFNumberLongType = 10, 340 kCFNumberLongLongType = 11, 341 kCFNumberFloatType = 12, 342 kCFNumberDoubleType = 13, 343 kCFNumberCFIndexType = 14, 344 kCFNumberNSIntegerType = 15, 345 kCFNumberCGFloatType = 16 346 }; 347 348 static Optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) { 349 static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 }; 350 351 if (i < kCFNumberCharType) 352 return FixedSize[i-1]; 353 354 QualType T; 355 356 switch (i) { 357 case kCFNumberCharType: T = Ctx.CharTy; break; 358 case kCFNumberShortType: T = Ctx.ShortTy; break; 359 case kCFNumberIntType: T = Ctx.IntTy; break; 360 case kCFNumberLongType: T = Ctx.LongTy; break; 361 case kCFNumberLongLongType: T = Ctx.LongLongTy; break; 362 case kCFNumberFloatType: T = Ctx.FloatTy; break; 363 case kCFNumberDoubleType: T = Ctx.DoubleTy; break; 364 case kCFNumberCFIndexType: 365 case kCFNumberNSIntegerType: 366 case kCFNumberCGFloatType: 367 // FIXME: We need a way to map from names to Type*. 368 default: 369 return None; 370 } 371 372 return Ctx.getTypeSize(T); 373 } 374 375 #if 0 376 static const char* GetCFNumberTypeStr(uint64_t i) { 377 static const char* Names[] = { 378 "kCFNumberSInt8Type", 379 "kCFNumberSInt16Type", 380 "kCFNumberSInt32Type", 381 "kCFNumberSInt64Type", 382 "kCFNumberFloat32Type", 383 "kCFNumberFloat64Type", 384 "kCFNumberCharType", 385 "kCFNumberShortType", 386 "kCFNumberIntType", 387 "kCFNumberLongType", 388 "kCFNumberLongLongType", 389 "kCFNumberFloatType", 390 "kCFNumberDoubleType", 391 "kCFNumberCFIndexType", 392 "kCFNumberNSIntegerType", 393 "kCFNumberCGFloatType" 394 }; 395 396 return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType"; 397 } 398 #endif 399 400 void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, 401 CheckerContext &C) const { 402 ProgramStateRef state = C.getState(); 403 const FunctionDecl *FD = C.getCalleeDecl(CE); 404 if (!FD) 405 return; 406 407 ASTContext &Ctx = C.getASTContext(); 408 if (!II) 409 II = &Ctx.Idents.get("CFNumberCreate"); 410 411 if (FD->getIdentifier() != II || CE->getNumArgs() != 3) 412 return; 413 414 // Get the value of the "theType" argument. 415 const LocationContext *LCtx = C.getLocationContext(); 416 SVal TheTypeVal = state->getSVal(CE->getArg(1), LCtx); 417 418 // FIXME: We really should allow ranges of valid theType values, and 419 // bifurcate the state appropriately. 420 Optional<nonloc::ConcreteInt> V = TheTypeVal.getAs<nonloc::ConcreteInt>(); 421 if (!V) 422 return; 423 424 uint64_t NumberKind = V->getValue().getLimitedValue(); 425 Optional<uint64_t> OptTargetSize = GetCFNumberSize(Ctx, NumberKind); 426 427 // FIXME: In some cases we can emit an error. 428 if (!OptTargetSize) 429 return; 430 431 uint64_t TargetSize = *OptTargetSize; 432 433 // Look at the value of the integer being passed by reference. Essentially 434 // we want to catch cases where the value passed in is not equal to the 435 // size of the type being created. 436 SVal TheValueExpr = state->getSVal(CE->getArg(2), LCtx); 437 438 // FIXME: Eventually we should handle arbitrary locations. We can do this 439 // by having an enhanced memory model that does low-level typing. 440 Optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>(); 441 if (!LV) 442 return; 443 444 const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts()); 445 if (!R) 446 return; 447 448 QualType T = Ctx.getCanonicalType(R->getValueType()); 449 450 // FIXME: If the pointee isn't an integer type, should we flag a warning? 451 // People can do weird stuff with pointers. 452 453 if (!T->isIntegralOrEnumerationType()) 454 return; 455 456 uint64_t SourceSize = Ctx.getTypeSize(T); 457 458 // CHECK: is SourceSize == TargetSize 459 if (SourceSize == TargetSize) 460 return; 461 462 // Generate an error. Only generate a sink if 'SourceSize < TargetSize'; 463 // otherwise generate a regular node. 464 // 465 // FIXME: We can actually create an abstract "CFNumber" object that has 466 // the bits initialized to the provided values. 467 // 468 if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink() 469 : C.addTransition()) { 470 SmallString<128> sbuf; 471 llvm::raw_svector_ostream os(sbuf); 472 473 os << (SourceSize == 8 ? "An " : "A ") 474 << SourceSize << " bit integer is used to initialize a CFNumber " 475 "object that represents " 476 << (TargetSize == 8 ? "an " : "a ") 477 << TargetSize << " bit integer. "; 478 479 if (SourceSize < TargetSize) 480 os << (TargetSize - SourceSize) 481 << " bits of the CFNumber value will be garbage." ; 482 else 483 os << (SourceSize - TargetSize) 484 << " bits of the input integer will be lost."; 485 486 if (!BT) 487 BT.reset(new APIMisuse(this, "Bad use of CFNumberCreate")); 488 489 BugReport *report = new BugReport(*BT, os.str(), N); 490 report->addRange(CE->getArg(2)->getSourceRange()); 491 C.emitReport(report); 492 } 493 } 494 495 //===----------------------------------------------------------------------===// 496 // CFRetain/CFRelease/CFMakeCollectable checking for null arguments. 497 //===----------------------------------------------------------------------===// 498 499 namespace { 500 class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > { 501 mutable OwningPtr<APIMisuse> BT; 502 mutable IdentifierInfo *Retain, *Release, *MakeCollectable; 503 public: 504 CFRetainReleaseChecker(): Retain(0), Release(0), MakeCollectable(0) {} 505 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 506 }; 507 } // end anonymous namespace 508 509 510 void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, 511 CheckerContext &C) const { 512 // If the CallExpr doesn't have exactly 1 argument just give up checking. 513 if (CE->getNumArgs() != 1) 514 return; 515 516 ProgramStateRef state = C.getState(); 517 const FunctionDecl *FD = C.getCalleeDecl(CE); 518 if (!FD) 519 return; 520 521 if (!BT) { 522 ASTContext &Ctx = C.getASTContext(); 523 Retain = &Ctx.Idents.get("CFRetain"); 524 Release = &Ctx.Idents.get("CFRelease"); 525 MakeCollectable = &Ctx.Idents.get("CFMakeCollectable"); 526 BT.reset(new APIMisuse( 527 this, "null passed to CFRetain/CFRelease/CFMakeCollectable")); 528 } 529 530 // Check if we called CFRetain/CFRelease/CFMakeCollectable. 531 const IdentifierInfo *FuncII = FD->getIdentifier(); 532 if (!(FuncII == Retain || FuncII == Release || FuncII == MakeCollectable)) 533 return; 534 535 // FIXME: The rest of this just checks that the argument is non-null. 536 // It should probably be refactored and combined with NonNullParamChecker. 537 538 // Get the argument's value. 539 const Expr *Arg = CE->getArg(0); 540 SVal ArgVal = state->getSVal(Arg, C.getLocationContext()); 541 Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>(); 542 if (!DefArgVal) 543 return; 544 545 // Get a NULL value. 546 SValBuilder &svalBuilder = C.getSValBuilder(); 547 DefinedSVal zero = 548 svalBuilder.makeZeroVal(Arg->getType()).castAs<DefinedSVal>(); 549 550 // Make an expression asserting that they're equal. 551 DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal); 552 553 // Are they equal? 554 ProgramStateRef stateTrue, stateFalse; 555 llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); 556 557 if (stateTrue && !stateFalse) { 558 ExplodedNode *N = C.generateSink(stateTrue); 559 if (!N) 560 return; 561 562 const char *description; 563 if (FuncII == Retain) 564 description = "Null pointer argument in call to CFRetain"; 565 else if (FuncII == Release) 566 description = "Null pointer argument in call to CFRelease"; 567 else if (FuncII == MakeCollectable) 568 description = "Null pointer argument in call to CFMakeCollectable"; 569 else 570 llvm_unreachable("impossible case"); 571 572 BugReport *report = new BugReport(*BT, description, N); 573 report->addRange(Arg->getSourceRange()); 574 bugreporter::trackNullOrUndefValue(N, Arg, *report); 575 C.emitReport(report); 576 return; 577 } 578 579 // From here on, we know the argument is non-null. 580 C.addTransition(stateFalse); 581 } 582 583 //===----------------------------------------------------------------------===// 584 // Check for sending 'retain', 'release', or 'autorelease' directly to a Class. 585 //===----------------------------------------------------------------------===// 586 587 namespace { 588 class ClassReleaseChecker : public Checker<check::PreObjCMessage> { 589 mutable Selector releaseS; 590 mutable Selector retainS; 591 mutable Selector autoreleaseS; 592 mutable Selector drainS; 593 mutable OwningPtr<BugType> BT; 594 595 public: 596 void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; 597 }; 598 } 599 600 void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, 601 CheckerContext &C) const { 602 603 if (!BT) { 604 BT.reset(new APIMisuse( 605 this, "message incorrectly sent to class instead of class instance")); 606 607 ASTContext &Ctx = C.getASTContext(); 608 releaseS = GetNullarySelector("release", Ctx); 609 retainS = GetNullarySelector("retain", Ctx); 610 autoreleaseS = GetNullarySelector("autorelease", Ctx); 611 drainS = GetNullarySelector("drain", Ctx); 612 } 613 614 if (msg.isInstanceMessage()) 615 return; 616 const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 617 assert(Class); 618 619 Selector S = msg.getSelector(); 620 if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS)) 621 return; 622 623 if (ExplodedNode *N = C.addTransition()) { 624 SmallString<200> buf; 625 llvm::raw_svector_ostream os(buf); 626 627 os << "The '"; 628 S.print(os); 629 os << "' message should be sent to instances " 630 "of class '" << Class->getName() 631 << "' and not the class directly"; 632 633 BugReport *report = new BugReport(*BT, os.str(), N); 634 report->addRange(msg.getSourceRange()); 635 C.emitReport(report); 636 } 637 } 638 639 //===----------------------------------------------------------------------===// 640 // Check for passing non-Objective-C types to variadic methods that expect 641 // only Objective-C types. 642 //===----------------------------------------------------------------------===// 643 644 namespace { 645 class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> { 646 mutable Selector arrayWithObjectsS; 647 mutable Selector dictionaryWithObjectsAndKeysS; 648 mutable Selector setWithObjectsS; 649 mutable Selector orderedSetWithObjectsS; 650 mutable Selector initWithObjectsS; 651 mutable Selector initWithObjectsAndKeysS; 652 mutable OwningPtr<BugType> BT; 653 654 bool isVariadicMessage(const ObjCMethodCall &msg) const; 655 656 public: 657 void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; 658 }; 659 } 660 661 /// isVariadicMessage - Returns whether the given message is a variadic message, 662 /// where all arguments must be Objective-C types. 663 bool 664 VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const { 665 const ObjCMethodDecl *MD = msg.getDecl(); 666 667 if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext())) 668 return false; 669 670 Selector S = msg.getSelector(); 671 672 if (msg.isInstanceMessage()) { 673 // FIXME: Ideally we'd look at the receiver interface here, but that's not 674 // useful for init, because alloc returns 'id'. In theory, this could lead 675 // to false positives, for example if there existed a class that had an 676 // initWithObjects: implementation that does accept non-Objective-C pointer 677 // types, but the chance of that happening is pretty small compared to the 678 // gains that this analysis gives. 679 const ObjCInterfaceDecl *Class = MD->getClassInterface(); 680 681 switch (findKnownClass(Class)) { 682 case FC_NSArray: 683 case FC_NSOrderedSet: 684 case FC_NSSet: 685 return S == initWithObjectsS; 686 case FC_NSDictionary: 687 return S == initWithObjectsAndKeysS; 688 default: 689 return false; 690 } 691 } else { 692 const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 693 694 switch (findKnownClass(Class)) { 695 case FC_NSArray: 696 return S == arrayWithObjectsS; 697 case FC_NSOrderedSet: 698 return S == orderedSetWithObjectsS; 699 case FC_NSSet: 700 return S == setWithObjectsS; 701 case FC_NSDictionary: 702 return S == dictionaryWithObjectsAndKeysS; 703 default: 704 return false; 705 } 706 } 707 } 708 709 void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, 710 CheckerContext &C) const { 711 if (!BT) { 712 BT.reset(new APIMisuse(this, 713 "Arguments passed to variadic method aren't all " 714 "Objective-C pointer types")); 715 716 ASTContext &Ctx = C.getASTContext(); 717 arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx); 718 dictionaryWithObjectsAndKeysS = 719 GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx); 720 setWithObjectsS = GetUnarySelector("setWithObjects", Ctx); 721 orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx); 722 723 initWithObjectsS = GetUnarySelector("initWithObjects", Ctx); 724 initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx); 725 } 726 727 if (!isVariadicMessage(msg)) 728 return; 729 730 // We are not interested in the selector arguments since they have 731 // well-defined types, so the compiler will issue a warning for them. 732 unsigned variadicArgsBegin = msg.getSelector().getNumArgs(); 733 734 // We're not interested in the last argument since it has to be nil or the 735 // compiler would have issued a warning for it elsewhere. 736 unsigned variadicArgsEnd = msg.getNumArgs() - 1; 737 738 if (variadicArgsEnd <= variadicArgsBegin) 739 return; 740 741 // Verify that all arguments have Objective-C types. 742 Optional<ExplodedNode*> errorNode; 743 744 for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) { 745 QualType ArgTy = msg.getArgExpr(I)->getType(); 746 if (ArgTy->isObjCObjectPointerType()) 747 continue; 748 749 // Block pointers are treaded as Objective-C pointers. 750 if (ArgTy->isBlockPointerType()) 751 continue; 752 753 // Ignore pointer constants. 754 if (msg.getArgSVal(I).getAs<loc::ConcreteInt>()) 755 continue; 756 757 // Ignore pointer types annotated with 'NSObject' attribute. 758 if (C.getASTContext().isObjCNSObjectType(ArgTy)) 759 continue; 760 761 // Ignore CF references, which can be toll-free bridged. 762 if (coreFoundation::isCFObjectRef(ArgTy)) 763 continue; 764 765 // Generate only one error node to use for all bug reports. 766 if (!errorNode.hasValue()) 767 errorNode = C.addTransition(); 768 769 if (!errorNode.getValue()) 770 continue; 771 772 SmallString<128> sbuf; 773 llvm::raw_svector_ostream os(sbuf); 774 775 StringRef TypeName = GetReceiverInterfaceName(msg); 776 if (!TypeName.empty()) 777 os << "Argument to '" << TypeName << "' method '"; 778 else 779 os << "Argument to method '"; 780 781 msg.getSelector().print(os); 782 os << "' should be an Objective-C pointer type, not '"; 783 ArgTy.print(os, C.getLangOpts()); 784 os << "'"; 785 786 BugReport *R = new BugReport(*BT, os.str(), errorNode.getValue()); 787 R->addRange(msg.getArgSourceRange(I)); 788 C.emitReport(R); 789 } 790 } 791 792 //===----------------------------------------------------------------------===// 793 // Improves the modeling of loops over Cocoa collections. 794 //===----------------------------------------------------------------------===// 795 796 // The map from container symbol to the container count symbol. 797 // We currently will remember the last countainer count symbol encountered. 798 REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef) 799 REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool) 800 801 namespace { 802 class ObjCLoopChecker 803 : public Checker<check::PostStmt<ObjCForCollectionStmt>, 804 check::PostObjCMessage, 805 check::DeadSymbols, 806 check::PointerEscape > { 807 mutable IdentifierInfo *CountSelectorII; 808 809 bool isCollectionCountMethod(const ObjCMethodCall &M, 810 CheckerContext &C) const; 811 812 public: 813 ObjCLoopChecker() : CountSelectorII(0) {} 814 void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const; 815 void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; 816 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 817 ProgramStateRef checkPointerEscape(ProgramStateRef State, 818 const InvalidatedSymbols &Escaped, 819 const CallEvent *Call, 820 PointerEscapeKind Kind) const; 821 }; 822 } 823 824 static bool isKnownNonNilCollectionType(QualType T) { 825 const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>(); 826 if (!PT) 827 return false; 828 829 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); 830 if (!ID) 831 return false; 832 833 switch (findKnownClass(ID)) { 834 case FC_NSArray: 835 case FC_NSDictionary: 836 case FC_NSEnumerator: 837 case FC_NSOrderedSet: 838 case FC_NSSet: 839 return true; 840 default: 841 return false; 842 } 843 } 844 845 /// Assumes that the collection is non-nil. 846 /// 847 /// If the collection is known to be nil, returns NULL to indicate an infeasible 848 /// path. 849 static ProgramStateRef checkCollectionNonNil(CheckerContext &C, 850 ProgramStateRef State, 851 const ObjCForCollectionStmt *FCS) { 852 if (!State) 853 return NULL; 854 855 SVal CollectionVal = C.getSVal(FCS->getCollection()); 856 Optional<DefinedSVal> KnownCollection = CollectionVal.getAs<DefinedSVal>(); 857 if (!KnownCollection) 858 return State; 859 860 ProgramStateRef StNonNil, StNil; 861 llvm::tie(StNonNil, StNil) = State->assume(*KnownCollection); 862 if (StNil && !StNonNil) { 863 // The collection is nil. This path is infeasible. 864 return NULL; 865 } 866 867 return StNonNil; 868 } 869 870 /// Assumes that the collection elements are non-nil. 871 /// 872 /// This only applies if the collection is one of those known not to contain 873 /// nil values. 874 static ProgramStateRef checkElementNonNil(CheckerContext &C, 875 ProgramStateRef State, 876 const ObjCForCollectionStmt *FCS) { 877 if (!State) 878 return NULL; 879 880 // See if the collection is one where we /know/ the elements are non-nil. 881 if (!isKnownNonNilCollectionType(FCS->getCollection()->getType())) 882 return State; 883 884 const LocationContext *LCtx = C.getLocationContext(); 885 const Stmt *Element = FCS->getElement(); 886 887 // FIXME: Copied from ExprEngineObjC. 888 Optional<Loc> ElementLoc; 889 if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) { 890 const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl()); 891 assert(ElemDecl->getInit() == 0); 892 ElementLoc = State->getLValue(ElemDecl, LCtx); 893 } else { 894 ElementLoc = State->getSVal(Element, LCtx).getAs<Loc>(); 895 } 896 897 if (!ElementLoc) 898 return State; 899 900 // Go ahead and assume the value is non-nil. 901 SVal Val = State->getSVal(*ElementLoc); 902 return State->assume(Val.castAs<DefinedOrUnknownSVal>(), true); 903 } 904 905 /// Returns NULL state if the collection is known to contain elements 906 /// (or is known not to contain elements if the Assumption parameter is false.) 907 static ProgramStateRef 908 assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State, 909 SymbolRef CollectionS, bool Assumption) { 910 if (!State || !CollectionS) 911 return State; 912 913 const SymbolRef *CountS = State->get<ContainerCountMap>(CollectionS); 914 if (!CountS) { 915 const bool *KnownNonEmpty = State->get<ContainerNonEmptyMap>(CollectionS); 916 if (!KnownNonEmpty) 917 return State->set<ContainerNonEmptyMap>(CollectionS, Assumption); 918 return (Assumption == *KnownNonEmpty) ? State : NULL; 919 } 920 921 SValBuilder &SvalBuilder = C.getSValBuilder(); 922 SVal CountGreaterThanZeroVal = 923 SvalBuilder.evalBinOp(State, BO_GT, 924 nonloc::SymbolVal(*CountS), 925 SvalBuilder.makeIntVal(0, (*CountS)->getType()), 926 SvalBuilder.getConditionType()); 927 Optional<DefinedSVal> CountGreaterThanZero = 928 CountGreaterThanZeroVal.getAs<DefinedSVal>(); 929 if (!CountGreaterThanZero) { 930 // The SValBuilder cannot construct a valid SVal for this condition. 931 // This means we cannot properly reason about it. 932 return State; 933 } 934 935 return State->assume(*CountGreaterThanZero, Assumption); 936 } 937 938 static ProgramStateRef 939 assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State, 940 const ObjCForCollectionStmt *FCS, 941 bool Assumption) { 942 if (!State) 943 return NULL; 944 945 SymbolRef CollectionS = 946 State->getSVal(FCS->getCollection(), C.getLocationContext()).getAsSymbol(); 947 return assumeCollectionNonEmpty(C, State, CollectionS, Assumption); 948 } 949 950 951 /// If the fist block edge is a back edge, we are reentering the loop. 952 static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N, 953 const ObjCForCollectionStmt *FCS) { 954 if (!N) 955 return false; 956 957 ProgramPoint P = N->getLocation(); 958 if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) { 959 if (BE->getSrc()->getLoopTarget() == FCS) 960 return true; 961 return false; 962 } 963 964 // Keep looking for a block edge. 965 for (ExplodedNode::const_pred_iterator I = N->pred_begin(), 966 E = N->pred_end(); I != E; ++I) { 967 if (alreadyExecutedAtLeastOneLoopIteration(*I, FCS)) 968 return true; 969 } 970 971 return false; 972 } 973 974 void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS, 975 CheckerContext &C) const { 976 ProgramStateRef State = C.getState(); 977 978 // Check if this is the branch for the end of the loop. 979 SVal CollectionSentinel = C.getSVal(FCS); 980 if (CollectionSentinel.isZeroConstant()) { 981 if (!alreadyExecutedAtLeastOneLoopIteration(C.getPredecessor(), FCS)) 982 State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/false); 983 984 // Otherwise, this is a branch that goes through the loop body. 985 } else { 986 State = checkCollectionNonNil(C, State, FCS); 987 State = checkElementNonNil(C, State, FCS); 988 State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true); 989 } 990 991 if (!State) 992 C.generateSink(); 993 else if (State != C.getState()) 994 C.addTransition(State); 995 } 996 997 bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M, 998 CheckerContext &C) const { 999 Selector S = M.getSelector(); 1000 // Initialize the identifiers on first use. 1001 if (!CountSelectorII) 1002 CountSelectorII = &C.getASTContext().Idents.get("count"); 1003 1004 // If the method returns collection count, record the value. 1005 if (S.isUnarySelector() && 1006 (S.getIdentifierInfoForSlot(0) == CountSelectorII)) 1007 return true; 1008 1009 return false; 1010 } 1011 1012 void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M, 1013 CheckerContext &C) const { 1014 if (!M.isInstanceMessage()) 1015 return; 1016 1017 const ObjCInterfaceDecl *ClassID = M.getReceiverInterface(); 1018 if (!ClassID) 1019 return; 1020 1021 FoundationClass Class = findKnownClass(ClassID); 1022 if (Class != FC_NSDictionary && 1023 Class != FC_NSArray && 1024 Class != FC_NSSet && 1025 Class != FC_NSOrderedSet) 1026 return; 1027 1028 SymbolRef ContainerS = M.getReceiverSVal().getAsSymbol(); 1029 if (!ContainerS) 1030 return; 1031 1032 // If we are processing a call to "count", get the symbolic value returned by 1033 // a call to "count" and add it to the map. 1034 if (!isCollectionCountMethod(M, C)) 1035 return; 1036 1037 const Expr *MsgExpr = M.getOriginExpr(); 1038 SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol(); 1039 if (CountS) { 1040 ProgramStateRef State = C.getState(); 1041 1042 C.getSymbolManager().addSymbolDependency(ContainerS, CountS); 1043 State = State->set<ContainerCountMap>(ContainerS, CountS); 1044 1045 if (const bool *NonEmpty = State->get<ContainerNonEmptyMap>(ContainerS)) { 1046 State = State->remove<ContainerNonEmptyMap>(ContainerS); 1047 State = assumeCollectionNonEmpty(C, State, ContainerS, *NonEmpty); 1048 } 1049 1050 C.addTransition(State); 1051 } 1052 return; 1053 } 1054 1055 static SymbolRef getMethodReceiverIfKnownImmutable(const CallEvent *Call) { 1056 const ObjCMethodCall *Message = dyn_cast_or_null<ObjCMethodCall>(Call); 1057 if (!Message) 1058 return 0; 1059 1060 const ObjCMethodDecl *MD = Message->getDecl(); 1061 if (!MD) 1062 return 0; 1063 1064 const ObjCInterfaceDecl *StaticClass; 1065 if (isa<ObjCProtocolDecl>(MD->getDeclContext())) { 1066 // We can't find out where the method was declared without doing more work. 1067 // Instead, see if the receiver is statically typed as a known immutable 1068 // collection. 1069 StaticClass = Message->getOriginExpr()->getReceiverInterface(); 1070 } else { 1071 StaticClass = MD->getClassInterface(); 1072 } 1073 1074 if (!StaticClass) 1075 return 0; 1076 1077 switch (findKnownClass(StaticClass, /*IncludeSuper=*/false)) { 1078 case FC_None: 1079 return 0; 1080 case FC_NSArray: 1081 case FC_NSDictionary: 1082 case FC_NSEnumerator: 1083 case FC_NSNull: 1084 case FC_NSOrderedSet: 1085 case FC_NSSet: 1086 case FC_NSString: 1087 break; 1088 } 1089 1090 return Message->getReceiverSVal().getAsSymbol(); 1091 } 1092 1093 ProgramStateRef 1094 ObjCLoopChecker::checkPointerEscape(ProgramStateRef State, 1095 const InvalidatedSymbols &Escaped, 1096 const CallEvent *Call, 1097 PointerEscapeKind Kind) const { 1098 SymbolRef ImmutableReceiver = getMethodReceiverIfKnownImmutable(Call); 1099 1100 // Remove the invalidated symbols form the collection count map. 1101 for (InvalidatedSymbols::const_iterator I = Escaped.begin(), 1102 E = Escaped.end(); 1103 I != E; ++I) { 1104 SymbolRef Sym = *I; 1105 1106 // Don't invalidate this symbol's count if we know the method being called 1107 // is declared on an immutable class. This isn't completely correct if the 1108 // receiver is also passed as an argument, but in most uses of NSArray, 1109 // NSDictionary, etc. this isn't likely to happen in a dangerous way. 1110 if (Sym == ImmutableReceiver) 1111 continue; 1112 1113 // The symbol escaped. Pessimistically, assume that the count could have 1114 // changed. 1115 State = State->remove<ContainerCountMap>(Sym); 1116 State = State->remove<ContainerNonEmptyMap>(Sym); 1117 } 1118 return State; 1119 } 1120 1121 void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper, 1122 CheckerContext &C) const { 1123 ProgramStateRef State = C.getState(); 1124 1125 // Remove the dead symbols from the collection count map. 1126 ContainerCountMapTy Tracked = State->get<ContainerCountMap>(); 1127 for (ContainerCountMapTy::iterator I = Tracked.begin(), 1128 E = Tracked.end(); I != E; ++I) { 1129 SymbolRef Sym = I->first; 1130 if (SymReaper.isDead(Sym)) { 1131 State = State->remove<ContainerCountMap>(Sym); 1132 State = State->remove<ContainerNonEmptyMap>(Sym); 1133 } 1134 } 1135 1136 C.addTransition(State); 1137 } 1138 1139 namespace { 1140 /// \class ObjCNonNilReturnValueChecker 1141 /// \brief The checker restricts the return values of APIs known to 1142 /// never (or almost never) return 'nil'. 1143 class ObjCNonNilReturnValueChecker 1144 : public Checker<check::PostObjCMessage, 1145 check::PostStmt<ObjCArrayLiteral>, 1146 check::PostStmt<ObjCDictionaryLiteral>, 1147 check::PostStmt<ObjCBoxedExpr> > { 1148 mutable bool Initialized; 1149 mutable Selector ObjectAtIndex; 1150 mutable Selector ObjectAtIndexedSubscript; 1151 mutable Selector NullSelector; 1152 1153 public: 1154 ObjCNonNilReturnValueChecker() : Initialized(false) {} 1155 1156 ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr, 1157 ProgramStateRef State, 1158 CheckerContext &C) const; 1159 void assumeExprIsNonNull(const Expr *E, CheckerContext &C) const { 1160 C.addTransition(assumeExprIsNonNull(E, C.getState(), C)); 1161 } 1162 1163 void checkPostStmt(const ObjCArrayLiteral *E, CheckerContext &C) const { 1164 assumeExprIsNonNull(E, C); 1165 } 1166 void checkPostStmt(const ObjCDictionaryLiteral *E, CheckerContext &C) const { 1167 assumeExprIsNonNull(E, C); 1168 } 1169 void checkPostStmt(const ObjCBoxedExpr *E, CheckerContext &C) const { 1170 assumeExprIsNonNull(E, C); 1171 } 1172 1173 void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; 1174 }; 1175 } 1176 1177 ProgramStateRef 1178 ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr, 1179 ProgramStateRef State, 1180 CheckerContext &C) const { 1181 SVal Val = State->getSVal(NonNullExpr, C.getLocationContext()); 1182 if (Optional<DefinedOrUnknownSVal> DV = Val.getAs<DefinedOrUnknownSVal>()) 1183 return State->assume(*DV, true); 1184 return State; 1185 } 1186 1187 void ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M, 1188 CheckerContext &C) 1189 const { 1190 ProgramStateRef State = C.getState(); 1191 1192 if (!Initialized) { 1193 ASTContext &Ctx = C.getASTContext(); 1194 ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx); 1195 ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx); 1196 NullSelector = GetNullarySelector("null", Ctx); 1197 } 1198 1199 // Check the receiver type. 1200 if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) { 1201 1202 // Assume that object returned from '[self init]' or '[super init]' is not 1203 // 'nil' if we are processing an inlined function/method. 1204 // 1205 // A defensive callee will (and should) check if the object returned by 1206 // '[super init]' is 'nil' before doing it's own initialization. However, 1207 // since 'nil' is rarely returned in practice, we should not warn when the 1208 // caller to the defensive constructor uses the object in contexts where 1209 // 'nil' is not accepted. 1210 if (!C.inTopFrame() && M.getDecl() && 1211 M.getDecl()->getMethodFamily() == OMF_init && 1212 M.isReceiverSelfOrSuper()) { 1213 State = assumeExprIsNonNull(M.getOriginExpr(), State, C); 1214 } 1215 1216 FoundationClass Cl = findKnownClass(Interface); 1217 1218 // Objects returned from 1219 // [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript] 1220 // are never 'nil'. 1221 if (Cl == FC_NSArray || Cl == FC_NSOrderedSet) { 1222 Selector Sel = M.getSelector(); 1223 if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript) { 1224 // Go ahead and assume the value is non-nil. 1225 State = assumeExprIsNonNull(M.getOriginExpr(), State, C); 1226 } 1227 } 1228 1229 // Objects returned from [NSNull null] are not nil. 1230 if (Cl == FC_NSNull) { 1231 if (M.getSelector() == NullSelector) { 1232 // Go ahead and assume the value is non-nil. 1233 State = assumeExprIsNonNull(M.getOriginExpr(), State, C); 1234 } 1235 } 1236 } 1237 C.addTransition(State); 1238 } 1239 1240 //===----------------------------------------------------------------------===// 1241 // Check registration. 1242 //===----------------------------------------------------------------------===// 1243 1244 void ento::registerNilArgChecker(CheckerManager &mgr) { 1245 mgr.registerChecker<NilArgChecker>(); 1246 } 1247 1248 void ento::registerCFNumberCreateChecker(CheckerManager &mgr) { 1249 mgr.registerChecker<CFNumberCreateChecker>(); 1250 } 1251 1252 void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) { 1253 mgr.registerChecker<CFRetainReleaseChecker>(); 1254 } 1255 1256 void ento::registerClassReleaseChecker(CheckerManager &mgr) { 1257 mgr.registerChecker<ClassReleaseChecker>(); 1258 } 1259 1260 void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) { 1261 mgr.registerChecker<VariadicMethodTypeChecker>(); 1262 } 1263 1264 void ento::registerObjCLoopChecker(CheckerManager &mgr) { 1265 mgr.registerChecker<ObjCLoopChecker>(); 1266 } 1267 1268 void 1269 ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) { 1270 mgr.registerChecker<ObjCNonNilReturnValueChecker>(); 1271 } 1272