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