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