BasicObjCFoundationChecks.cpp revision 983326f32c746f5e47161a73758e4d363263dd2c
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/CheckerV2.h" 20#include "clang/StaticAnalyzer/Core/CheckerManager.h" 21#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 22#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" 23#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" 24#include "clang/StaticAnalyzer/Core/PathSensitive/GRState.h" 25#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 26#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.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 CheckerV2<check::PreObjCMessage> { 73 mutable llvm::OwningPtr<APIMisuse> BT; 74 75 void WarnNilArg(CheckerContext &C, 76 const ObjCMessage &msg, unsigned Arg) const; 77 78 public: 79 void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const; 80 }; 81} 82 83void NilArgChecker::WarnNilArg(CheckerContext &C, 84 const ObjCMessage &msg, 85 unsigned int Arg) const 86{ 87 if (!BT) 88 BT.reset(new APIMisuse("nil argument")); 89 90 if (ExplodedNode *N = C.generateSink()) { 91 llvm::SmallString<128> sbuf; 92 llvm::raw_svector_ostream os(sbuf); 93 os << "Argument to '" << GetReceiverNameType(msg) << "' method '" 94 << msg.getSelector().getAsString() << "' cannot be nil"; 95 96 RangedBugReport *R = new RangedBugReport(*BT, os.str(), N); 97 R->addRange(msg.getArgSourceRange(Arg)); 98 C.EmitReport(R); 99 } 100} 101 102void NilArgChecker::checkPreObjCMessage(ObjCMessage msg, 103 CheckerContext &C) const { 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 CheckerV2< check::PreStmt<CallExpr> > { 144 mutable llvm::OwningPtr<APIMisuse> BT; 145 mutable IdentifierInfo* II; 146public: 147 CFNumberCreateChecker() : II(0) {} 148 149 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 150 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::checkPreStmt(const CallExpr *CE, 251 CheckerContext &C) const { 252 const Expr* Callee = CE->getCallee(); 253 const GRState *state = C.getState(); 254 SVal CallV = state->getSVal(Callee); 255 const FunctionDecl* FD = CallV.getAsFunctionDecl(); 256 257 if (!FD) 258 return; 259 260 ASTContext &Ctx = C.getASTContext(); 261 if (!II) 262 II = &Ctx.Idents.get("CFNumberCreate"); 263 264 if (FD->getIdentifier() != II || CE->getNumArgs() != 3) 265 return; 266 267 // Get the value of the "theType" argument. 268 SVal TheTypeVal = state->getSVal(CE->getArg(1)); 269 270 // FIXME: We really should allow ranges of valid theType values, and 271 // bifurcate the state appropriately. 272 nonloc::ConcreteInt* V = dyn_cast<nonloc::ConcreteInt>(&TheTypeVal); 273 if (!V) 274 return; 275 276 uint64_t NumberKind = V->getValue().getLimitedValue(); 277 Optional<uint64_t> TargetSize = GetCFNumberSize(Ctx, NumberKind); 278 279 // FIXME: In some cases we can emit an error. 280 if (!TargetSize.isKnown()) 281 return; 282 283 // Look at the value of the integer being passed by reference. Essentially 284 // we want to catch cases where the value passed in is not equal to the 285 // size of the type being created. 286 SVal TheValueExpr = state->getSVal(CE->getArg(2)); 287 288 // FIXME: Eventually we should handle arbitrary locations. We can do this 289 // by having an enhanced memory model that does low-level typing. 290 loc::MemRegionVal* LV = dyn_cast<loc::MemRegionVal>(&TheValueExpr); 291 if (!LV) 292 return; 293 294 const TypedRegion* R = dyn_cast<TypedRegion>(LV->stripCasts()); 295 if (!R) 296 return; 297 298 QualType T = Ctx.getCanonicalType(R->getValueType()); 299 300 // FIXME: If the pointee isn't an integer type, should we flag a warning? 301 // People can do weird stuff with pointers. 302 303 if (!T->isIntegerType()) 304 return; 305 306 uint64_t SourceSize = Ctx.getTypeSize(T); 307 308 // CHECK: is SourceSize == TargetSize 309 if (SourceSize == TargetSize) 310 return; 311 312 // Generate an error. Only generate a sink if 'SourceSize < TargetSize'; 313 // otherwise generate a regular node. 314 // 315 // FIXME: We can actually create an abstract "CFNumber" object that has 316 // the bits initialized to the provided values. 317 // 318 if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink() 319 : C.generateNode()) { 320 llvm::SmallString<128> sbuf; 321 llvm::raw_svector_ostream os(sbuf); 322 323 os << (SourceSize == 8 ? "An " : "A ") 324 << SourceSize << " bit integer is used to initialize a CFNumber " 325 "object that represents " 326 << (TargetSize == 8 ? "an " : "a ") 327 << TargetSize << " bit integer. "; 328 329 if (SourceSize < TargetSize) 330 os << (TargetSize - SourceSize) 331 << " bits of the CFNumber value will be garbage." ; 332 else 333 os << (SourceSize - TargetSize) 334 << " bits of the input integer will be lost."; 335 336 if (!BT) 337 BT.reset(new APIMisuse("Bad use of CFNumberCreate")); 338 339 RangedBugReport *report = new RangedBugReport(*BT, os.str(), N); 340 report->addRange(CE->getArg(2)->getSourceRange()); 341 C.EmitReport(report); 342 } 343} 344 345//===----------------------------------------------------------------------===// 346// CFRetain/CFRelease checking for null arguments. 347//===----------------------------------------------------------------------===// 348 349namespace { 350class CFRetainReleaseChecker : public CheckerV2< check::PreStmt<CallExpr> > { 351 mutable llvm::OwningPtr<APIMisuse> BT; 352 mutable IdentifierInfo *Retain, *Release; 353public: 354 CFRetainReleaseChecker(): Retain(0), Release(0) {} 355 void checkPreStmt(const CallExpr* CE, CheckerContext& C) const; 356}; 357} // end anonymous namespace 358 359 360void CFRetainReleaseChecker::checkPreStmt(const CallExpr* CE, 361 CheckerContext& C) const { 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.reset(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 CheckerV2<check::PreObjCMessage> { 433 mutable Selector releaseS; 434 mutable Selector retainS; 435 mutable Selector autoreleaseS; 436 mutable Selector drainS; 437 mutable llvm::OwningPtr<BugType> BT; 438 439public: 440 void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const; 441}; 442} 443 444void ClassReleaseChecker::checkPreObjCMessage(ObjCMessage msg, 445 CheckerContext &C) const { 446 447 if (!BT) { 448 BT.reset(new APIMisuse("message incorrectly sent to class instead of class " 449 "instance")); 450 451 ASTContext &Ctx = C.getASTContext(); 452 releaseS = GetNullarySelector("release", Ctx); 453 retainS = GetNullarySelector("retain", Ctx); 454 autoreleaseS = GetNullarySelector("autorelease", Ctx); 455 drainS = GetNullarySelector("drain", Ctx); 456 } 457 458 if (msg.isInstanceMessage()) 459 return; 460 const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 461 assert(Class); 462 463 Selector S = msg.getSelector(); 464 if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS)) 465 return; 466 467 if (ExplodedNode *N = C.generateNode()) { 468 llvm::SmallString<200> buf; 469 llvm::raw_svector_ostream os(buf); 470 471 os << "The '" << S.getAsString() << "' message should be sent to instances " 472 "of class '" << Class->getName() 473 << "' and not the class directly"; 474 475 RangedBugReport *report = new RangedBugReport(*BT, os.str(), N); 476 report->addRange(msg.getSourceRange()); 477 C.EmitReport(report); 478 } 479} 480 481//===----------------------------------------------------------------------===// 482// Check registration. 483//===----------------------------------------------------------------------===// 484 485void ento::registerNilArgChecker(CheckerManager &mgr) { 486 mgr.registerChecker<NilArgChecker>(); 487} 488 489void ento::registerCFNumberCreateChecker(CheckerManager &mgr) { 490 mgr.registerChecker<CFNumberCreateChecker>(); 491} 492 493void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) { 494 mgr.registerChecker<CFRetainReleaseChecker>(); 495} 496 497void ento::registerClassReleaseChecker(CheckerManager &mgr) { 498 mgr.registerChecker<ClassReleaseChecker>(); 499} 500