1283 lines · cpp
1//== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- C++ -*--2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines BasicObjCFoundationChecks, a class that encapsulates10// a set of simple checks to run on Objective-C code using Apple's Foundation11// classes.12//13//===----------------------------------------------------------------------===//14 15#include "clang/AST/ASTContext.h"16#include "clang/AST/DeclObjC.h"17#include "clang/AST/Expr.h"18#include "clang/AST/ExprObjC.h"19#include "clang/AST/StmtObjC.h"20#include "clang/Analysis/DomainSpecific/CocoaConventions.h"21#include "clang/Analysis/SelectorExtras.h"22#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"23#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"24#include "clang/StaticAnalyzer/Core/Checker.h"25#include "clang/StaticAnalyzer/Core/CheckerManager.h"26#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"27#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"28#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"29#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"30#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"31#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"32#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"33#include "llvm/ADT/STLExtras.h"34#include "llvm/ADT/StringMap.h"35#include "llvm/Support/raw_ostream.h"36#include <optional>37 38using namespace clang;39using namespace ento;40using namespace llvm;41 42namespace {43class APIMisuse : public BugType {44public:45 APIMisuse(const CheckerBase *checker, const char *name)46 : BugType(checker, name, categories::AppleAPIMisuse) {}47};48} // end anonymous namespace49 50//===----------------------------------------------------------------------===//51// Utility functions.52//===----------------------------------------------------------------------===//53 54static StringRef GetReceiverInterfaceName(const ObjCMethodCall &msg) {55 if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())56 return ID->getIdentifier()->getName();57 return StringRef();58}59 60enum FoundationClass {61 FC_None,62 FC_NSArray,63 FC_NSDictionary,64 FC_NSEnumerator,65 FC_NSNull,66 FC_NSOrderedSet,67 FC_NSSet,68 FC_NSString69};70 71static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID,72 bool IncludeSuperclasses = true) {73 static llvm::StringMap<FoundationClass> Classes;74 if (Classes.empty()) {75 Classes["NSArray"] = FC_NSArray;76 Classes["NSDictionary"] = FC_NSDictionary;77 Classes["NSEnumerator"] = FC_NSEnumerator;78 Classes["NSNull"] = FC_NSNull;79 Classes["NSOrderedSet"] = FC_NSOrderedSet;80 Classes["NSSet"] = FC_NSSet;81 Classes["NSString"] = FC_NSString;82 }83 84 // FIXME: Should we cache this at all?85 FoundationClass result = Classes.lookup(ID->getIdentifier()->getName());86 if (result == FC_None && IncludeSuperclasses)87 if (const ObjCInterfaceDecl *Super = ID->getSuperClass())88 return findKnownClass(Super);89 90 return result;91}92 93//===----------------------------------------------------------------------===//94// NilArgChecker - Check for prohibited nil arguments to ObjC method calls.95//===----------------------------------------------------------------------===//96 97namespace {98class NilArgChecker : public Checker<check::PreObjCMessage,99 check::PostStmt<ObjCDictionaryLiteral>,100 check::PostStmt<ObjCArrayLiteral>,101 EventDispatcher<ImplicitNullDerefEvent>> {102 const APIMisuse BT{this, "nil argument"};103 104 mutable llvm::SmallDenseMap<Selector, unsigned, 16> StringSelectors;105 mutable Selector ArrayWithObjectSel;106 mutable Selector AddObjectSel;107 mutable Selector InsertObjectAtIndexSel;108 mutable Selector ReplaceObjectAtIndexWithObjectSel;109 mutable Selector SetObjectAtIndexedSubscriptSel;110 mutable Selector ArrayByAddingObjectSel;111 mutable Selector DictionaryWithObjectForKeySel;112 mutable Selector SetObjectForKeySel;113 mutable Selector SetObjectForKeyedSubscriptSel;114 mutable Selector RemoveObjectForKeySel;115 116 void warnIfNilExpr(const Expr *E, const char *Msg, CheckerContext &C) const;117 118 void warnIfNilArg(CheckerContext &C, const ObjCMethodCall &msg, unsigned Arg,119 FoundationClass Class, bool CanBeSubscript = false) const;120 121 void generateBugReport(ExplodedNode *N, StringRef Msg, SourceRange Range,122 const Expr *Expr, CheckerContext &C) const;123 124public:125 void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;126 void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const;127 void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const;128};129} // end anonymous namespace130 131void NilArgChecker::warnIfNilExpr(const Expr *E,132 const char *Msg,133 CheckerContext &C) const {134 auto Location = C.getSVal(E).getAs<Loc>();135 if (!Location)136 return;137 138 auto [NonNull, Null] = C.getState()->assume(*Location);139 140 // If it's known to be null.141 if (!NonNull && Null) {142 if (ExplodedNode *N = C.generateErrorNode()) {143 generateBugReport(N, Msg, E->getSourceRange(), E, C);144 return;145 }146 }147 148 // If it might be null, assume that it cannot after this operation.149 if (Null) {150 // One needs to make sure the pointer is non-null to be used here.151 if (ExplodedNode *N = C.generateSink(Null, C.getPredecessor())) {152 dispatchEvent({*Location, /*IsLoad=*/false, N, &C.getBugReporter(),153 /*IsDirectDereference=*/false});154 }155 C.addTransition(NonNull);156 }157}158 159void NilArgChecker::warnIfNilArg(CheckerContext &C,160 const ObjCMethodCall &msg,161 unsigned int Arg,162 FoundationClass Class,163 bool CanBeSubscript) const {164 // Check if the argument is nil.165 ProgramStateRef State = C.getState();166 if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue())167 return;168 169 // NOTE: We cannot throw non-fatal errors from warnIfNilExpr,170 // because it's called multiple times from some callers, so it'd cause171 // an unwanted state split if two or more non-fatal errors are thrown172 // within the same checker callback. For now we don't want to, but173 // it'll need to be fixed if we ever want to.174 if (ExplodedNode *N = C.generateErrorNode()) {175 SmallString<128> sbuf;176 llvm::raw_svector_ostream os(sbuf);177 178 if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) {179 180 if (Class == FC_NSArray) {181 os << "Array element cannot be nil";182 } else if (Class == FC_NSDictionary) {183 if (Arg == 0) {184 os << "Value stored into '";185 os << GetReceiverInterfaceName(msg) << "' cannot be nil";186 } else {187 assert(Arg == 1);188 os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil";189 }190 } else191 llvm_unreachable("Missing foundation class for the subscript expr");192 193 } else {194 if (Class == FC_NSDictionary) {195 if (Arg == 0)196 os << "Value argument ";197 else {198 assert(Arg == 1);199 os << "Key argument ";200 }201 os << "to '";202 msg.getSelector().print(os);203 os << "' cannot be nil";204 } else {205 os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '";206 msg.getSelector().print(os);207 os << "' cannot be nil";208 }209 }210 211 generateBugReport(N, os.str(), msg.getArgSourceRange(Arg),212 msg.getArgExpr(Arg), C);213 }214}215 216void NilArgChecker::generateBugReport(ExplodedNode *N,217 StringRef Msg,218 SourceRange Range,219 const Expr *E,220 CheckerContext &C) const {221 auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);222 R->addRange(Range);223 bugreporter::trackExpressionValue(N, E, *R);224 C.emitReport(std::move(R));225}226 227void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,228 CheckerContext &C) const {229 const ObjCInterfaceDecl *ID = msg.getReceiverInterface();230 if (!ID)231 return;232 233 FoundationClass Class = findKnownClass(ID);234 235 static const unsigned InvalidArgIndex = UINT_MAX;236 unsigned Arg = InvalidArgIndex;237 bool CanBeSubscript = false;238 239 if (Class == FC_NSString) {240 Selector S = msg.getSelector();241 242 if (S.isUnarySelector())243 return;244 245 if (StringSelectors.empty()) {246 ASTContext &Ctx = C.getASTContext();247 Selector Sels[] = {248 getKeywordSelector(Ctx, "caseInsensitiveCompare"),249 getKeywordSelector(Ctx, "compare"),250 getKeywordSelector(Ctx, "compare", "options"),251 getKeywordSelector(Ctx, "compare", "options", "range"),252 getKeywordSelector(Ctx, "compare", "options", "range", "locale"),253 getKeywordSelector(Ctx, "componentsSeparatedByCharactersInSet"),254 getKeywordSelector(Ctx, "initWithFormat"),255 getKeywordSelector(Ctx, "localizedCaseInsensitiveCompare"),256 getKeywordSelector(Ctx, "localizedCompare"),257 getKeywordSelector(Ctx, "localizedStandardCompare"),258 };259 for (Selector KnownSel : Sels)260 StringSelectors[KnownSel] = 0;261 }262 auto I = StringSelectors.find(S);263 if (I == StringSelectors.end())264 return;265 Arg = I->second;266 } else if (Class == FC_NSArray) {267 Selector S = msg.getSelector();268 269 if (S.isUnarySelector())270 return;271 272 if (ArrayWithObjectSel.isNull()) {273 ASTContext &Ctx = C.getASTContext();274 ArrayWithObjectSel = getKeywordSelector(Ctx, "arrayWithObject");275 AddObjectSel = getKeywordSelector(Ctx, "addObject");276 InsertObjectAtIndexSel =277 getKeywordSelector(Ctx, "insertObject", "atIndex");278 ReplaceObjectAtIndexWithObjectSel =279 getKeywordSelector(Ctx, "replaceObjectAtIndex", "withObject");280 SetObjectAtIndexedSubscriptSel =281 getKeywordSelector(Ctx, "setObject", "atIndexedSubscript");282 ArrayByAddingObjectSel = getKeywordSelector(Ctx, "arrayByAddingObject");283 }284 285 if (S == ArrayWithObjectSel || S == AddObjectSel ||286 S == InsertObjectAtIndexSel || S == ArrayByAddingObjectSel) {287 Arg = 0;288 } else if (S == SetObjectAtIndexedSubscriptSel) {289 Arg = 0;290 CanBeSubscript = true;291 } else if (S == ReplaceObjectAtIndexWithObjectSel) {292 Arg = 1;293 }294 } else if (Class == FC_NSDictionary) {295 Selector S = msg.getSelector();296 297 if (S.isUnarySelector())298 return;299 300 if (DictionaryWithObjectForKeySel.isNull()) {301 ASTContext &Ctx = C.getASTContext();302 DictionaryWithObjectForKeySel =303 getKeywordSelector(Ctx, "dictionaryWithObject", "forKey");304 SetObjectForKeySel = getKeywordSelector(Ctx, "setObject", "forKey");305 SetObjectForKeyedSubscriptSel =306 getKeywordSelector(Ctx, "setObject", "forKeyedSubscript");307 RemoveObjectForKeySel = getKeywordSelector(Ctx, "removeObjectForKey");308 }309 310 if (S == DictionaryWithObjectForKeySel || S == SetObjectForKeySel) {311 Arg = 0;312 warnIfNilArg(C, msg, /* Arg */1, Class);313 } else if (S == SetObjectForKeyedSubscriptSel) {314 CanBeSubscript = true;315 Arg = 1;316 } else if (S == RemoveObjectForKeySel) {317 Arg = 0;318 }319 }320 321 // If argument is '0', report a warning.322 if ((Arg != InvalidArgIndex))323 warnIfNilArg(C, msg, Arg, Class, CanBeSubscript);324}325 326void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL,327 CheckerContext &C) const {328 unsigned NumOfElements = AL->getNumElements();329 for (unsigned i = 0; i < NumOfElements; ++i) {330 warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C);331 }332}333 334void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,335 CheckerContext &C) const {336 unsigned NumOfElements = DL->getNumElements();337 for (unsigned i = 0; i < NumOfElements; ++i) {338 ObjCDictionaryElement Element = DL->getKeyValueElement(i);339 warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C);340 warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C);341 }342}343 344//===----------------------------------------------------------------------===//345// Checking for mismatched types passed to CFNumberCreate/CFNumberGetValue.346//===----------------------------------------------------------------------===//347 348namespace {349class CFNumberChecker : public Checker< check::PreStmt<CallExpr> > {350 const APIMisuse BT{this, "Bad use of CFNumber APIs"};351 mutable IdentifierInfo *ICreate = nullptr, *IGetValue = nullptr;352public:353 CFNumberChecker() = default;354 355 void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;356};357} // end anonymous namespace358 359enum CFNumberType {360 kCFNumberSInt8Type = 1,361 kCFNumberSInt16Type = 2,362 kCFNumberSInt32Type = 3,363 kCFNumberSInt64Type = 4,364 kCFNumberFloat32Type = 5,365 kCFNumberFloat64Type = 6,366 kCFNumberCharType = 7,367 kCFNumberShortType = 8,368 kCFNumberIntType = 9,369 kCFNumberLongType = 10,370 kCFNumberLongLongType = 11,371 kCFNumberFloatType = 12,372 kCFNumberDoubleType = 13,373 kCFNumberCFIndexType = 14,374 kCFNumberNSIntegerType = 15,375 kCFNumberCGFloatType = 16376};377 378static std::optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) {379 static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 };380 381 if (i < kCFNumberCharType)382 return FixedSize[i-1];383 384 QualType T;385 386 switch (i) {387 case kCFNumberCharType: T = Ctx.CharTy; break;388 case kCFNumberShortType: T = Ctx.ShortTy; break;389 case kCFNumberIntType: T = Ctx.IntTy; break;390 case kCFNumberLongType: T = Ctx.LongTy; break;391 case kCFNumberLongLongType: T = Ctx.LongLongTy; break;392 case kCFNumberFloatType: T = Ctx.FloatTy; break;393 case kCFNumberDoubleType: T = Ctx.DoubleTy; break;394 case kCFNumberCFIndexType:395 case kCFNumberNSIntegerType:396 case kCFNumberCGFloatType:397 // FIXME: We need a way to map from names to Type*.398 default:399 return std::nullopt;400 }401 402 return Ctx.getTypeSize(T);403}404 405#if 0406static const char* GetCFNumberTypeStr(uint64_t i) {407 static const char* Names[] = {408 "kCFNumberSInt8Type",409 "kCFNumberSInt16Type",410 "kCFNumberSInt32Type",411 "kCFNumberSInt64Type",412 "kCFNumberFloat32Type",413 "kCFNumberFloat64Type",414 "kCFNumberCharType",415 "kCFNumberShortType",416 "kCFNumberIntType",417 "kCFNumberLongType",418 "kCFNumberLongLongType",419 "kCFNumberFloatType",420 "kCFNumberDoubleType",421 "kCFNumberCFIndexType",422 "kCFNumberNSIntegerType",423 "kCFNumberCGFloatType"424 };425 426 return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType";427}428#endif429 430void CFNumberChecker::checkPreStmt(const CallExpr *CE,431 CheckerContext &C) const {432 ProgramStateRef state = C.getState();433 const FunctionDecl *FD = C.getCalleeDecl(CE);434 if (!FD)435 return;436 437 ASTContext &Ctx = C.getASTContext();438 if (!ICreate) {439 ICreate = &Ctx.Idents.get("CFNumberCreate");440 IGetValue = &Ctx.Idents.get("CFNumberGetValue");441 }442 if (!(FD->getIdentifier() == ICreate || FD->getIdentifier() == IGetValue) ||443 CE->getNumArgs() != 3)444 return;445 446 // Get the value of the "theType" argument.447 SVal TheTypeVal = C.getSVal(CE->getArg(1));448 449 // FIXME: We really should allow ranges of valid theType values, and450 // bifurcate the state appropriately.451 std::optional<nonloc::ConcreteInt> V =452 dyn_cast<nonloc::ConcreteInt>(TheTypeVal);453 if (!V)454 return;455 456 uint64_t NumberKind = V->getValue()->getLimitedValue();457 std::optional<uint64_t> OptCFNumberSize = GetCFNumberSize(Ctx, NumberKind);458 459 // FIXME: In some cases we can emit an error.460 if (!OptCFNumberSize)461 return;462 463 uint64_t CFNumberSize = *OptCFNumberSize;464 465 // Look at the value of the integer being passed by reference. Essentially466 // we want to catch cases where the value passed in is not equal to the467 // size of the type being created.468 SVal TheValueExpr = C.getSVal(CE->getArg(2));469 470 // FIXME: Eventually we should handle arbitrary locations. We can do this471 // by having an enhanced memory model that does low-level typing.472 std::optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>();473 if (!LV)474 return;475 476 const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts());477 if (!R)478 return;479 480 QualType T = Ctx.getCanonicalType(R->getValueType());481 482 // FIXME: If the pointee isn't an integer type, should we flag a warning?483 // People can do weird stuff with pointers.484 485 if (!T->isIntegralOrEnumerationType())486 return;487 488 uint64_t PrimitiveTypeSize = Ctx.getTypeSize(T);489 490 if (PrimitiveTypeSize == CFNumberSize)491 return;492 493 // FIXME: We can actually create an abstract "CFNumber" object that has494 // the bits initialized to the provided values.495 ExplodedNode *N = C.generateNonFatalErrorNode();496 if (N) {497 SmallString<128> sbuf;498 llvm::raw_svector_ostream os(sbuf);499 bool isCreate = (FD->getIdentifier() == ICreate);500 501 if (isCreate) {502 os << (PrimitiveTypeSize == 8 ? "An " : "A ")503 << PrimitiveTypeSize << "-bit integer is used to initialize a "504 << "CFNumber object that represents "505 << (CFNumberSize == 8 ? "an " : "a ")506 << CFNumberSize << "-bit integer; ";507 } else {508 os << "A CFNumber object that represents "509 << (CFNumberSize == 8 ? "an " : "a ")510 << CFNumberSize << "-bit integer is used to initialize "511 << (PrimitiveTypeSize == 8 ? "an " : "a ")512 << PrimitiveTypeSize << "-bit integer; ";513 }514 515 if (PrimitiveTypeSize < CFNumberSize)516 os << (CFNumberSize - PrimitiveTypeSize)517 << " bits of the CFNumber value will "518 << (isCreate ? "be garbage." : "overwrite adjacent storage.");519 else520 os << (PrimitiveTypeSize - CFNumberSize)521 << " bits of the integer value will be "522 << (isCreate ? "lost." : "garbage.");523 524 auto report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);525 report->addRange(CE->getArg(2)->getSourceRange());526 C.emitReport(std::move(report));527 }528}529 530//===----------------------------------------------------------------------===//531// CFRetain/CFRelease/CFMakeCollectable/CFAutorelease checking for null arguments.532//===----------------------------------------------------------------------===//533 534namespace {535class CFRetainReleaseChecker : public Checker<check::PreCall> {536 const APIMisuse BT{this, "null passed to CF memory management function"};537 const CallDescriptionSet ModelledCalls = {538 {CDM::CLibrary, {"CFRetain"}, 1},539 {CDM::CLibrary, {"CFRelease"}, 1},540 {CDM::CLibrary, {"CFMakeCollectable"}, 1},541 {CDM::CLibrary, {"CFAutorelease"}, 1},542 };543 544public:545 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;546};547} // end anonymous namespace548 549void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call,550 CheckerContext &C) const {551 // Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease.552 if (!ModelledCalls.contains(Call))553 return;554 555 // Get the argument's value.556 SVal ArgVal = Call.getArgSVal(0);557 std::optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>();558 if (!DefArgVal)559 return;560 561 // Is it null?562 ProgramStateRef state = C.getState();563 ProgramStateRef stateNonNull, stateNull;564 std::tie(stateNonNull, stateNull) = state->assume(*DefArgVal);565 566 if (!stateNonNull) {567 ExplodedNode *N = C.generateErrorNode(stateNull);568 if (!N)569 return;570 571 SmallString<64> Str;572 raw_svector_ostream OS(Str);573 OS << "Null pointer argument in call to "574 << cast<FunctionDecl>(Call.getDecl())->getName();575 576 auto report = std::make_unique<PathSensitiveBugReport>(BT, OS.str(), N);577 report->addRange(Call.getArgSourceRange(0));578 bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report);579 C.emitReport(std::move(report));580 return;581 }582 583 // From here on, we know the argument is non-null.584 C.addTransition(stateNonNull);585}586 587//===----------------------------------------------------------------------===//588// Check for sending 'retain', 'release', or 'autorelease' directly to a Class.589//===----------------------------------------------------------------------===//590 591namespace {592class ClassReleaseChecker : public Checker<check::PreObjCMessage> {593 mutable Selector releaseS;594 mutable Selector retainS;595 mutable Selector autoreleaseS;596 mutable Selector drainS;597 const APIMisuse BT{598 this, "message incorrectly sent to class instead of class instance"};599 600public:601 void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;602};603} // end anonymous namespace604 605void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,606 CheckerContext &C) const {607 if (releaseS.isNull()) {608 ASTContext &Ctx = C.getASTContext();609 releaseS = GetNullarySelector("release", Ctx);610 retainS = GetNullarySelector("retain", Ctx);611 autoreleaseS = GetNullarySelector("autorelease", Ctx);612 drainS = GetNullarySelector("drain", Ctx);613 }614 615 if (msg.isInstanceMessage())616 return;617 const ObjCInterfaceDecl *Class = msg.getReceiverInterface();618 assert(Class);619 620 Selector S = msg.getSelector();621 if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))622 return;623 624 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {625 SmallString<200> buf;626 llvm::raw_svector_ostream os(buf);627 628 os << "The '";629 S.print(os);630 os << "' message should be sent to instances "631 "of class '" << Class->getName()632 << "' and not the class directly";633 634 auto report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);635 report->addRange(msg.getSourceRange());636 C.emitReport(std::move(report));637 }638}639 640//===----------------------------------------------------------------------===//641// Check for passing non-Objective-C types to variadic methods that expect642// only Objective-C types.643//===----------------------------------------------------------------------===//644 645namespace {646class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> {647 mutable Selector arrayWithObjectsS;648 mutable Selector dictionaryWithObjectsAndKeysS;649 mutable Selector setWithObjectsS;650 mutable Selector orderedSetWithObjectsS;651 mutable Selector initWithObjectsS;652 mutable Selector initWithObjectsAndKeysS;653 const APIMisuse BT{this, "Arguments passed to variadic method aren't all "654 "Objective-C pointer types"};655 656 bool isVariadicMessage(const ObjCMethodCall &msg) const;657 658public:659 void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;660};661} // end anonymous namespace662 663/// isVariadicMessage - Returns whether the given message is a variadic message,664/// where all arguments must be Objective-C types.665bool666VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const {667 const ObjCMethodDecl *MD = msg.getDecl();668 669 if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))670 return false;671 672 Selector S = msg.getSelector();673 674 if (msg.isInstanceMessage()) {675 // FIXME: Ideally we'd look at the receiver interface here, but that's not676 // useful for init, because alloc returns 'id'. In theory, this could lead677 // to false positives, for example if there existed a class that had an678 // initWithObjects: implementation that does accept non-Objective-C pointer679 // types, but the chance of that happening is pretty small compared to the680 // gains that this analysis gives.681 const ObjCInterfaceDecl *Class = MD->getClassInterface();682 683 switch (findKnownClass(Class)) {684 case FC_NSArray:685 case FC_NSOrderedSet:686 case FC_NSSet:687 return S == initWithObjectsS;688 case FC_NSDictionary:689 return S == initWithObjectsAndKeysS;690 default:691 return false;692 }693 } else {694 const ObjCInterfaceDecl *Class = msg.getReceiverInterface();695 696 switch (findKnownClass(Class)) {697 case FC_NSArray:698 return S == arrayWithObjectsS;699 case FC_NSOrderedSet:700 return S == orderedSetWithObjectsS;701 case FC_NSSet:702 return S == setWithObjectsS;703 case FC_NSDictionary:704 return S == dictionaryWithObjectsAndKeysS;705 default:706 return false;707 }708 }709}710 711void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg,712 CheckerContext &C) const {713 if (arrayWithObjectsS.isNull()) {714 ASTContext &Ctx = C.getASTContext();715 arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);716 dictionaryWithObjectsAndKeysS =717 GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);718 setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);719 orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx);720 721 initWithObjectsS = GetUnarySelector("initWithObjects", Ctx);722 initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx);723 }724 725 if (!isVariadicMessage(msg))726 return;727 728 // We are not interested in the selector arguments since they have729 // well-defined types, so the compiler will issue a warning for them.730 unsigned variadicArgsBegin = msg.getSelector().getNumArgs();731 732 // We're not interested in the last argument since it has to be nil or the733 // compiler would have issued a warning for it elsewhere.734 unsigned variadicArgsEnd = msg.getNumArgs() - 1;735 736 if (variadicArgsEnd <= variadicArgsBegin)737 return;738 739 // Verify that all arguments have Objective-C types.740 std::optional<ExplodedNode *> errorNode;741 742 for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {743 QualType ArgTy = msg.getArgExpr(I)->getType();744 if (ArgTy->isObjCObjectPointerType())745 continue;746 747 // Block pointers are treaded as Objective-C pointers.748 if (ArgTy->isBlockPointerType())749 continue;750 751 // Ignore pointer constants.752 if (isa<loc::ConcreteInt>(msg.getArgSVal(I)))753 continue;754 755 // Ignore pointer types annotated with 'NSObject' attribute.756 if (C.getASTContext().isObjCNSObjectType(ArgTy))757 continue;758 759 // Ignore CF references, which can be toll-free bridged.760 if (coreFoundation::isCFObjectRef(ArgTy))761 continue;762 763 // Generate only one error node to use for all bug reports.764 if (!errorNode)765 errorNode = C.generateNonFatalErrorNode();766 767 if (!*errorNode)768 continue;769 770 SmallString<128> sbuf;771 llvm::raw_svector_ostream os(sbuf);772 773 StringRef TypeName = GetReceiverInterfaceName(msg);774 if (!TypeName.empty())775 os << "Argument to '" << TypeName << "' method '";776 else777 os << "Argument to method '";778 779 msg.getSelector().print(os);780 os << "' should be an Objective-C pointer type, not '";781 ArgTy.print(os, C.getLangOpts());782 os << "'";783 784 auto R = std::make_unique<PathSensitiveBugReport>(BT, os.str(), *errorNode);785 R->addRange(msg.getArgSourceRange(I));786 C.emitReport(std::move(R));787 }788}789 790//===----------------------------------------------------------------------===//791// Improves the modeling of loops over Cocoa collections.792//===----------------------------------------------------------------------===//793 794// The map from container symbol to the container count symbol.795// We currently will remember the last container count symbol encountered.796REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef)797REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool)798 799namespace {800class ObjCLoopChecker801 : public Checker<check::PostStmt<ObjCForCollectionStmt>,802 check::PostObjCMessage,803 check::DeadSymbols,804 check::PointerEscape > {805 mutable IdentifierInfo *CountSelectorII = nullptr;806 807 bool isCollectionCountMethod(const ObjCMethodCall &M,808 CheckerContext &C) const;809 810public:811 ObjCLoopChecker() = default;812 void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const;813 void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;814 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;815 ProgramStateRef checkPointerEscape(ProgramStateRef State,816 const InvalidatedSymbols &Escaped,817 const CallEvent *Call,818 PointerEscapeKind Kind) const;819};820} // end anonymous namespace821 822static bool isKnownNonNilCollectionType(QualType T) {823 const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();824 if (!PT)825 return false;826 827 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();828 if (!ID)829 return false;830 831 switch (findKnownClass(ID)) {832 case FC_NSArray:833 case FC_NSDictionary:834 case FC_NSEnumerator:835 case FC_NSOrderedSet:836 case FC_NSSet:837 return true;838 default:839 return false;840 }841}842 843/// Assumes that the collection is non-nil.844///845/// If the collection is known to be nil, returns NULL to indicate an infeasible846/// path.847static ProgramStateRef checkCollectionNonNil(CheckerContext &C,848 ProgramStateRef State,849 const ObjCForCollectionStmt *FCS) {850 if (!State)851 return nullptr;852 853 SVal CollectionVal = C.getSVal(FCS->getCollection());854 std::optional<DefinedSVal> KnownCollection =855 CollectionVal.getAs<DefinedSVal>();856 if (!KnownCollection)857 return State;858 859 ProgramStateRef StNonNil, StNil;860 std::tie(StNonNil, StNil) = State->assume(*KnownCollection);861 if (StNil && !StNonNil) {862 // The collection is nil. This path is infeasible.863 return nullptr;864 }865 866 return StNonNil;867}868 869/// Assumes that the collection elements are non-nil.870///871/// This only applies if the collection is one of those known not to contain872/// nil values.873static ProgramStateRef checkElementNonNil(CheckerContext &C,874 ProgramStateRef State,875 const ObjCForCollectionStmt *FCS) {876 if (!State)877 return nullptr;878 879 // See if the collection is one where we /know/ the elements are non-nil.880 if (!isKnownNonNilCollectionType(FCS->getCollection()->getType()))881 return State;882 883 const LocationContext *LCtx = C.getLocationContext();884 const Stmt *Element = FCS->getElement();885 886 // FIXME: Copied from ExprEngineObjC.887 std::optional<Loc> ElementLoc;888 if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) {889 const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl());890 assert(ElemDecl->getInit() == nullptr);891 ElementLoc = State->getLValue(ElemDecl, LCtx);892 } else {893 ElementLoc = State->getSVal(Element, LCtx).getAs<Loc>();894 }895 896 if (!ElementLoc)897 return State;898 899 // Go ahead and assume the value is non-nil.900 SVal Val = State->getSVal(*ElementLoc);901 return State->assume(cast<DefinedOrUnknownSVal>(Val), true);902}903 904/// Returns NULL state if the collection is known to contain elements905/// (or is known not to contain elements if the Assumption parameter is false.)906static ProgramStateRef907assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,908 SymbolRef CollectionS, bool Assumption) {909 if (!State || !CollectionS)910 return State;911 912 const SymbolRef *CountS = State->get<ContainerCountMap>(CollectionS);913 if (!CountS) {914 const bool *KnownNonEmpty = State->get<ContainerNonEmptyMap>(CollectionS);915 if (!KnownNonEmpty)916 return State->set<ContainerNonEmptyMap>(CollectionS, Assumption);917 return (Assumption == *KnownNonEmpty) ? State : nullptr;918 }919 920 SValBuilder &SvalBuilder = C.getSValBuilder();921 SVal CountGreaterThanZeroVal =922 SvalBuilder.evalBinOp(State, BO_GT,923 nonloc::SymbolVal(*CountS),924 SvalBuilder.makeIntVal(0, (*CountS)->getType()),925 SvalBuilder.getConditionType());926 std::optional<DefinedSVal> CountGreaterThanZero =927 CountGreaterThanZeroVal.getAs<DefinedSVal>();928 if (!CountGreaterThanZero) {929 // The SValBuilder cannot construct a valid SVal for this condition.930 // This means we cannot properly reason about it.931 return State;932 }933 934 return State->assume(*CountGreaterThanZero, Assumption);935}936 937static ProgramStateRef938assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,939 const ObjCForCollectionStmt *FCS,940 bool Assumption) {941 if (!State)942 return nullptr;943 944 SymbolRef CollectionS = C.getSVal(FCS->getCollection()).getAsSymbol();945 return assumeCollectionNonEmpty(C, State, CollectionS, Assumption);946}947 948/// If the fist block edge is a back edge, we are reentering the loop.949static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N,950 const ObjCForCollectionStmt *FCS) {951 if (!N)952 return false;953 954 ProgramPoint P = N->getLocation();955 if (std::optional<BlockEdge> BE = P.getAs<BlockEdge>()) {956 return BE->getSrc()->getLoopTarget() == FCS;957 }958 959 // Keep looking for a block edge.960 for (const ExplodedNode *N : N->preds()) {961 if (alreadyExecutedAtLeastOneLoopIteration(N, FCS))962 return true;963 }964 965 return false;966}967 968void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS,969 CheckerContext &C) const {970 ProgramStateRef State = C.getState();971 972 // Check if this is the branch for the end of the loop.973 if (!ExprEngine::hasMoreIteration(State, FCS, C.getLocationContext())) {974 if (!alreadyExecutedAtLeastOneLoopIteration(C.getPredecessor(), FCS))975 State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/false);976 977 // Otherwise, this is a branch that goes through the loop body.978 } else {979 State = checkCollectionNonNil(C, State, FCS);980 State = checkElementNonNil(C, State, FCS);981 State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true);982 }983 984 if (!State)985 C.generateSink(C.getState(), C.getPredecessor());986 else if (State != C.getState())987 C.addTransition(State);988}989 990bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M,991 CheckerContext &C) const {992 Selector S = M.getSelector();993 // Initialize the identifiers on first use.994 if (!CountSelectorII)995 CountSelectorII = &C.getASTContext().Idents.get("count");996 997 // If the method returns collection count, record the value.998 return S.isUnarySelector() &&999 (S.getIdentifierInfoForSlot(0) == CountSelectorII);1000}1001 1002void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M,1003 CheckerContext &C) const {1004 if (!M.isInstanceMessage())1005 return;1006 1007 const ObjCInterfaceDecl *ClassID = M.getReceiverInterface();1008 if (!ClassID)1009 return;1010 1011 FoundationClass Class = findKnownClass(ClassID);1012 if (Class != FC_NSDictionary &&1013 Class != FC_NSArray &&1014 Class != FC_NSSet &&1015 Class != FC_NSOrderedSet)1016 return;1017 1018 SymbolRef ContainerS = M.getReceiverSVal().getAsSymbol();1019 if (!ContainerS)1020 return;1021 1022 // If we are processing a call to "count", get the symbolic value returned by1023 // a call to "count" and add it to the map.1024 if (!isCollectionCountMethod(M, C))1025 return;1026 1027 const Expr *MsgExpr = M.getOriginExpr();1028 SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol();1029 if (CountS) {1030 ProgramStateRef State = C.getState();1031 1032 C.getSymbolManager().addSymbolDependency(ContainerS, CountS);1033 State = State->set<ContainerCountMap>(ContainerS, CountS);1034 1035 if (const bool *NonEmpty = State->get<ContainerNonEmptyMap>(ContainerS)) {1036 State = State->remove<ContainerNonEmptyMap>(ContainerS);1037 State = assumeCollectionNonEmpty(C, State, ContainerS, *NonEmpty);1038 }1039 1040 C.addTransition(State);1041 }1042}1043 1044static SymbolRef getMethodReceiverIfKnownImmutable(const CallEvent *Call) {1045 const ObjCMethodCall *Message = dyn_cast_or_null<ObjCMethodCall>(Call);1046 if (!Message)1047 return nullptr;1048 1049 const ObjCMethodDecl *MD = Message->getDecl();1050 if (!MD)1051 return nullptr;1052 1053 const ObjCInterfaceDecl *StaticClass;1054 if (isa<ObjCProtocolDecl>(MD->getDeclContext())) {1055 // We can't find out where the method was declared without doing more work.1056 // Instead, see if the receiver is statically typed as a known immutable1057 // collection.1058 StaticClass = Message->getOriginExpr()->getReceiverInterface();1059 } else {1060 StaticClass = MD->getClassInterface();1061 }1062 1063 if (!StaticClass)1064 return nullptr;1065 1066 switch (findKnownClass(StaticClass, /*IncludeSuper=*/false)) {1067 case FC_None:1068 return nullptr;1069 case FC_NSArray:1070 case FC_NSDictionary:1071 case FC_NSEnumerator:1072 case FC_NSNull:1073 case FC_NSOrderedSet:1074 case FC_NSSet:1075 case FC_NSString:1076 break;1077 }1078 1079 return Message->getReceiverSVal().getAsSymbol();1080}1081 1082ProgramStateRef1083ObjCLoopChecker::checkPointerEscape(ProgramStateRef State,1084 const InvalidatedSymbols &Escaped,1085 const CallEvent *Call,1086 PointerEscapeKind Kind) const {1087 SymbolRef ImmutableReceiver = getMethodReceiverIfKnownImmutable(Call);1088 1089 // Remove the invalidated symbols from the collection count map.1090 for (SymbolRef Sym : Escaped) {1091 // Don't invalidate this symbol's count if we know the method being called1092 // is declared on an immutable class. This isn't completely correct if the1093 // receiver is also passed as an argument, but in most uses of NSArray,1094 // NSDictionary, etc. this isn't likely to happen in a dangerous way.1095 if (Sym == ImmutableReceiver)1096 continue;1097 1098 // The symbol escaped. Pessimistically, assume that the count could have1099 // changed.1100 State = State->remove<ContainerCountMap>(Sym);1101 State = State->remove<ContainerNonEmptyMap>(Sym);1102 }1103 return State;1104}1105 1106void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper,1107 CheckerContext &C) const {1108 ProgramStateRef State = C.getState();1109 1110 // Remove the dead symbols from the collection count map.1111 ContainerCountMapTy Tracked = State->get<ContainerCountMap>();1112 for (SymbolRef Sym : llvm::make_first_range(Tracked)) {1113 if (SymReaper.isDead(Sym)) {1114 State = State->remove<ContainerCountMap>(Sym);1115 State = State->remove<ContainerNonEmptyMap>(Sym);1116 }1117 }1118 1119 C.addTransition(State);1120}1121 1122namespace {1123/// \class ObjCNonNilReturnValueChecker1124/// The checker restricts the return values of APIs known to1125/// never (or almost never) return 'nil'.1126class ObjCNonNilReturnValueChecker1127 : public Checker<check::PostObjCMessage,1128 check::PostStmt<ObjCArrayLiteral>,1129 check::PostStmt<ObjCDictionaryLiteral>,1130 check::PostStmt<ObjCBoxedExpr> > {1131 mutable bool Initialized = false;1132 mutable Selector ObjectAtIndex;1133 mutable Selector ObjectAtIndexedSubscript;1134 mutable Selector NullSelector;1135 1136public:1137 ObjCNonNilReturnValueChecker() = default;1138 1139 ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr,1140 ProgramStateRef State,1141 CheckerContext &C) const;1142 void assumeExprIsNonNull(const Expr *E, CheckerContext &C) const {1143 C.addTransition(assumeExprIsNonNull(E, C.getState(), C));1144 }1145 1146 void checkPostStmt(const ObjCArrayLiteral *E, CheckerContext &C) const {1147 assumeExprIsNonNull(E, C);1148 }1149 void checkPostStmt(const ObjCDictionaryLiteral *E, CheckerContext &C) const {1150 assumeExprIsNonNull(E, C);1151 }1152 void checkPostStmt(const ObjCBoxedExpr *E, CheckerContext &C) const {1153 assumeExprIsNonNull(E, C);1154 }1155 1156 void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;1157};1158} // end anonymous namespace1159 1160ProgramStateRef1161ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr,1162 ProgramStateRef State,1163 CheckerContext &C) const {1164 SVal Val = C.getSVal(NonNullExpr);1165 if (std::optional<DefinedOrUnknownSVal> DV =1166 Val.getAs<DefinedOrUnknownSVal>())1167 return State->assume(*DV, true);1168 return State;1169}1170 1171void ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M,1172 CheckerContext &C)1173 const {1174 ProgramStateRef State = C.getState();1175 1176 if (!Initialized) {1177 ASTContext &Ctx = C.getASTContext();1178 ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx);1179 ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx);1180 NullSelector = GetNullarySelector("null", Ctx);1181 }1182 1183 // Check the receiver type.1184 if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) {1185 1186 // Assume that object returned from '[self init]' or '[super init]' is not1187 // 'nil' if we are processing an inlined function/method.1188 //1189 // A defensive callee will (and should) check if the object returned by1190 // '[super init]' is 'nil' before doing it's own initialization. However,1191 // since 'nil' is rarely returned in practice, we should not warn when the1192 // caller to the defensive constructor uses the object in contexts where1193 // 'nil' is not accepted.1194 if (!C.inTopFrame() && M.getDecl() &&1195 M.getDecl()->getMethodFamily() == OMF_init &&1196 M.isReceiverSelfOrSuper()) {1197 State = assumeExprIsNonNull(M.getOriginExpr(), State, C);1198 }1199 1200 FoundationClass Cl = findKnownClass(Interface);1201 1202 // Objects returned from1203 // [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript]1204 // are never 'nil'.1205 if (Cl == FC_NSArray || Cl == FC_NSOrderedSet) {1206 Selector Sel = M.getSelector();1207 if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript) {1208 // Go ahead and assume the value is non-nil.1209 State = assumeExprIsNonNull(M.getOriginExpr(), State, C);1210 }1211 }1212 1213 // Objects returned from [NSNull null] are not nil.1214 if (Cl == FC_NSNull) {1215 if (M.getSelector() == NullSelector) {1216 // Go ahead and assume the value is non-nil.1217 State = assumeExprIsNonNull(M.getOriginExpr(), State, C);1218 }1219 }1220 }1221 C.addTransition(State);1222}1223 1224//===----------------------------------------------------------------------===//1225// Check registration.1226//===----------------------------------------------------------------------===//1227 1228void ento::registerNilArgChecker(CheckerManager &mgr) {1229 mgr.registerChecker<NilArgChecker>();1230}1231 1232bool ento::shouldRegisterNilArgChecker(const CheckerManager &mgr) {1233 return true;1234}1235 1236void ento::registerCFNumberChecker(CheckerManager &mgr) {1237 mgr.registerChecker<CFNumberChecker>();1238}1239 1240bool ento::shouldRegisterCFNumberChecker(const CheckerManager &mgr) {1241 return true;1242}1243 1244void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {1245 mgr.registerChecker<CFRetainReleaseChecker>();1246}1247 1248bool ento::shouldRegisterCFRetainReleaseChecker(const CheckerManager &mgr) {1249 return true;1250}1251 1252void ento::registerClassReleaseChecker(CheckerManager &mgr) {1253 mgr.registerChecker<ClassReleaseChecker>();1254}1255 1256bool ento::shouldRegisterClassReleaseChecker(const CheckerManager &mgr) {1257 return true;1258}1259 1260void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {1261 mgr.registerChecker<VariadicMethodTypeChecker>();1262}1263 1264bool ento::shouldRegisterVariadicMethodTypeChecker(const CheckerManager &mgr) {1265 return true;1266}1267 1268void ento::registerObjCLoopChecker(CheckerManager &mgr) {1269 mgr.registerChecker<ObjCLoopChecker>();1270}1271 1272bool ento::shouldRegisterObjCLoopChecker(const CheckerManager &mgr) {1273 return true;1274}1275 1276void ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) {1277 mgr.registerChecker<ObjCNonNilReturnValueChecker>();1278}1279 1280bool ento::shouldRegisterObjCNonNilReturnValueChecker(const CheckerManager &mgr) {1281 return true;1282}1283