BasicObjCFoundationChecks.cpp revision ec8605f1d7ec846dbf51047bfd5c56d32d1ff91c
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 ObjCInterfaceType* GetReceiverType(const ObjCMessage &msg) {
45  if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())
46    return ID->getTypeForDecl()->getAs<ObjCInterfaceType>();
47  return NULL;
48}
49
50static const char* GetReceiverNameType(const ObjCMessage &msg) {
51  if (const ObjCInterfaceType *ReceiverType = GetReceiverType(msg))
52    return ReceiverType->getDecl()->getIdentifier()->getNameStart();
53  return NULL;
54}
55
56static bool isNSString(llvm::StringRef ClassName) {
57  return ClassName == "NSString" || ClassName == "NSMutableString";
58}
59
60static inline bool isNil(SVal X) {
61  return isa<loc::ConcreteInt>(X);
62}
63
64//===----------------------------------------------------------------------===//
65// NilArgChecker - Check for prohibited nil arguments to ObjC method calls.
66//===----------------------------------------------------------------------===//
67
68namespace {
69  class NilArgChecker : public Checker<check::PreObjCMessage> {
70    mutable llvm::OwningPtr<APIMisuse> BT;
71
72    void WarnNilArg(CheckerContext &C,
73                    const ObjCMessage &msg, unsigned Arg) const;
74
75  public:
76    void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
77  };
78}
79
80void NilArgChecker::WarnNilArg(CheckerContext &C,
81                               const ObjCMessage &msg,
82                               unsigned int Arg) const
83{
84  if (!BT)
85    BT.reset(new APIMisuse("nil argument"));
86
87  if (ExplodedNode *N = C.generateSink()) {
88    llvm::SmallString<128> sbuf;
89    llvm::raw_svector_ostream os(sbuf);
90    os << "Argument to '" << GetReceiverNameType(msg) << "' method '"
91       << msg.getSelector().getAsString() << "' cannot be nil";
92
93    RangedBugReport *R = new RangedBugReport(*BT, os.str(), N);
94    R->addRange(msg.getArgSourceRange(Arg));
95    C.EmitReport(R);
96  }
97}
98
99void NilArgChecker::checkPreObjCMessage(ObjCMessage msg,
100                                        CheckerContext &C) const {
101  const ObjCInterfaceType *ReceiverType = GetReceiverType(msg);
102  if (!ReceiverType)
103    return;
104
105  if (isNSString(ReceiverType->getDecl()->getIdentifier()->getName())) {
106    Selector S = msg.getSelector();
107
108    if (S.isUnarySelector())
109      return;
110
111    // FIXME: This is going to be really slow doing these checks with
112    //  lexical comparisons.
113
114    std::string NameStr = S.getAsString();
115    llvm::StringRef Name(NameStr);
116    assert(!Name.empty());
117
118    // FIXME: Checking for initWithFormat: will not work in most cases
119    //  yet because [NSString alloc] returns id, not NSString*.  We will
120    //  need support for tracking expected-type information in the analyzer
121    //  to find these errors.
122    if (Name == "caseInsensitiveCompare:" ||
123        Name == "compare:" ||
124        Name == "compare:options:" ||
125        Name == "compare:options:range:" ||
126        Name == "compare:options:range:locale:" ||
127        Name == "componentsSeparatedByCharactersInSet:" ||
128        Name == "initWithFormat:") {
129      if (isNil(msg.getArgSVal(0, C.getState())))
130        WarnNilArg(C, msg, 0);
131    }
132  }
133}
134
135//===----------------------------------------------------------------------===//
136// Error reporting.
137//===----------------------------------------------------------------------===//
138
139namespace {
140class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > {
141  mutable llvm::OwningPtr<APIMisuse> BT;
142  mutable IdentifierInfo* II;
143public:
144  CFNumberCreateChecker() : II(0) {}
145
146  void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
147
148private:
149  void EmitError(const TypedRegion* R, const Expr* Ex,
150                uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind);
151};
152} // end anonymous namespace
153
154enum CFNumberType {
155  kCFNumberSInt8Type = 1,
156  kCFNumberSInt16Type = 2,
157  kCFNumberSInt32Type = 3,
158  kCFNumberSInt64Type = 4,
159  kCFNumberFloat32Type = 5,
160  kCFNumberFloat64Type = 6,
161  kCFNumberCharType = 7,
162  kCFNumberShortType = 8,
163  kCFNumberIntType = 9,
164  kCFNumberLongType = 10,
165  kCFNumberLongLongType = 11,
166  kCFNumberFloatType = 12,
167  kCFNumberDoubleType = 13,
168  kCFNumberCFIndexType = 14,
169  kCFNumberNSIntegerType = 15,
170  kCFNumberCGFloatType = 16
171};
172
173namespace {
174  template<typename T>
175  class Optional {
176    bool IsKnown;
177    T Val;
178  public:
179    Optional() : IsKnown(false), Val(0) {}
180    Optional(const T& val) : IsKnown(true), Val(val) {}
181
182    bool isKnown() const { return IsKnown; }
183
184    const T& getValue() const {
185      assert (isKnown());
186      return Val;
187    }
188
189    operator const T&() const {
190      return getValue();
191    }
192  };
193}
194
195static Optional<uint64_t> GetCFNumberSize(ASTContext& Ctx, uint64_t i) {
196  static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 };
197
198  if (i < kCFNumberCharType)
199    return FixedSize[i-1];
200
201  QualType T;
202
203  switch (i) {
204    case kCFNumberCharType:     T = Ctx.CharTy;     break;
205    case kCFNumberShortType:    T = Ctx.ShortTy;    break;
206    case kCFNumberIntType:      T = Ctx.IntTy;      break;
207    case kCFNumberLongType:     T = Ctx.LongTy;     break;
208    case kCFNumberLongLongType: T = Ctx.LongLongTy; break;
209    case kCFNumberFloatType:    T = Ctx.FloatTy;    break;
210    case kCFNumberDoubleType:   T = Ctx.DoubleTy;   break;
211    case kCFNumberCFIndexType:
212    case kCFNumberNSIntegerType:
213    case kCFNumberCGFloatType:
214      // FIXME: We need a way to map from names to Type*.
215    default:
216      return Optional<uint64_t>();
217  }
218
219  return Ctx.getTypeSize(T);
220}
221
222#if 0
223static const char* GetCFNumberTypeStr(uint64_t i) {
224  static const char* Names[] = {
225    "kCFNumberSInt8Type",
226    "kCFNumberSInt16Type",
227    "kCFNumberSInt32Type",
228    "kCFNumberSInt64Type",
229    "kCFNumberFloat32Type",
230    "kCFNumberFloat64Type",
231    "kCFNumberCharType",
232    "kCFNumberShortType",
233    "kCFNumberIntType",
234    "kCFNumberLongType",
235    "kCFNumberLongLongType",
236    "kCFNumberFloatType",
237    "kCFNumberDoubleType",
238    "kCFNumberCFIndexType",
239    "kCFNumberNSIntegerType",
240    "kCFNumberCGFloatType"
241  };
242
243  return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType";
244}
245#endif
246
247void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE,
248                                         CheckerContext &C) const {
249  const Expr* Callee = CE->getCallee();
250  const GRState *state = C.getState();
251  SVal CallV = state->getSVal(Callee);
252  const FunctionDecl* FD = CallV.getAsFunctionDecl();
253
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  SVal TheTypeVal = state->getSVal(CE->getArg(1));
266
267  // FIXME: We really should allow ranges of valid theType values, and
268  //   bifurcate the state appropriately.
269  nonloc::ConcreteInt* V = dyn_cast<nonloc::ConcreteInt>(&TheTypeVal);
270  if (!V)
271    return;
272
273  uint64_t NumberKind = V->getValue().getLimitedValue();
274  Optional<uint64_t> TargetSize = GetCFNumberSize(Ctx, NumberKind);
275
276  // FIXME: In some cases we can emit an error.
277  if (!TargetSize.isKnown())
278    return;
279
280  // Look at the value of the integer being passed by reference.  Essentially
281  // we want to catch cases where the value passed in is not equal to the
282  // size of the type being created.
283  SVal TheValueExpr = state->getSVal(CE->getArg(2));
284
285  // FIXME: Eventually we should handle arbitrary locations.  We can do this
286  //  by having an enhanced memory model that does low-level typing.
287  loc::MemRegionVal* LV = dyn_cast<loc::MemRegionVal>(&TheValueExpr);
288  if (!LV)
289    return;
290
291  const TypedRegion* R = dyn_cast<TypedRegion>(LV->stripCasts());
292  if (!R)
293    return;
294
295  QualType T = Ctx.getCanonicalType(R->getValueType());
296
297  // FIXME: If the pointee isn't an integer type, should we flag a warning?
298  //  People can do weird stuff with pointers.
299
300  if (!T->isIntegerType())
301    return;
302
303  uint64_t SourceSize = Ctx.getTypeSize(T);
304
305  // CHECK: is SourceSize == TargetSize
306  if (SourceSize == TargetSize)
307    return;
308
309  // Generate an error.  Only generate a sink if 'SourceSize < TargetSize';
310  // otherwise generate a regular node.
311  //
312  // FIXME: We can actually create an abstract "CFNumber" object that has
313  //  the bits initialized to the provided values.
314  //
315  if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink()
316                                                : C.generateNode()) {
317    llvm::SmallString<128> sbuf;
318    llvm::raw_svector_ostream os(sbuf);
319
320    os << (SourceSize == 8 ? "An " : "A ")
321       << SourceSize << " bit integer is used to initialize a CFNumber "
322                        "object that represents "
323       << (TargetSize == 8 ? "an " : "a ")
324       << TargetSize << " bit integer. ";
325
326    if (SourceSize < TargetSize)
327      os << (TargetSize - SourceSize)
328      << " bits of the CFNumber value will be garbage." ;
329    else
330      os << (SourceSize - TargetSize)
331      << " bits of the input integer will be lost.";
332
333    if (!BT)
334      BT.reset(new APIMisuse("Bad use of CFNumberCreate"));
335
336    RangedBugReport *report = new RangedBugReport(*BT, os.str(), N);
337    report->addRange(CE->getArg(2)->getSourceRange());
338    C.EmitReport(report);
339  }
340}
341
342//===----------------------------------------------------------------------===//
343// CFRetain/CFRelease checking for null arguments.
344//===----------------------------------------------------------------------===//
345
346namespace {
347class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > {
348  mutable llvm::OwningPtr<APIMisuse> BT;
349  mutable IdentifierInfo *Retain, *Release;
350public:
351  CFRetainReleaseChecker(): Retain(0), Release(0) {}
352  void checkPreStmt(const CallExpr* CE, CheckerContext& C) const;
353};
354} // end anonymous namespace
355
356
357void CFRetainReleaseChecker::checkPreStmt(const CallExpr* CE,
358                                          CheckerContext& C) const {
359  // If the CallExpr doesn't have exactly 1 argument just give up checking.
360  if (CE->getNumArgs() != 1)
361    return;
362
363  // Get the function declaration of the callee.
364  const GRState* state = C.getState();
365  SVal X = state->getSVal(CE->getCallee());
366  const FunctionDecl* FD = X.getAsFunctionDecl();
367
368  if (!FD)
369    return;
370
371  if (!BT) {
372    ASTContext &Ctx = C.getASTContext();
373    Retain = &Ctx.Idents.get("CFRetain");
374    Release = &Ctx.Idents.get("CFRelease");
375    BT.reset(new APIMisuse("null passed to CFRetain/CFRelease"));
376  }
377
378  // Check if we called CFRetain/CFRelease.
379  const IdentifierInfo *FuncII = FD->getIdentifier();
380  if (!(FuncII == Retain || FuncII == Release))
381    return;
382
383  // FIXME: The rest of this just checks that the argument is non-null.
384  // It should probably be refactored and combined with AttrNonNullChecker.
385
386  // Get the argument's value.
387  const Expr *Arg = CE->getArg(0);
388  SVal ArgVal = state->getSVal(Arg);
389  DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal);
390  if (!DefArgVal)
391    return;
392
393  // Get a NULL value.
394  SValBuilder &svalBuilder = C.getSValBuilder();
395  DefinedSVal zero = cast<DefinedSVal>(svalBuilder.makeZeroVal(Arg->getType()));
396
397  // Make an expression asserting that they're equal.
398  DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal);
399
400  // Are they equal?
401  const GRState *stateTrue, *stateFalse;
402  llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull);
403
404  if (stateTrue && !stateFalse) {
405    ExplodedNode *N = C.generateSink(stateTrue);
406    if (!N)
407      return;
408
409    const char *description = (FuncII == Retain)
410                            ? "Null pointer argument in call to CFRetain"
411                            : "Null pointer argument in call to CFRelease";
412
413    EnhancedBugReport *report = new EnhancedBugReport(*BT, description, N);
414    report->addRange(Arg->getSourceRange());
415    report->addVisitorCreator(bugreporter::registerTrackNullOrUndefValue, Arg);
416    C.EmitReport(report);
417    return;
418  }
419
420  // From here on, we know the argument is non-null.
421  C.addTransition(stateFalse);
422}
423
424//===----------------------------------------------------------------------===//
425// Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
426//===----------------------------------------------------------------------===//
427
428namespace {
429class ClassReleaseChecker : public Checker<check::PreObjCMessage> {
430  mutable Selector releaseS;
431  mutable Selector retainS;
432  mutable Selector autoreleaseS;
433  mutable Selector drainS;
434  mutable llvm::OwningPtr<BugType> BT;
435
436public:
437  void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
438};
439}
440
441void ClassReleaseChecker::checkPreObjCMessage(ObjCMessage msg,
442                                              CheckerContext &C) const {
443
444  if (!BT) {
445    BT.reset(new APIMisuse("message incorrectly sent to class instead of class "
446                           "instance"));
447
448    ASTContext &Ctx = C.getASTContext();
449    releaseS = GetNullarySelector("release", Ctx);
450    retainS = GetNullarySelector("retain", Ctx);
451    autoreleaseS = GetNullarySelector("autorelease", Ctx);
452    drainS = GetNullarySelector("drain", Ctx);
453  }
454
455  if (msg.isInstanceMessage())
456    return;
457  const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
458  assert(Class);
459
460  Selector S = msg.getSelector();
461  if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
462    return;
463
464  if (ExplodedNode *N = C.generateNode()) {
465    llvm::SmallString<200> buf;
466    llvm::raw_svector_ostream os(buf);
467
468    os << "The '" << S.getAsString() << "' message should be sent to instances "
469          "of class '" << Class->getName()
470       << "' and not the class directly";
471
472    RangedBugReport *report = new RangedBugReport(*BT, os.str(), N);
473    report->addRange(msg.getSourceRange());
474    C.EmitReport(report);
475  }
476}
477
478//===----------------------------------------------------------------------===//
479// Check registration.
480//===----------------------------------------------------------------------===//
481
482void ento::registerNilArgChecker(CheckerManager &mgr) {
483  mgr.registerChecker<NilArgChecker>();
484}
485
486void ento::registerCFNumberCreateChecker(CheckerManager &mgr) {
487  mgr.registerChecker<CFNumberCreateChecker>();
488}
489
490void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
491  mgr.registerChecker<CFRetainReleaseChecker>();
492}
493
494void ento::registerClassReleaseChecker(CheckerManager &mgr) {
495  mgr.registerChecker<ClassReleaseChecker>();
496}
497