blob: d6c09a2e04a62d9115566458a8f76793b208dd76 [file] [log] [blame]
Shih-wei Liaof8fd82b2010-02-10 11:10:31 -08001//== 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 "BasicObjCFoundationChecks.h"
17
18#include "clang/Checker/PathSensitive/ExplodedGraph.h"
19#include "clang/Checker/PathSensitive/GRSimpleAPICheck.h"
20#include "clang/Checker/PathSensitive/GRExprEngine.h"
21#include "clang/Checker/PathSensitive/GRState.h"
22#include "clang/Checker/BugReporter/BugReporter.h"
23#include "clang/Checker/PathSensitive/MemRegion.h"
24#include "clang/Checker/BugReporter/PathDiagnostic.h"
25#include "clang/Checker/PathSensitive/CheckerVisitor.h"
26#include "clang/Checker/Checkers/LocalCheckers.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;
33
34static const ObjCInterfaceType* GetReceiverType(const ObjCMessageExpr* ME) {
35 const Expr* Receiver = ME->getReceiver();
36
37 if (!Receiver)
38 return NULL;
39
40 if (const ObjCObjectPointerType *PT =
41 Receiver->getType()->getAs<ObjCObjectPointerType>())
42 return PT->getInterfaceType();
43
44 return NULL;
45}
46
47static const char* GetReceiverNameType(const ObjCMessageExpr* ME) {
48 if (const ObjCInterfaceType *ReceiverType = GetReceiverType(ME))
49 return ReceiverType->getDecl()->getIdentifier()->getNameStart();
50 return NULL;
51}
52
53namespace {
54
55class APIMisuse : public BugType {
56public:
57 APIMisuse(const char* name) : BugType(name, "API Misuse (Apple)") {}
58};
59
60class BasicObjCFoundationChecks : public GRSimpleAPICheck {
61 APIMisuse *BT;
62 BugReporter& BR;
63 ASTContext &Ctx;
64
65 bool isNSString(const ObjCInterfaceType *T, llvm::StringRef suffix);
66 bool AuditNSString(ExplodedNode* N, const ObjCMessageExpr* ME);
67
68 void Warn(ExplodedNode* N, const Expr* E, const std::string& s);
69 void WarnNilArg(ExplodedNode* N, const Expr* E);
70
71 bool CheckNilArg(ExplodedNode* N, unsigned Arg);
72
73public:
74 BasicObjCFoundationChecks(ASTContext& ctx, BugReporter& br)
75 : BT(0), BR(br), Ctx(ctx) {}
76
77 bool Audit(ExplodedNode* N, GRStateManager&);
78
79private:
80 void WarnNilArg(ExplodedNode* N, const ObjCMessageExpr* ME, unsigned Arg) {
81 std::string sbuf;
82 llvm::raw_string_ostream os(sbuf);
83 os << "Argument to '" << GetReceiverNameType(ME) << "' method '"
84 << ME->getSelector().getAsString() << "' cannot be nil.";
85
86 // Lazily create the BugType object for NilArg. This will be owned
87 // by the BugReporter object 'BR' once we call BR.EmitWarning.
88 if (!BT) BT = new APIMisuse("nil argument");
89
90 RangedBugReport *R = new RangedBugReport(*BT, os.str(), N);
91 R->addRange(ME->getArg(Arg)->getSourceRange());
92 BR.EmitReport(R);
93 }
94};
95
96} // end anonymous namespace
97
98
99GRSimpleAPICheck*
100clang::CreateBasicObjCFoundationChecks(ASTContext& Ctx, BugReporter& BR) {
101 return new BasicObjCFoundationChecks(Ctx, BR);
102}
103
104
105
106bool BasicObjCFoundationChecks::Audit(ExplodedNode* N,
107 GRStateManager&) {
108
109 const ObjCMessageExpr* ME =
110 cast<ObjCMessageExpr>(cast<PostStmt>(N->getLocation()).getStmt());
111
112 const ObjCInterfaceType *ReceiverType = GetReceiverType(ME);
113
114 if (!ReceiverType)
115 return false;
116
117 if (isNSString(ReceiverType,
118 ReceiverType->getDecl()->getIdentifier()->getName()))
119 return AuditNSString(N, ME);
120
121 return false;
122}
123
124static inline bool isNil(SVal X) {
125 return isa<loc::ConcreteInt>(X);
126}
127
128//===----------------------------------------------------------------------===//
129// Error reporting.
130//===----------------------------------------------------------------------===//
131
132bool BasicObjCFoundationChecks::CheckNilArg(ExplodedNode* N, unsigned Arg) {
133 const ObjCMessageExpr* ME =
134 cast<ObjCMessageExpr>(cast<PostStmt>(N->getLocation()).getStmt());
135
136 const Expr * E = ME->getArg(Arg);
137
138 if (isNil(N->getState()->getSVal(E))) {
139 WarnNilArg(N, ME, Arg);
140 return true;
141 }
142
143 return false;
144}
145
146//===----------------------------------------------------------------------===//
147// NSString checking.
148//===----------------------------------------------------------------------===//
149
150bool BasicObjCFoundationChecks::isNSString(const ObjCInterfaceType *T,
151 llvm::StringRef ClassName) {
152 return ClassName == "NSString" || ClassName == "NSMutableString";
153}
154
155bool BasicObjCFoundationChecks::AuditNSString(ExplodedNode* N,
156 const ObjCMessageExpr* ME) {
157
158 Selector S = ME->getSelector();
159
160 if (S.isUnarySelector())
161 return false;
162
163 // FIXME: This is going to be really slow doing these checks with
164 // lexical comparisons.
165
166 std::string NameStr = S.getAsString();
167 llvm::StringRef Name(NameStr);
168 assert(!Name.empty());
169
170 // FIXME: Checking for initWithFormat: will not work in most cases
171 // yet because [NSString alloc] returns id, not NSString*. We will
172 // need support for tracking expected-type information in the analyzer
173 // to find these errors.
174 if (Name == "caseInsensitiveCompare:" ||
175 Name == "compare:" ||
176 Name == "compare:options:" ||
177 Name == "compare:options:range:" ||
178 Name == "compare:options:range:locale:" ||
179 Name == "componentsSeparatedByCharactersInSet:" ||
180 Name == "initWithFormat:")
181 return CheckNilArg(N, 0);
182
183 return false;
184}
185
186//===----------------------------------------------------------------------===//
187// Error reporting.
188//===----------------------------------------------------------------------===//
189
190namespace {
191
192class AuditCFNumberCreate : public GRSimpleAPICheck {
193 APIMisuse* BT;
194
195 // FIXME: Either this should be refactored into GRSimpleAPICheck, or
196 // it should always be passed with a call to Audit. The latter
197 // approach makes this class more stateless.
198 ASTContext& Ctx;
199 IdentifierInfo* II;
200 BugReporter& BR;
201
202public:
203 AuditCFNumberCreate(ASTContext& ctx, BugReporter& br)
204 : BT(0), Ctx(ctx), II(&Ctx.Idents.get("CFNumberCreate")), BR(br){}
205
206 ~AuditCFNumberCreate() {}
207
208 bool Audit(ExplodedNode* N, GRStateManager&);
209
210private:
211 void AddError(const TypedRegion* R, const Expr* Ex, ExplodedNode *N,
212 uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind);
213};
214} // end anonymous namespace
215
216enum CFNumberType {
217 kCFNumberSInt8Type = 1,
218 kCFNumberSInt16Type = 2,
219 kCFNumberSInt32Type = 3,
220 kCFNumberSInt64Type = 4,
221 kCFNumberFloat32Type = 5,
222 kCFNumberFloat64Type = 6,
223 kCFNumberCharType = 7,
224 kCFNumberShortType = 8,
225 kCFNumberIntType = 9,
226 kCFNumberLongType = 10,
227 kCFNumberLongLongType = 11,
228 kCFNumberFloatType = 12,
229 kCFNumberDoubleType = 13,
230 kCFNumberCFIndexType = 14,
231 kCFNumberNSIntegerType = 15,
232 kCFNumberCGFloatType = 16
233};
234
235namespace {
236 template<typename T>
237 class Optional {
238 bool IsKnown;
239 T Val;
240 public:
241 Optional() : IsKnown(false), Val(0) {}
242 Optional(const T& val) : IsKnown(true), Val(val) {}
243
244 bool isKnown() const { return IsKnown; }
245
246 const T& getValue() const {
247 assert (isKnown());
248 return Val;
249 }
250
251 operator const T&() const {
252 return getValue();
253 }
254 };
255}
256
257static Optional<uint64_t> GetCFNumberSize(ASTContext& Ctx, uint64_t i) {
258 static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 };
259
260 if (i < kCFNumberCharType)
261 return FixedSize[i-1];
262
263 QualType T;
264
265 switch (i) {
266 case kCFNumberCharType: T = Ctx.CharTy; break;
267 case kCFNumberShortType: T = Ctx.ShortTy; break;
268 case kCFNumberIntType: T = Ctx.IntTy; break;
269 case kCFNumberLongType: T = Ctx.LongTy; break;
270 case kCFNumberLongLongType: T = Ctx.LongLongTy; break;
271 case kCFNumberFloatType: T = Ctx.FloatTy; break;
272 case kCFNumberDoubleType: T = Ctx.DoubleTy; break;
273 case kCFNumberCFIndexType:
274 case kCFNumberNSIntegerType:
275 case kCFNumberCGFloatType:
276 // FIXME: We need a way to map from names to Type*.
277 default:
278 return Optional<uint64_t>();
279 }
280
281 return Ctx.getTypeSize(T);
282}
283
284#if 0
285static const char* GetCFNumberTypeStr(uint64_t i) {
286 static const char* Names[] = {
287 "kCFNumberSInt8Type",
288 "kCFNumberSInt16Type",
289 "kCFNumberSInt32Type",
290 "kCFNumberSInt64Type",
291 "kCFNumberFloat32Type",
292 "kCFNumberFloat64Type",
293 "kCFNumberCharType",
294 "kCFNumberShortType",
295 "kCFNumberIntType",
296 "kCFNumberLongType",
297 "kCFNumberLongLongType",
298 "kCFNumberFloatType",
299 "kCFNumberDoubleType",
300 "kCFNumberCFIndexType",
301 "kCFNumberNSIntegerType",
302 "kCFNumberCGFloatType"
303 };
304
305 return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType";
306}
307#endif
308
309bool AuditCFNumberCreate::Audit(ExplodedNode* N,GRStateManager&){
310 const CallExpr* CE =
311 cast<CallExpr>(cast<PostStmt>(N->getLocation()).getStmt());
312 const Expr* Callee = CE->getCallee();
313 SVal CallV = N->getState()->getSVal(Callee);
314 const FunctionDecl* FD = CallV.getAsFunctionDecl();
315
316 if (!FD || FD->getIdentifier() != II || CE->getNumArgs()!=3)
317 return false;
318
319 // Get the value of the "theType" argument.
320 SVal TheTypeVal = N->getState()->getSVal(CE->getArg(1));
321
322 // FIXME: We really should allow ranges of valid theType values, and
323 // bifurcate the state appropriately.
324 nonloc::ConcreteInt* V = dyn_cast<nonloc::ConcreteInt>(&TheTypeVal);
325
326 if (!V)
327 return false;
328
329 uint64_t NumberKind = V->getValue().getLimitedValue();
330 Optional<uint64_t> TargetSize = GetCFNumberSize(Ctx, NumberKind);
331
332 // FIXME: In some cases we can emit an error.
333 if (!TargetSize.isKnown())
334 return false;
335
336 // Look at the value of the integer being passed by reference. Essentially
337 // we want to catch cases where the value passed in is not equal to the
338 // size of the type being created.
339 SVal TheValueExpr = N->getState()->getSVal(CE->getArg(2));
340
341 // FIXME: Eventually we should handle arbitrary locations. We can do this
342 // by having an enhanced memory model that does low-level typing.
343 loc::MemRegionVal* LV = dyn_cast<loc::MemRegionVal>(&TheValueExpr);
344
345 if (!LV)
346 return false;
347
348 const TypedRegion* R = dyn_cast<TypedRegion>(LV->StripCasts());
349
350 if (!R)
351 return false;
352
353 QualType T = Ctx.getCanonicalType(R->getValueType(Ctx));
354
355 // FIXME: If the pointee isn't an integer type, should we flag a warning?
356 // People can do weird stuff with pointers.
357
358 if (!T->isIntegerType())
359 return false;
360
361 uint64_t SourceSize = Ctx.getTypeSize(T);
362
363 // CHECK: is SourceSize == TargetSize
364
365 if (SourceSize == TargetSize)
366 return false;
367
368 AddError(R, CE->getArg(2), N, SourceSize, TargetSize, NumberKind);
369
370 // FIXME: We can actually create an abstract "CFNumber" object that has
371 // the bits initialized to the provided values.
372 return SourceSize < TargetSize;
373}
374
375void AuditCFNumberCreate::AddError(const TypedRegion* R, const Expr* Ex,
376 ExplodedNode *N,
377 uint64_t SourceSize, uint64_t TargetSize,
378 uint64_t NumberKind) {
379
380 std::string sbuf;
381 llvm::raw_string_ostream os(sbuf);
382
383 os << (SourceSize == 8 ? "An " : "A ")
384 << SourceSize << " bit integer is used to initialize a CFNumber "
385 "object that represents "
386 << (TargetSize == 8 ? "an " : "a ")
387 << TargetSize << " bit integer. ";
388
389 if (SourceSize < TargetSize)
390 os << (TargetSize - SourceSize)
391 << " bits of the CFNumber value will be garbage." ;
392 else
393 os << (SourceSize - TargetSize)
394 << " bits of the input integer will be lost.";
395
396 // Lazily create the BugType object. This will be owned
397 // by the BugReporter object 'BR' once we call BR.EmitWarning.
398 if (!BT) BT = new APIMisuse("Bad use of CFNumberCreate");
399 RangedBugReport *report = new RangedBugReport(*BT, os.str(), N);
400 report->addRange(Ex->getSourceRange());
401 BR.EmitReport(report);
402}
403
404GRSimpleAPICheck*
405clang::CreateAuditCFNumberCreate(ASTContext& Ctx, BugReporter& BR) {
406 return new AuditCFNumberCreate(Ctx, BR);
407}
408
409//===----------------------------------------------------------------------===//
410// CFRetain/CFRelease auditing for null arguments.
411//===----------------------------------------------------------------------===//
412
413namespace {
414class AuditCFRetainRelease : public GRSimpleAPICheck {
415 APIMisuse *BT;
416
417 // FIXME: Either this should be refactored into GRSimpleAPICheck, or
418 // it should always be passed with a call to Audit. The latter
419 // approach makes this class more stateless.
420 ASTContext& Ctx;
421 IdentifierInfo *Retain, *Release;
422 BugReporter& BR;
423
424public:
425 AuditCFRetainRelease(ASTContext& ctx, BugReporter& br)
426 : BT(0), Ctx(ctx),
427 Retain(&Ctx.Idents.get("CFRetain")), Release(&Ctx.Idents.get("CFRelease")),
428 BR(br){}
429
430 ~AuditCFRetainRelease() {}
431
432 bool Audit(ExplodedNode* N, GRStateManager&);
433};
434} // end anonymous namespace
435
436
437bool AuditCFRetainRelease::Audit(ExplodedNode* N, GRStateManager&) {
438 const CallExpr* CE = cast<CallExpr>(cast<PostStmt>(N->getLocation()).getStmt());
439
440 // If the CallExpr doesn't have exactly 1 argument just give up checking.
441 if (CE->getNumArgs() != 1)
442 return false;
443
444 // Check if we called CFRetain/CFRelease.
445 const GRState* state = N->getState();
446 SVal X = state->getSVal(CE->getCallee());
447 const FunctionDecl* FD = X.getAsFunctionDecl();
448
449 if (!FD)
450 return false;
451
452 const IdentifierInfo *FuncII = FD->getIdentifier();
453 if (!(FuncII == Retain || FuncII == Release))
454 return false;
455
456 // Finally, check if the argument is NULL.
457 // FIXME: We should be able to bifurcate the state here, as a successful
458 // check will result in the value not being NULL afterwards.
459 // FIXME: Need a way to register vistors for the BugReporter. Would like
460 // to benefit from the same diagnostics that regular null dereference
461 // reporting has.
462 if (state->getStateManager().isEqual(state, CE->getArg(0), 0)) {
463 if (!BT)
464 BT = new APIMisuse("null passed to CFRetain/CFRelease");
465
466 const char *description = (FuncII == Retain)
467 ? "Null pointer argument in call to CFRetain"
468 : "Null pointer argument in call to CFRelease";
469
470 RangedBugReport *report = new RangedBugReport(*BT, description, N);
471 report->addRange(CE->getArg(0)->getSourceRange());
472 BR.EmitReport(report);
473 return true;
474 }
475
476 return false;
477}
478
479
480GRSimpleAPICheck*
481clang::CreateAuditCFRetainRelease(ASTContext& Ctx, BugReporter& BR) {
482 return new AuditCFRetainRelease(Ctx, BR);
483}
484
485//===----------------------------------------------------------------------===//
486// Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
487//===----------------------------------------------------------------------===//
488
489namespace {
490class ClassReleaseChecker :
491 public CheckerVisitor<ClassReleaseChecker> {
492 Selector releaseS;
493 Selector retainS;
494 Selector autoreleaseS;
495 Selector drainS;
496 BugType *BT;
497public:
498 ClassReleaseChecker(ASTContext &Ctx)
499 : releaseS(GetNullarySelector("release", Ctx)),
500 retainS(GetNullarySelector("retain", Ctx)),
501 autoreleaseS(GetNullarySelector("autorelease", Ctx)),
502 drainS(GetNullarySelector("drain", Ctx)),
503 BT(0) {}
504
505 static void *getTag() { static int x = 0; return &x; }
506
507 void PreVisitObjCMessageExpr(CheckerContext &C, const ObjCMessageExpr *ME);
508};
509}
510
511void ClassReleaseChecker::PreVisitObjCMessageExpr(CheckerContext &C,
512 const ObjCMessageExpr *ME) {
513
514 const IdentifierInfo *ClsName = ME->getClassName();
515 if (!ClsName)
516 return;
517
518 Selector S = ME->getSelector();
519 if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
520 return;
521
522 if (!BT)
523 BT = new APIMisuse("message incorrectly sent to class instead of class "
524 "instance");
525
526 ExplodedNode *N = C.GenerateNode();
527
528 if (!N)
529 return;
530
531 llvm::SmallString<200> buf;
532 llvm::raw_svector_ostream os(buf);
533
534 os << "The '" << S.getAsString() << "' message should be sent to instances "
535 "of class '" << ClsName->getName()
536 << "' and not the class directly";
537
538 RangedBugReport *report = new RangedBugReport(*BT, os.str(), N);
539 report->addRange(ME->getSourceRange());
540 C.EmitReport(report);
541}
542
543//===----------------------------------------------------------------------===//
544// Check registration.
545//===----------------------------------------------------------------------===//
546
547void clang::RegisterAppleChecks(GRExprEngine& Eng, const Decl &D) {
548 ASTContext& Ctx = Eng.getContext();
549 BugReporter &BR = Eng.getBugReporter();
550
551 Eng.AddCheck(CreateBasicObjCFoundationChecks(Ctx, BR),
552 Stmt::ObjCMessageExprClass);
553 Eng.AddCheck(CreateAuditCFNumberCreate(Ctx, BR), Stmt::CallExprClass);
554 Eng.AddCheck(CreateAuditCFRetainRelease(Ctx, BR), Stmt::CallExprClass);
555
556 RegisterNSErrorChecks(BR, Eng, D);
557 RegisterNSAutoreleasePoolChecks(Eng);
558 Eng.registerCheck(new ClassReleaseChecker(Ctx));
559}