xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp (revision 49b1e38e4bee0f0c6f8b49e1a62d5284084e09e7)
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 
32 using namespace clang;
33 using namespace ento;
34 
35 namespace {
36 class APIMisuse : public BugType {
37 public:
38   APIMisuse(const char* name) : BugType(name, "API Misuse (Apple)") {}
39 };
40 } // end anonymous namespace
41 
42 //===----------------------------------------------------------------------===//
43 // Utility functions.
44 //===----------------------------------------------------------------------===//
45 
46 static const char* GetReceiverNameType(const ObjCMessage &msg) {
47   if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())
48     return ID->getIdentifier()->getNameStart();
49   return 0;
50 }
51 
52 static 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 
63 static 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 
71 namespace {
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 
83 void 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 
102 void 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.getLocationContext(), C.getState())))
133         WarnNilArg(C, msg, 0);
134     }
135   }
136 }
137 
138 //===----------------------------------------------------------------------===//
139 // Error reporting.
140 //===----------------------------------------------------------------------===//
141 
142 namespace {
143 class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > {
144   mutable llvm::OwningPtr<APIMisuse> BT;
145   mutable IdentifierInfo* II;
146 public:
147   CFNumberCreateChecker() : II(0) {}
148 
149   void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
150 
151 private:
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 
157 enum 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 
176 namespace {
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 
198 static 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
226 static 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 
250 void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE,
251                                          CheckerContext &C) const {
252   ProgramStateRef 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   const LocationContext *LCtx = C.getLocationContext();
266   SVal TheTypeVal = state->getSVal(CE->getArg(1), LCtx);
267 
268   // FIXME: We really should allow ranges of valid theType values, and
269   //   bifurcate the state appropriately.
270   nonloc::ConcreteInt* V = dyn_cast<nonloc::ConcreteInt>(&TheTypeVal);
271   if (!V)
272     return;
273 
274   uint64_t NumberKind = V->getValue().getLimitedValue();
275   Optional<uint64_t> TargetSize = GetCFNumberSize(Ctx, NumberKind);
276 
277   // FIXME: In some cases we can emit an error.
278   if (!TargetSize.isKnown())
279     return;
280 
281   // Look at the value of the integer being passed by reference.  Essentially
282   // we want to catch cases where the value passed in is not equal to the
283   // size of the type being created.
284   SVal TheValueExpr = state->getSVal(CE->getArg(2), LCtx);
285 
286   // FIXME: Eventually we should handle arbitrary locations.  We can do this
287   //  by having an enhanced memory model that does low-level typing.
288   loc::MemRegionVal* LV = dyn_cast<loc::MemRegionVal>(&TheValueExpr);
289   if (!LV)
290     return;
291 
292   const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts());
293   if (!R)
294     return;
295 
296   QualType T = Ctx.getCanonicalType(R->getValueType());
297 
298   // FIXME: If the pointee isn't an integer type, should we flag a warning?
299   //  People can do weird stuff with pointers.
300 
301   if (!T->isIntegerType())
302     return;
303 
304   uint64_t SourceSize = Ctx.getTypeSize(T);
305 
306   // CHECK: is SourceSize == TargetSize
307   if (SourceSize == TargetSize)
308     return;
309 
310   // Generate an error.  Only generate a sink if 'SourceSize < TargetSize';
311   // otherwise generate a regular node.
312   //
313   // FIXME: We can actually create an abstract "CFNumber" object that has
314   //  the bits initialized to the provided values.
315   //
316   if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink()
317                                                 : C.addTransition()) {
318     llvm::SmallString<128> sbuf;
319     llvm::raw_svector_ostream os(sbuf);
320 
321     os << (SourceSize == 8 ? "An " : "A ")
322        << SourceSize << " bit integer is used to initialize a CFNumber "
323                         "object that represents "
324        << (TargetSize == 8 ? "an " : "a ")
325        << TargetSize << " bit integer. ";
326 
327     if (SourceSize < TargetSize)
328       os << (TargetSize - SourceSize)
329       << " bits of the CFNumber value will be garbage." ;
330     else
331       os << (SourceSize - TargetSize)
332       << " bits of the input integer will be lost.";
333 
334     if (!BT)
335       BT.reset(new APIMisuse("Bad use of CFNumberCreate"));
336 
337     BugReport *report = new BugReport(*BT, os.str(), N);
338     report->addRange(CE->getArg(2)->getSourceRange());
339     C.EmitReport(report);
340   }
341 }
342 
343 //===----------------------------------------------------------------------===//
344 // CFRetain/CFRelease checking for null arguments.
345 //===----------------------------------------------------------------------===//
346 
347 namespace {
348 class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > {
349   mutable llvm::OwningPtr<APIMisuse> BT;
350   mutable IdentifierInfo *Retain, *Release;
351 public:
352   CFRetainReleaseChecker(): Retain(0), Release(0) {}
353   void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
354 };
355 } // end anonymous namespace
356 
357 
358 void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE,
359                                           CheckerContext &C) const {
360   // If the CallExpr doesn't have exactly 1 argument just give up checking.
361   if (CE->getNumArgs() != 1)
362     return;
363 
364   ProgramStateRef state = C.getState();
365   const FunctionDecl *FD = C.getCalleeDecl(CE);
366   if (!FD)
367     return;
368 
369   if (!BT) {
370     ASTContext &Ctx = C.getASTContext();
371     Retain = &Ctx.Idents.get("CFRetain");
372     Release = &Ctx.Idents.get("CFRelease");
373     BT.reset(new APIMisuse("null passed to CFRetain/CFRelease"));
374   }
375 
376   // Check if we called CFRetain/CFRelease.
377   const IdentifierInfo *FuncII = FD->getIdentifier();
378   if (!(FuncII == Retain || FuncII == Release))
379     return;
380 
381   // FIXME: The rest of this just checks that the argument is non-null.
382   // It should probably be refactored and combined with AttrNonNullChecker.
383 
384   // Get the argument's value.
385   const Expr *Arg = CE->getArg(0);
386   SVal ArgVal = state->getSVal(Arg, C.getLocationContext());
387   DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal);
388   if (!DefArgVal)
389     return;
390 
391   // Get a NULL value.
392   SValBuilder &svalBuilder = C.getSValBuilder();
393   DefinedSVal zero = cast<DefinedSVal>(svalBuilder.makeZeroVal(Arg->getType()));
394 
395   // Make an expression asserting that they're equal.
396   DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal);
397 
398   // Are they equal?
399   ProgramStateRef stateTrue, stateFalse;
400   llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull);
401 
402   if (stateTrue && !stateFalse) {
403     ExplodedNode *N = C.generateSink(stateTrue);
404     if (!N)
405       return;
406 
407     const char *description = (FuncII == Retain)
408                             ? "Null pointer argument in call to CFRetain"
409                             : "Null pointer argument in call to CFRelease";
410 
411     BugReport *report = new BugReport(*BT, description, N);
412     report->addRange(Arg->getSourceRange());
413     report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Arg));
414     C.EmitReport(report);
415     return;
416   }
417 
418   // From here on, we know the argument is non-null.
419   C.addTransition(stateFalse);
420 }
421 
422 //===----------------------------------------------------------------------===//
423 // Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
424 //===----------------------------------------------------------------------===//
425 
426 namespace {
427 class ClassReleaseChecker : public Checker<check::PreObjCMessage> {
428   mutable Selector releaseS;
429   mutable Selector retainS;
430   mutable Selector autoreleaseS;
431   mutable Selector drainS;
432   mutable llvm::OwningPtr<BugType> BT;
433 
434 public:
435   void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
436 };
437 }
438 
439 void ClassReleaseChecker::checkPreObjCMessage(ObjCMessage msg,
440                                               CheckerContext &C) const {
441 
442   if (!BT) {
443     BT.reset(new APIMisuse("message incorrectly sent to class instead of class "
444                            "instance"));
445 
446     ASTContext &Ctx = C.getASTContext();
447     releaseS = GetNullarySelector("release", Ctx);
448     retainS = GetNullarySelector("retain", Ctx);
449     autoreleaseS = GetNullarySelector("autorelease", Ctx);
450     drainS = GetNullarySelector("drain", Ctx);
451   }
452 
453   if (msg.isInstanceMessage())
454     return;
455   const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
456   assert(Class);
457 
458   Selector S = msg.getSelector();
459   if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
460     return;
461 
462   if (ExplodedNode *N = C.addTransition()) {
463     llvm::SmallString<200> buf;
464     llvm::raw_svector_ostream os(buf);
465 
466     os << "The '" << S.getAsString() << "' message should be sent to instances "
467           "of class '" << Class->getName()
468        << "' and not the class directly";
469 
470     BugReport *report = new BugReport(*BT, os.str(), N);
471     report->addRange(msg.getSourceRange());
472     C.EmitReport(report);
473   }
474 }
475 
476 //===----------------------------------------------------------------------===//
477 // Check for passing non-Objective-C types to variadic methods that expect
478 // only Objective-C types.
479 //===----------------------------------------------------------------------===//
480 
481 namespace {
482 class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> {
483   mutable Selector arrayWithObjectsS;
484   mutable Selector dictionaryWithObjectsAndKeysS;
485   mutable Selector setWithObjectsS;
486   mutable Selector initWithObjectsS;
487   mutable Selector initWithObjectsAndKeysS;
488   mutable llvm::OwningPtr<BugType> BT;
489 
490   bool isVariadicMessage(const ObjCMessage &msg) const;
491 
492 public:
493   void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
494 };
495 }
496 
497 /// isVariadicMessage - Returns whether the given message is a variadic message,
498 /// where all arguments must be Objective-C types.
499 bool
500 VariadicMethodTypeChecker::isVariadicMessage(const ObjCMessage &msg) const {
501   const ObjCMethodDecl *MD = msg.getMethodDecl();
502 
503   if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))
504     return false;
505 
506   Selector S = msg.getSelector();
507 
508   if (msg.isInstanceMessage()) {
509     // FIXME: Ideally we'd look at the receiver interface here, but that's not
510     // useful for init, because alloc returns 'id'. In theory, this could lead
511     // to false positives, for example if there existed a class that had an
512     // initWithObjects: implementation that does accept non-Objective-C pointer
513     // types, but the chance of that happening is pretty small compared to the
514     // gains that this analysis gives.
515     const ObjCInterfaceDecl *Class = MD->getClassInterface();
516 
517     // -[NSArray initWithObjects:]
518     if (isReceiverClassOrSuperclass(Class, "NSArray") &&
519         S == initWithObjectsS)
520       return true;
521 
522     // -[NSDictionary initWithObjectsAndKeys:]
523     if (isReceiverClassOrSuperclass(Class, "NSDictionary") &&
524         S == initWithObjectsAndKeysS)
525       return true;
526 
527     // -[NSSet initWithObjects:]
528     if (isReceiverClassOrSuperclass(Class, "NSSet") &&
529         S == initWithObjectsS)
530       return true;
531   } else {
532     const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
533 
534     // -[NSArray arrayWithObjects:]
535     if (isReceiverClassOrSuperclass(Class, "NSArray") &&
536         S == arrayWithObjectsS)
537       return true;
538 
539     // -[NSDictionary dictionaryWithObjectsAndKeys:]
540     if (isReceiverClassOrSuperclass(Class, "NSDictionary") &&
541         S == dictionaryWithObjectsAndKeysS)
542       return true;
543 
544     // -[NSSet setWithObjects:]
545     if (isReceiverClassOrSuperclass(Class, "NSSet") &&
546         S == setWithObjectsS)
547       return true;
548   }
549 
550   return false;
551 }
552 
553 void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg,
554                                                     CheckerContext &C) const {
555   if (!BT) {
556     BT.reset(new APIMisuse("Arguments passed to variadic method aren't all "
557                            "Objective-C pointer types"));
558 
559     ASTContext &Ctx = C.getASTContext();
560     arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);
561     dictionaryWithObjectsAndKeysS =
562       GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);
563     setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);
564 
565     initWithObjectsS = GetUnarySelector("initWithObjects", Ctx);
566     initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx);
567   }
568 
569   if (!isVariadicMessage(msg))
570       return;
571 
572   // We are not interested in the selector arguments since they have
573   // well-defined types, so the compiler will issue a warning for them.
574   unsigned variadicArgsBegin = msg.getSelector().getNumArgs();
575 
576   // We're not interested in the last argument since it has to be nil or the
577   // compiler would have issued a warning for it elsewhere.
578   unsigned variadicArgsEnd = msg.getNumArgs() - 1;
579 
580   if (variadicArgsEnd <= variadicArgsBegin)
581     return;
582 
583   // Verify that all arguments have Objective-C types.
584   llvm::Optional<ExplodedNode*> errorNode;
585   ProgramStateRef state = C.getState();
586 
587   for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {
588     QualType ArgTy = msg.getArgType(I);
589     if (ArgTy->isObjCObjectPointerType())
590       continue;
591 
592     // Block pointers are treaded as Objective-C pointers.
593     if (ArgTy->isBlockPointerType())
594       continue;
595 
596     // Ignore pointer constants.
597     if (isa<loc::ConcreteInt>(msg.getArgSVal(I, C.getLocationContext(),
598                                              state)))
599       continue;
600 
601     // Ignore pointer types annotated with 'NSObject' attribute.
602     if (C.getASTContext().isObjCNSObjectType(ArgTy))
603       continue;
604 
605     // Ignore CF references, which can be toll-free bridged.
606     if (coreFoundation::isCFObjectRef(ArgTy))
607       continue;
608 
609     // Generate only one error node to use for all bug reports.
610     if (!errorNode.hasValue()) {
611       errorNode = C.addTransition();
612     }
613 
614     if (!errorNode.getValue())
615       continue;
616 
617     llvm::SmallString<128> sbuf;
618     llvm::raw_svector_ostream os(sbuf);
619 
620     if (const char *TypeName = GetReceiverNameType(msg))
621       os << "Argument to '" << TypeName << "' method '";
622     else
623       os << "Argument to method '";
624 
625     os << msg.getSelector().getAsString()
626       << "' should be an Objective-C pointer type, not '"
627       << ArgTy.getAsString() << "'";
628 
629     BugReport *R = new BugReport(*BT, os.str(),
630                                              errorNode.getValue());
631     R->addRange(msg.getArgSourceRange(I));
632     C.EmitReport(R);
633   }
634 }
635 
636 //===----------------------------------------------------------------------===//
637 // Check registration.
638 //===----------------------------------------------------------------------===//
639 
640 void ento::registerNilArgChecker(CheckerManager &mgr) {
641   mgr.registerChecker<NilArgChecker>();
642 }
643 
644 void ento::registerCFNumberCreateChecker(CheckerManager &mgr) {
645   mgr.registerChecker<CFNumberCreateChecker>();
646 }
647 
648 void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
649   mgr.registerChecker<CFRetainReleaseChecker>();
650 }
651 
652 void ento::registerClassReleaseChecker(CheckerManager &mgr) {
653   mgr.registerChecker<ClassReleaseChecker>();
654 }
655 
656 void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
657   mgr.registerChecker<VariadicMethodTypeChecker>();
658 }
659