BasicObjCFoundationChecks.cpp revision f7ccbad5d9949e7ddd1cbef43d482553b811e026
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    C.EmitReport(report);
416    return;
417  }
418
419  // From here on, we know the argument is non-null.
420  C.addTransition(stateFalse);
421}
422
423//===----------------------------------------------------------------------===//
424// Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
425//===----------------------------------------------------------------------===//
426
427namespace {
428class ClassReleaseChecker : public Checker<check::PreObjCMessage> {
429  mutable Selector releaseS;
430  mutable Selector retainS;
431  mutable Selector autoreleaseS;
432  mutable Selector drainS;
433  mutable OwningPtr<BugType> BT;
434
435public:
436  void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
437};
438}
439
440void ClassReleaseChecker::checkPreObjCMessage(ObjCMessage msg,
441                                              CheckerContext &C) const {
442
443  if (!BT) {
444    BT.reset(new APIMisuse("message incorrectly sent to class instead of class "
445                           "instance"));
446
447    ASTContext &Ctx = C.getASTContext();
448    releaseS = GetNullarySelector("release", Ctx);
449    retainS = GetNullarySelector("retain", Ctx);
450    autoreleaseS = GetNullarySelector("autorelease", Ctx);
451    drainS = GetNullarySelector("drain", Ctx);
452  }
453
454  if (msg.isInstanceMessage())
455    return;
456  const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
457  assert(Class);
458
459  Selector S = msg.getSelector();
460  if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
461    return;
462
463  if (ExplodedNode *N = C.addTransition()) {
464    SmallString<200> buf;
465    llvm::raw_svector_ostream os(buf);
466
467    os << "The '" << S.getAsString() << "' message should be sent to instances "
468          "of class '" << Class->getName()
469       << "' and not the class directly";
470
471    BugReport *report = new BugReport(*BT, os.str(), N);
472    report->addRange(msg.getSourceRange());
473    C.EmitReport(report);
474  }
475}
476
477//===----------------------------------------------------------------------===//
478// Check for passing non-Objective-C types to variadic methods that expect
479// only Objective-C types.
480//===----------------------------------------------------------------------===//
481
482namespace {
483class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> {
484  mutable Selector arrayWithObjectsS;
485  mutable Selector dictionaryWithObjectsAndKeysS;
486  mutable Selector setWithObjectsS;
487  mutable Selector initWithObjectsS;
488  mutable Selector initWithObjectsAndKeysS;
489  mutable OwningPtr<BugType> BT;
490
491  bool isVariadicMessage(const ObjCMessage &msg) const;
492
493public:
494  void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
495};
496}
497
498/// isVariadicMessage - Returns whether the given message is a variadic message,
499/// where all arguments must be Objective-C types.
500bool
501VariadicMethodTypeChecker::isVariadicMessage(const ObjCMessage &msg) const {
502  const ObjCMethodDecl *MD = msg.getMethodDecl();
503
504  if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))
505    return false;
506
507  Selector S = msg.getSelector();
508
509  if (msg.isInstanceMessage()) {
510    // FIXME: Ideally we'd look at the receiver interface here, but that's not
511    // useful for init, because alloc returns 'id'. In theory, this could lead
512    // to false positives, for example if there existed a class that had an
513    // initWithObjects: implementation that does accept non-Objective-C pointer
514    // types, but the chance of that happening is pretty small compared to the
515    // gains that this analysis gives.
516    const ObjCInterfaceDecl *Class = MD->getClassInterface();
517
518    // -[NSArray initWithObjects:]
519    if (isReceiverClassOrSuperclass(Class, "NSArray") &&
520        S == initWithObjectsS)
521      return true;
522
523    // -[NSDictionary initWithObjectsAndKeys:]
524    if (isReceiverClassOrSuperclass(Class, "NSDictionary") &&
525        S == initWithObjectsAndKeysS)
526      return true;
527
528    // -[NSSet initWithObjects:]
529    if (isReceiverClassOrSuperclass(Class, "NSSet") &&
530        S == initWithObjectsS)
531      return true;
532  } else {
533    const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
534
535    // -[NSArray arrayWithObjects:]
536    if (isReceiverClassOrSuperclass(Class, "NSArray") &&
537        S == arrayWithObjectsS)
538      return true;
539
540    // -[NSDictionary dictionaryWithObjectsAndKeys:]
541    if (isReceiverClassOrSuperclass(Class, "NSDictionary") &&
542        S == dictionaryWithObjectsAndKeysS)
543      return true;
544
545    // -[NSSet setWithObjects:]
546    if (isReceiverClassOrSuperclass(Class, "NSSet") &&
547        S == setWithObjectsS)
548      return true;
549  }
550
551  return false;
552}
553
554void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg,
555                                                    CheckerContext &C) const {
556  if (!BT) {
557    BT.reset(new APIMisuse("Arguments passed to variadic method aren't all "
558                           "Objective-C pointer types"));
559
560    ASTContext &Ctx = C.getASTContext();
561    arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);
562    dictionaryWithObjectsAndKeysS =
563      GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);
564    setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);
565
566    initWithObjectsS = GetUnarySelector("initWithObjects", Ctx);
567    initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx);
568  }
569
570  if (!isVariadicMessage(msg))
571      return;
572
573  // We are not interested in the selector arguments since they have
574  // well-defined types, so the compiler will issue a warning for them.
575  unsigned variadicArgsBegin = msg.getSelector().getNumArgs();
576
577  // We're not interested in the last argument since it has to be nil or the
578  // compiler would have issued a warning for it elsewhere.
579  unsigned variadicArgsEnd = msg.getNumArgs() - 1;
580
581  if (variadicArgsEnd <= variadicArgsBegin)
582    return;
583
584  // Verify that all arguments have Objective-C types.
585  llvm::Optional<ExplodedNode*> errorNode;
586  ProgramStateRef state = C.getState();
587
588  for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {
589    QualType ArgTy = msg.getArgType(I);
590    if (ArgTy->isObjCObjectPointerType())
591      continue;
592
593    // Block pointers are treaded as Objective-C pointers.
594    if (ArgTy->isBlockPointerType())
595      continue;
596
597    // Ignore pointer constants.
598    if (isa<loc::ConcreteInt>(msg.getArgSVal(I, C.getLocationContext(),
599                                             state)))
600      continue;
601
602    // Ignore pointer types annotated with 'NSObject' attribute.
603    if (C.getASTContext().isObjCNSObjectType(ArgTy))
604      continue;
605
606    // Ignore CF references, which can be toll-free bridged.
607    if (coreFoundation::isCFObjectRef(ArgTy))
608      continue;
609
610    // Generate only one error node to use for all bug reports.
611    if (!errorNode.hasValue()) {
612      errorNode = C.addTransition();
613    }
614
615    if (!errorNode.getValue())
616      continue;
617
618    SmallString<128> sbuf;
619    llvm::raw_svector_ostream os(sbuf);
620
621    if (const char *TypeName = GetReceiverNameType(msg))
622      os << "Argument to '" << TypeName << "' method '";
623    else
624      os << "Argument to method '";
625
626    os << msg.getSelector().getAsString()
627      << "' should be an Objective-C pointer type, not '"
628      << ArgTy.getAsString() << "'";
629
630    BugReport *R = new BugReport(*BT, os.str(),
631                                             errorNode.getValue());
632    R->addRange(msg.getArgSourceRange(I));
633    C.EmitReport(R);
634  }
635}
636
637//===----------------------------------------------------------------------===//
638// Check registration.
639//===----------------------------------------------------------------------===//
640
641void ento::registerNilArgChecker(CheckerManager &mgr) {
642  mgr.registerChecker<NilArgChecker>();
643}
644
645void ento::registerCFNumberCreateChecker(CheckerManager &mgr) {
646  mgr.registerChecker<CFNumberCreateChecker>();
647}
648
649void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
650  mgr.registerChecker<CFRetainReleaseChecker>();
651}
652
653void ento::registerClassReleaseChecker(CheckerManager &mgr) {
654  mgr.registerChecker<ClassReleaseChecker>();
655}
656
657void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
658  mgr.registerChecker<VariadicMethodTypeChecker>();
659}
660