BasicObjCFoundationChecks.cpp revision 234353
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
33using namespace clang;
34using namespace ento;
35
36namespace {
37class APIMisuse : public BugType {
38public:
39  APIMisuse(const char* name) : BugType(name, "API Misuse (Apple)") {}
40};
41} // end anonymous namespace
42
43//===----------------------------------------------------------------------===//
44// Utility functions.
45//===----------------------------------------------------------------------===//
46
47static const char* GetReceiverNameType(const ObjCMessage &msg) {
48  if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())
49    return ID->getIdentifier()->getNameStart();
50  return 0;
51}
52
53static 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
64static 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
72namespace {
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
84void 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
103void 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
143namespace {
144class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > {
145  mutable OwningPtr<APIMisuse> BT;
146  mutable IdentifierInfo* II;
147public:
148  CFNumberCreateChecker() : II(0) {}
149
150  void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
151
152private:
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
158enum 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
177namespace {
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
199static 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
227static 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
251void 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
348namespace {
349class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > {
350  mutable OwningPtr<APIMisuse> BT;
351  mutable IdentifierInfo *Retain, *Release;
352public:
353  CFRetainReleaseChecker(): Retain(0), Release(0) {}
354  void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
355};
356} // end anonymous namespace
357
358
359void 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
428namespace {
429class 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
436public:
437  void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
438};
439}
440
441void 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
483namespace {
484class 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
495public:
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.
502bool
503VariadicMethodTypeChecker::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
566void 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
654void ento::registerNilArgChecker(CheckerManager &mgr) {
655  mgr.registerChecker<NilArgChecker>();
656}
657
658void ento::registerCFNumberCreateChecker(CheckerManager &mgr) {
659  mgr.registerChecker<CFNumberCreateChecker>();
660}
661
662void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
663  mgr.registerChecker<CFRetainReleaseChecker>();
664}
665
666void ento::registerClassReleaseChecker(CheckerManager &mgr) {
667  mgr.registerChecker<ClassReleaseChecker>();
668}
669
670void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
671  mgr.registerChecker<VariadicMethodTypeChecker>();
672}
673