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