xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp (revision 4903802fbfff23b27f0a030f6103818f6edb2a16)
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 llvm::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     llvm::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 llvm::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     llvm::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 llvm::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     C.EmitReport(report);
416     return;
417   }
418 
419   // From here on, we know the argument is non-null.
420   C.addTransition(stateFalse);
421 }
422 
423 //===----------------------------------------------------------------------===//
424 // Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
425 //===----------------------------------------------------------------------===//
426 
427 namespace {
428 class ClassReleaseChecker : public Checker<check::PreObjCMessage> {
429   mutable Selector releaseS;
430   mutable Selector retainS;
431   mutable Selector autoreleaseS;
432   mutable Selector drainS;
433   mutable llvm::OwningPtr<BugType> BT;
434 
435 public:
436   void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
437 };
438 }
439 
440 void ClassReleaseChecker::checkPreObjCMessage(ObjCMessage msg,
441                                               CheckerContext &C) const {
442 
443   if (!BT) {
444     BT.reset(new APIMisuse("message incorrectly sent to class instead of class "
445                            "instance"));
446 
447     ASTContext &Ctx = C.getASTContext();
448     releaseS = GetNullarySelector("release", Ctx);
449     retainS = GetNullarySelector("retain", Ctx);
450     autoreleaseS = GetNullarySelector("autorelease", Ctx);
451     drainS = GetNullarySelector("drain", Ctx);
452   }
453 
454   if (msg.isInstanceMessage())
455     return;
456   const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
457   assert(Class);
458 
459   Selector S = msg.getSelector();
460   if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
461     return;
462 
463   if (ExplodedNode *N = C.addTransition()) {
464     llvm::SmallString<200> buf;
465     llvm::raw_svector_ostream os(buf);
466 
467     os << "The '" << S.getAsString() << "' message should be sent to instances "
468           "of class '" << Class->getName()
469        << "' and not the class directly";
470 
471     BugReport *report = new BugReport(*BT, os.str(), N);
472     report->addRange(msg.getSourceRange());
473     C.EmitReport(report);
474   }
475 }
476 
477 //===----------------------------------------------------------------------===//
478 // Check for passing non-Objective-C types to variadic methods that expect
479 // only Objective-C types.
480 //===----------------------------------------------------------------------===//
481 
482 namespace {
483 class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> {
484   mutable Selector arrayWithObjectsS;
485   mutable Selector dictionaryWithObjectsAndKeysS;
486   mutable Selector setWithObjectsS;
487   mutable Selector initWithObjectsS;
488   mutable Selector initWithObjectsAndKeysS;
489   mutable llvm::OwningPtr<BugType> BT;
490 
491   bool isVariadicMessage(const ObjCMessage &msg) const;
492 
493 public:
494   void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
495 };
496 }
497 
498 /// isVariadicMessage - Returns whether the given message is a variadic message,
499 /// where all arguments must be Objective-C types.
500 bool
501 VariadicMethodTypeChecker::isVariadicMessage(const ObjCMessage &msg) const {
502   const ObjCMethodDecl *MD = msg.getMethodDecl();
503 
504   if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))
505     return false;
506 
507   Selector S = msg.getSelector();
508 
509   if (msg.isInstanceMessage()) {
510     // FIXME: Ideally we'd look at the receiver interface here, but that's not
511     // useful for init, because alloc returns 'id'. In theory, this could lead
512     // to false positives, for example if there existed a class that had an
513     // initWithObjects: implementation that does accept non-Objective-C pointer
514     // types, but the chance of that happening is pretty small compared to the
515     // gains that this analysis gives.
516     const ObjCInterfaceDecl *Class = MD->getClassInterface();
517 
518     // -[NSArray initWithObjects:]
519     if (isReceiverClassOrSuperclass(Class, "NSArray") &&
520         S == initWithObjectsS)
521       return true;
522 
523     // -[NSDictionary initWithObjectsAndKeys:]
524     if (isReceiverClassOrSuperclass(Class, "NSDictionary") &&
525         S == initWithObjectsAndKeysS)
526       return true;
527 
528     // -[NSSet initWithObjects:]
529     if (isReceiverClassOrSuperclass(Class, "NSSet") &&
530         S == initWithObjectsS)
531       return true;
532   } else {
533     const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
534 
535     // -[NSArray arrayWithObjects:]
536     if (isReceiverClassOrSuperclass(Class, "NSArray") &&
537         S == arrayWithObjectsS)
538       return true;
539 
540     // -[NSDictionary dictionaryWithObjectsAndKeys:]
541     if (isReceiverClassOrSuperclass(Class, "NSDictionary") &&
542         S == dictionaryWithObjectsAndKeysS)
543       return true;
544 
545     // -[NSSet setWithObjects:]
546     if (isReceiverClassOrSuperclass(Class, "NSSet") &&
547         S == setWithObjectsS)
548       return true;
549   }
550 
551   return false;
552 }
553 
554 void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg,
555                                                     CheckerContext &C) const {
556   if (!BT) {
557     BT.reset(new APIMisuse("Arguments passed to variadic method aren't all "
558                            "Objective-C pointer types"));
559 
560     ASTContext &Ctx = C.getASTContext();
561     arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);
562     dictionaryWithObjectsAndKeysS =
563       GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);
564     setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);
565 
566     initWithObjectsS = GetUnarySelector("initWithObjects", Ctx);
567     initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx);
568   }
569 
570   if (!isVariadicMessage(msg))
571       return;
572 
573   // We are not interested in the selector arguments since they have
574   // well-defined types, so the compiler will issue a warning for them.
575   unsigned variadicArgsBegin = msg.getSelector().getNumArgs();
576 
577   // We're not interested in the last argument since it has to be nil or the
578   // compiler would have issued a warning for it elsewhere.
579   unsigned variadicArgsEnd = msg.getNumArgs() - 1;
580 
581   if (variadicArgsEnd <= variadicArgsBegin)
582     return;
583 
584   // Verify that all arguments have Objective-C types.
585   llvm::Optional<ExplodedNode*> errorNode;
586   ProgramStateRef state = C.getState();
587 
588   for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {
589     QualType ArgTy = msg.getArgType(I);
590     if (ArgTy->isObjCObjectPointerType())
591       continue;
592 
593     // Block pointers are treaded as Objective-C pointers.
594     if (ArgTy->isBlockPointerType())
595       continue;
596 
597     // Ignore pointer constants.
598     if (isa<loc::ConcreteInt>(msg.getArgSVal(I, C.getLocationContext(),
599                                              state)))
600       continue;
601 
602     // Ignore pointer types annotated with 'NSObject' attribute.
603     if (C.getASTContext().isObjCNSObjectType(ArgTy))
604       continue;
605 
606     // Ignore CF references, which can be toll-free bridged.
607     if (coreFoundation::isCFObjectRef(ArgTy))
608       continue;
609 
610     // Generate only one error node to use for all bug reports.
611     if (!errorNode.hasValue()) {
612       errorNode = C.addTransition();
613     }
614 
615     if (!errorNode.getValue())
616       continue;
617 
618     llvm::SmallString<128> sbuf;
619     llvm::raw_svector_ostream os(sbuf);
620 
621     if (const char *TypeName = GetReceiverNameType(msg))
622       os << "Argument to '" << TypeName << "' method '";
623     else
624       os << "Argument to method '";
625 
626     os << msg.getSelector().getAsString()
627       << "' should be an Objective-C pointer type, not '"
628       << ArgTy.getAsString() << "'";
629 
630     BugReport *R = new BugReport(*BT, os.str(),
631                                              errorNode.getValue());
632     R->addRange(msg.getArgSourceRange(I));
633     C.EmitReport(R);
634   }
635 }
636 
637 //===----------------------------------------------------------------------===//
638 // Check registration.
639 //===----------------------------------------------------------------------===//
640 
641 void ento::registerNilArgChecker(CheckerManager &mgr) {
642   mgr.registerChecker<NilArgChecker>();
643 }
644 
645 void ento::registerCFNumberCreateChecker(CheckerManager &mgr) {
646   mgr.registerChecker<CFNumberCreateChecker>();
647 }
648 
649 void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
650   mgr.registerChecker<CFRetainReleaseChecker>();
651 }
652 
653 void ento::registerClassReleaseChecker(CheckerManager &mgr) {
654   mgr.registerChecker<ClassReleaseChecker>();
655 }
656 
657 void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
658   mgr.registerChecker<VariadicMethodTypeChecker>();
659 }
660