brintos

brintos / llvm-project-archived public Read only

0
0
Text · 43.4 KiB · e682c4e Raw
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