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/PathSensitive/ExplodedGraph.h" 20 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerVisitor.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/StaticAnalyzer/Core/PathSensitive/CheckerVisitor.h" 26 #include "clang/StaticAnalyzer/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 32 using namespace clang; 33 using namespace ento; 34 35 namespace { 36 class APIMisuse : public BugType { 37 public: 38 APIMisuse(const char* name) : BugType(name, "API Misuse (Apple)") {} 39 }; 40 } // end anonymous namespace 41 42 //===----------------------------------------------------------------------===// 43 // Utility functions. 44 //===----------------------------------------------------------------------===// 45 46 static const ObjCInterfaceType* GetReceiverType(const ObjCMessage &msg) { 47 if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface()) 48 return ID->getTypeForDecl()->getAs<ObjCInterfaceType>(); 49 return NULL; 50 } 51 52 static const char* GetReceiverNameType(const ObjCMessage &msg) { 53 if (const ObjCInterfaceType *ReceiverType = GetReceiverType(msg)) 54 return ReceiverType->getDecl()->getIdentifier()->getNameStart(); 55 return NULL; 56 } 57 58 static bool isNSString(llvm::StringRef ClassName) { 59 return ClassName == "NSString" || ClassName == "NSMutableString"; 60 } 61 62 static inline bool isNil(SVal X) { 63 return isa<loc::ConcreteInt>(X); 64 } 65 66 //===----------------------------------------------------------------------===// 67 // NilArgChecker - Check for prohibited nil arguments to ObjC method calls. 68 //===----------------------------------------------------------------------===// 69 70 namespace { 71 class NilArgChecker : public CheckerVisitor<NilArgChecker> { 72 APIMisuse *BT; 73 void WarnNilArg(CheckerContext &C, const ObjCMessage &msg, unsigned Arg); 74 public: 75 NilArgChecker() : BT(0) {} 76 static void *getTag() { static int x = 0; return &x; } 77 void preVisitObjCMessage(CheckerContext &C, ObjCMessage msg); 78 }; 79 } 80 81 void NilArgChecker::WarnNilArg(CheckerContext &C, 82 const ObjCMessage &msg, 83 unsigned int Arg) 84 { 85 if (!BT) 86 BT = new APIMisuse("nil argument"); 87 88 if (ExplodedNode *N = C.generateSink()) { 89 llvm::SmallString<128> sbuf; 90 llvm::raw_svector_ostream os(sbuf); 91 os << "Argument to '" << GetReceiverNameType(msg) << "' method '" 92 << msg.getSelector().getAsString() << "' cannot be nil"; 93 94 RangedBugReport *R = new RangedBugReport(*BT, os.str(), N); 95 R->addRange(msg.getArgSourceRange(Arg)); 96 C.EmitReport(R); 97 } 98 } 99 100 void NilArgChecker::preVisitObjCMessage(CheckerContext &C, 101 ObjCMessage msg) 102 { 103 const ObjCInterfaceType *ReceiverType = GetReceiverType(msg); 104 if (!ReceiverType) 105 return; 106 107 if (isNSString(ReceiverType->getDecl()->getIdentifier()->getName())) { 108 Selector S = msg.getSelector(); 109 110 if (S.isUnarySelector()) 111 return; 112 113 // FIXME: This is going to be really slow doing these checks with 114 // lexical comparisons. 115 116 std::string NameStr = S.getAsString(); 117 llvm::StringRef Name(NameStr); 118 assert(!Name.empty()); 119 120 // FIXME: Checking for initWithFormat: will not work in most cases 121 // yet because [NSString alloc] returns id, not NSString*. We will 122 // need support for tracking expected-type information in the analyzer 123 // to find these errors. 124 if (Name == "caseInsensitiveCompare:" || 125 Name == "compare:" || 126 Name == "compare:options:" || 127 Name == "compare:options:range:" || 128 Name == "compare:options:range:locale:" || 129 Name == "componentsSeparatedByCharactersInSet:" || 130 Name == "initWithFormat:") { 131 if (isNil(msg.getArgSVal(0, C.getState()))) 132 WarnNilArg(C, msg, 0); 133 } 134 } 135 } 136 137 //===----------------------------------------------------------------------===// 138 // Error reporting. 139 //===----------------------------------------------------------------------===// 140 141 namespace { 142 class CFNumberCreateChecker : public CheckerVisitor<CFNumberCreateChecker> { 143 APIMisuse* BT; 144 IdentifierInfo* II; 145 public: 146 CFNumberCreateChecker() : BT(0), II(0) {} 147 ~CFNumberCreateChecker() {} 148 static void *getTag() { static int x = 0; return &x; } 149 void PreVisitCallExpr(CheckerContext &C, const CallExpr *CE); 150 private: 151 void EmitError(const TypedRegion* R, const Expr* Ex, 152 uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind); 153 }; 154 } // end anonymous namespace 155 156 enum CFNumberType { 157 kCFNumberSInt8Type = 1, 158 kCFNumberSInt16Type = 2, 159 kCFNumberSInt32Type = 3, 160 kCFNumberSInt64Type = 4, 161 kCFNumberFloat32Type = 5, 162 kCFNumberFloat64Type = 6, 163 kCFNumberCharType = 7, 164 kCFNumberShortType = 8, 165 kCFNumberIntType = 9, 166 kCFNumberLongType = 10, 167 kCFNumberLongLongType = 11, 168 kCFNumberFloatType = 12, 169 kCFNumberDoubleType = 13, 170 kCFNumberCFIndexType = 14, 171 kCFNumberNSIntegerType = 15, 172 kCFNumberCGFloatType = 16 173 }; 174 175 namespace { 176 template<typename T> 177 class Optional { 178 bool IsKnown; 179 T Val; 180 public: 181 Optional() : IsKnown(false), Val(0) {} 182 Optional(const T& val) : IsKnown(true), Val(val) {} 183 184 bool isKnown() const { return IsKnown; } 185 186 const T& getValue() const { 187 assert (isKnown()); 188 return Val; 189 } 190 191 operator const T&() const { 192 return getValue(); 193 } 194 }; 195 } 196 197 static Optional<uint64_t> GetCFNumberSize(ASTContext& Ctx, uint64_t i) { 198 static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 }; 199 200 if (i < kCFNumberCharType) 201 return FixedSize[i-1]; 202 203 QualType T; 204 205 switch (i) { 206 case kCFNumberCharType: T = Ctx.CharTy; break; 207 case kCFNumberShortType: T = Ctx.ShortTy; break; 208 case kCFNumberIntType: T = Ctx.IntTy; break; 209 case kCFNumberLongType: T = Ctx.LongTy; break; 210 case kCFNumberLongLongType: T = Ctx.LongLongTy; break; 211 case kCFNumberFloatType: T = Ctx.FloatTy; break; 212 case kCFNumberDoubleType: T = Ctx.DoubleTy; break; 213 case kCFNumberCFIndexType: 214 case kCFNumberNSIntegerType: 215 case kCFNumberCGFloatType: 216 // FIXME: We need a way to map from names to Type*. 217 default: 218 return Optional<uint64_t>(); 219 } 220 221 return Ctx.getTypeSize(T); 222 } 223 224 #if 0 225 static const char* GetCFNumberTypeStr(uint64_t i) { 226 static const char* Names[] = { 227 "kCFNumberSInt8Type", 228 "kCFNumberSInt16Type", 229 "kCFNumberSInt32Type", 230 "kCFNumberSInt64Type", 231 "kCFNumberFloat32Type", 232 "kCFNumberFloat64Type", 233 "kCFNumberCharType", 234 "kCFNumberShortType", 235 "kCFNumberIntType", 236 "kCFNumberLongType", 237 "kCFNumberLongLongType", 238 "kCFNumberFloatType", 239 "kCFNumberDoubleType", 240 "kCFNumberCFIndexType", 241 "kCFNumberNSIntegerType", 242 "kCFNumberCGFloatType" 243 }; 244 245 return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType"; 246 } 247 #endif 248 249 void CFNumberCreateChecker::PreVisitCallExpr(CheckerContext &C, 250 const CallExpr *CE) 251 { 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 = 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 349 namespace { 350 class CFRetainReleaseChecker : public CheckerVisitor<CFRetainReleaseChecker> { 351 APIMisuse *BT; 352 IdentifierInfo *Retain, *Release; 353 public: 354 CFRetainReleaseChecker(): BT(0), Retain(0), Release(0) {} 355 static void *getTag() { static int x = 0; return &x; } 356 void PreVisitCallExpr(CheckerContext& C, const CallExpr* CE); 357 }; 358 } // end anonymous namespace 359 360 361 void CFRetainReleaseChecker::PreVisitCallExpr(CheckerContext& C, 362 const CallExpr* CE) { 363 // If the CallExpr doesn't have exactly 1 argument just give up checking. 364 if (CE->getNumArgs() != 1) 365 return; 366 367 // Get the function declaration of the callee. 368 const GRState* state = C.getState(); 369 SVal X = state->getSVal(CE->getCallee()); 370 const FunctionDecl* FD = X.getAsFunctionDecl(); 371 372 if (!FD) 373 return; 374 375 if (!BT) { 376 ASTContext &Ctx = C.getASTContext(); 377 Retain = &Ctx.Idents.get("CFRetain"); 378 Release = &Ctx.Idents.get("CFRelease"); 379 BT = new APIMisuse("null passed to CFRetain/CFRelease"); 380 } 381 382 // Check if we called CFRetain/CFRelease. 383 const IdentifierInfo *FuncII = FD->getIdentifier(); 384 if (!(FuncII == Retain || FuncII == Release)) 385 return; 386 387 // FIXME: The rest of this just checks that the argument is non-null. 388 // It should probably be refactored and combined with AttrNonNullChecker. 389 390 // Get the argument's value. 391 const Expr *Arg = CE->getArg(0); 392 SVal ArgVal = state->getSVal(Arg); 393 DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal); 394 if (!DefArgVal) 395 return; 396 397 // Get a NULL value. 398 SValBuilder &svalBuilder = C.getSValBuilder(); 399 DefinedSVal zero = cast<DefinedSVal>(svalBuilder.makeZeroVal(Arg->getType())); 400 401 // Make an expression asserting that they're equal. 402 DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal); 403 404 // Are they equal? 405 const GRState *stateTrue, *stateFalse; 406 llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); 407 408 if (stateTrue && !stateFalse) { 409 ExplodedNode *N = C.generateSink(stateTrue); 410 if (!N) 411 return; 412 413 const char *description = (FuncII == Retain) 414 ? "Null pointer argument in call to CFRetain" 415 : "Null pointer argument in call to CFRelease"; 416 417 EnhancedBugReport *report = new EnhancedBugReport(*BT, description, N); 418 report->addRange(Arg->getSourceRange()); 419 report->addVisitorCreator(bugreporter::registerTrackNullOrUndefValue, Arg); 420 C.EmitReport(report); 421 return; 422 } 423 424 // From here on, we know the argument is non-null. 425 C.addTransition(stateFalse); 426 } 427 428 //===----------------------------------------------------------------------===// 429 // Check for sending 'retain', 'release', or 'autorelease' directly to a Class. 430 //===----------------------------------------------------------------------===// 431 432 namespace { 433 class ClassReleaseChecker : public CheckerVisitor<ClassReleaseChecker> { 434 Selector releaseS; 435 Selector retainS; 436 Selector autoreleaseS; 437 Selector drainS; 438 BugType *BT; 439 public: 440 ClassReleaseChecker() 441 : BT(0) {} 442 443 static void *getTag() { static int x = 0; return &x; } 444 445 void preVisitObjCMessage(CheckerContext &C, ObjCMessage msg); 446 }; 447 } 448 449 void ClassReleaseChecker::preVisitObjCMessage(CheckerContext &C, 450 ObjCMessage msg) { 451 452 if (!BT) { 453 BT = new APIMisuse("message incorrectly sent to class instead of class " 454 "instance"); 455 456 ASTContext &Ctx = C.getASTContext(); 457 releaseS = GetNullarySelector("release", Ctx); 458 retainS = GetNullarySelector("retain", Ctx); 459 autoreleaseS = GetNullarySelector("autorelease", Ctx); 460 drainS = GetNullarySelector("drain", Ctx); 461 } 462 463 if (msg.isInstanceMessage()) 464 return; 465 const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); 466 assert(Class); 467 468 Selector S = msg.getSelector(); 469 if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS)) 470 return; 471 472 if (ExplodedNode *N = C.generateNode()) { 473 llvm::SmallString<200> buf; 474 llvm::raw_svector_ostream os(buf); 475 476 os << "The '" << S.getAsString() << "' message should be sent to instances " 477 "of class '" << Class->getName() 478 << "' and not the class directly"; 479 480 RangedBugReport *report = new RangedBugReport(*BT, os.str(), N); 481 report->addRange(msg.getSourceRange()); 482 C.EmitReport(report); 483 } 484 } 485 486 //===----------------------------------------------------------------------===// 487 // Check registration. 488 //===----------------------------------------------------------------------===// 489 490 void ento::registerNilArgChecker(ExprEngine& Eng) { 491 Eng.registerCheck(new NilArgChecker()); 492 } 493 494 void ento::registerCFNumberCreateChecker(ExprEngine& Eng) { 495 Eng.registerCheck(new CFNumberCreateChecker()); 496 } 497 498 void ento::registerCFRetainReleaseChecker(ExprEngine& Eng) { 499 Eng.registerCheck(new CFRetainReleaseChecker()); 500 } 501 502 void ento::registerClassReleaseChecker(ExprEngine& Eng) { 503 Eng.registerCheck(new ClassReleaseChecker()); 504 } 505