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