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