BasicObjCFoundationChecks.cpp revision 432424d67641d609e4990d791baa782fc161027e
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 "BasicObjCFoundationChecks.h" 17 18#include "clang/StaticAnalyzer/PathSensitive/ExplodedGraph.h" 19#include "clang/StaticAnalyzer/PathSensitive/CheckerVisitor.h" 20#include "clang/StaticAnalyzer/PathSensitive/ExprEngine.h" 21#include "clang/StaticAnalyzer/PathSensitive/GRState.h" 22#include "clang/StaticAnalyzer/BugReporter/BugType.h" 23#include "clang/StaticAnalyzer/PathSensitive/MemRegion.h" 24#include "clang/StaticAnalyzer/PathSensitive/CheckerVisitor.h" 25#include "clang/StaticAnalyzer/Checkers/LocalCheckers.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 ObjCInterfaceType* GetReceiverType(const ObjCMessage &msg) { 46 if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface()) 47 return ID->getTypeForDecl()->getAs<ObjCInterfaceType>(); 48 return NULL; 49} 50 51static const char* GetReceiverNameType(const ObjCMessage &msg) { 52 if (const ObjCInterfaceType *ReceiverType = GetReceiverType(msg)) 53 return ReceiverType->getDecl()->getIdentifier()->getNameStart(); 54 return NULL; 55} 56 57static bool isNSString(llvm::StringRef ClassName) { 58 return ClassName == "NSString" || ClassName == "NSMutableString"; 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 CheckerVisitor<NilArgChecker> { 71 APIMisuse *BT; 72 void WarnNilArg(CheckerContext &C, const ObjCMessage &msg, unsigned Arg); 73 public: 74 NilArgChecker() : BT(0) {} 75 static void *getTag() { static int x = 0; return &x; } 76 void preVisitObjCMessage(CheckerContext &C, ObjCMessage msg); 77 }; 78} 79 80void NilArgChecker::WarnNilArg(CheckerContext &C, 81 const ObjCMessage &msg, 82 unsigned int Arg) 83{ 84 if (!BT) 85 BT = 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::preVisitObjCMessage(CheckerContext &C, 100 ObjCMessage msg) 101{ 102 const ObjCInterfaceType *ReceiverType = GetReceiverType(msg); 103 if (!ReceiverType) 104 return; 105 106 if (isNSString(ReceiverType->getDecl()->getIdentifier()->getName())) { 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 CheckerVisitor<CFNumberCreateChecker> { 142 APIMisuse* BT; 143 IdentifierInfo* II; 144public: 145 CFNumberCreateChecker() : BT(0), II(0) {} 146 ~CFNumberCreateChecker() {} 147 static void *getTag() { static int x = 0; return &x; } 148 void PreVisitCallExpr(CheckerContext &C, const CallExpr *CE); 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::PreVisitCallExpr(CheckerContext &C, 249 const CallExpr *CE) 250{ 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 = 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 CheckerVisitor<CFRetainReleaseChecker> { 350 APIMisuse *BT; 351 IdentifierInfo *Retain, *Release; 352public: 353 CFRetainReleaseChecker(): BT(0), Retain(0), Release(0) {} 354 static void *getTag() { static int x = 0; return &x; } 355 void PreVisitCallExpr(CheckerContext& C, const CallExpr* CE); 356}; 357} // end anonymous namespace 358 359 360void CFRetainReleaseChecker::PreVisitCallExpr(CheckerContext& C, 361 const CallExpr* CE) { 362 // If the CallExpr doesn't have exactly 1 argument just give up checking. 363 if (CE->getNumArgs() != 1) 364 return; 365 366 // Get the function declaration of the callee. 367 const GRState* state = C.getState(); 368 SVal X = state->getSVal(CE->getCallee()); 369 const FunctionDecl* FD = X.getAsFunctionDecl(); 370 371 if (!FD) 372 return; 373 374 if (!BT) { 375 ASTContext &Ctx = C.getASTContext(); 376 Retain = &Ctx.Idents.get("CFRetain"); 377 Release = &Ctx.Idents.get("CFRelease"); 378 BT = new APIMisuse("null passed to CFRetain/CFRelease"); 379 } 380 381 // Check if we called CFRetain/CFRelease. 382 const IdentifierInfo *FuncII = FD->getIdentifier(); 383 if (!(FuncII == Retain || FuncII == Release)) 384 return; 385 386 // FIXME: The rest of this just checks that the argument is non-null. 387 // It should probably be refactored and combined with AttrNonNullChecker. 388 389 // Get the argument's value. 390 const Expr *Arg = CE->getArg(0); 391 SVal ArgVal = state->getSVal(Arg); 392 DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal); 393 if (!DefArgVal) 394 return; 395 396 // Get a NULL value. 397 SValBuilder &svalBuilder = C.getSValBuilder(); 398 DefinedSVal zero = cast<DefinedSVal>(svalBuilder.makeZeroVal(Arg->getType())); 399 400 // Make an expression asserting that they're equal. 401 DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal); 402 403 // Are they equal? 404 const GRState *stateTrue, *stateFalse; 405 llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); 406 407 if (stateTrue && !stateFalse) { 408 ExplodedNode *N = C.generateSink(stateTrue); 409 if (!N) 410 return; 411 412 const char *description = (FuncII == Retain) 413 ? "Null pointer argument in call to CFRetain" 414 : "Null pointer argument in call to CFRelease"; 415 416 EnhancedBugReport *report = new EnhancedBugReport(*BT, description, N); 417 report->addRange(Arg->getSourceRange()); 418 report->addVisitorCreator(bugreporter::registerTrackNullOrUndefValue, Arg); 419 C.EmitReport(report); 420 return; 421 } 422 423 // From here on, we know the argument is non-null. 424 C.addTransition(stateFalse); 425} 426 427//===----------------------------------------------------------------------===// 428// Check for sending 'retain', 'release', or 'autorelease' directly to a Class. 429//===----------------------------------------------------------------------===// 430 431namespace { 432class ClassReleaseChecker : public CheckerVisitor<ClassReleaseChecker> { 433 Selector releaseS; 434 Selector retainS; 435 Selector autoreleaseS; 436 Selector drainS; 437 BugType *BT; 438public: 439 ClassReleaseChecker() 440 : BT(0) {} 441 442 static void *getTag() { static int x = 0; return &x; } 443 444 void preVisitObjCMessage(CheckerContext &C, ObjCMessage msg); 445}; 446} 447 448void ClassReleaseChecker::preVisitObjCMessage(CheckerContext &C, 449 ObjCMessage msg) { 450 451 if (!BT) { 452 BT = new APIMisuse("message incorrectly sent to class instead of class " 453 "instance"); 454 455 ASTContext &Ctx = C.getASTContext(); 456 releaseS = GetNullarySelector("release", Ctx); 457 retainS = GetNullarySelector("retain", Ctx); 458 autoreleaseS = GetNullarySelector("autorelease", Ctx); 459 drainS = GetNullarySelector("drain", Ctx); 460 } 461 462 if (msg.isInstanceMessage()) 463 return; 464 const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 465 assert(Class); 466 467 Selector S = msg.getSelector(); 468 if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS)) 469 return; 470 471 if (ExplodedNode *N = C.generateNode()) { 472 llvm::SmallString<200> buf; 473 llvm::raw_svector_ostream os(buf); 474 475 os << "The '" << S.getAsString() << "' message should be sent to instances " 476 "of class '" << Class->getName() 477 << "' and not the class directly"; 478 479 RangedBugReport *report = new RangedBugReport(*BT, os.str(), N); 480 report->addRange(msg.getSourceRange()); 481 C.EmitReport(report); 482 } 483} 484 485//===----------------------------------------------------------------------===// 486// Check registration. 487//===----------------------------------------------------------------------===// 488 489void ento::RegisterAppleChecks(ExprEngine& Eng, const Decl &D) { 490 Eng.registerCheck(new NilArgChecker()); 491 Eng.registerCheck(new CFNumberCreateChecker()); 492 RegisterNSErrorChecks(Eng.getBugReporter(), Eng, D); 493 RegisterNSAutoreleasePoolChecks(Eng); 494 Eng.registerCheck(new CFRetainReleaseChecker()); 495 Eng.registerCheck(new ClassReleaseChecker()); 496} 497