BasicObjCFoundationChecks.cpp revision 8bef8238181a30e52dea380789a7e2d760eac532
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
32using namespace clang;
33using namespace ento;
34
35namespace {
36class APIMisuse : public BugType {
37public:
38  APIMisuse(const char* name) : BugType(name, "API Misuse (Apple)") {}
39};
40} // end anonymous namespace
41
42//===----------------------------------------------------------------------===//
43// Utility functions.
44//===----------------------------------------------------------------------===//
45
46static const char* GetReceiverNameType(const ObjCMessage &msg) {
47  if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())
48    return ID->getIdentifier()->getNameStart();
49  return 0;
50}
51
52static 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
63static 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
71namespace {
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
83void 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
102void 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
142namespace {
143class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > {
144  mutable llvm::OwningPtr<APIMisuse> BT;
145  mutable IdentifierInfo* II;
146public:
147  CFNumberCreateChecker() : II(0) {}
148
149  void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
150
151private:
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
157enum 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
176namespace {
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
198static 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
226static 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
250void 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
347namespace {
348class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > {
349  mutable llvm::OwningPtr<APIMisuse> BT;
350  mutable IdentifierInfo *Retain, *Release;
351public:
352  CFRetainReleaseChecker(): Retain(0), Release(0) {}
353  void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
354};
355} // end anonymous namespace
356
357
358void 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
426namespace {
427class 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
434public:
435  void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
436};
437}
438
439void 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
481namespace {
482class 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
492public:
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.
499bool
500VariadicMethodTypeChecker::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
553void 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
640void ento::registerNilArgChecker(CheckerManager &mgr) {
641  mgr.registerChecker<NilArgChecker>();
642}
643
644void ento::registerCFNumberCreateChecker(CheckerManager &mgr) {
645  mgr.registerChecker<CFNumberCreateChecker>();
646}
647
648void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
649  mgr.registerChecker<CFRetainReleaseChecker>();
650}
651
652void ento::registerClassReleaseChecker(CheckerManager &mgr) {
653  mgr.registerChecker<ClassReleaseChecker>();
654}
655
656void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
657  mgr.registerChecker<VariadicMethodTypeChecker>();
658}
659