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