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