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