Path: blob/main/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
35269 views
//== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- C++ -*--1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file defines BasicObjCFoundationChecks, a class that encapsulates9// a set of simple checks to run on Objective-C code using Apple's Foundation10// classes.11//12//===----------------------------------------------------------------------===//1314#include "clang/AST/ASTContext.h"15#include "clang/AST/DeclObjC.h"16#include "clang/AST/Expr.h"17#include "clang/AST/ExprObjC.h"18#include "clang/AST/StmtObjC.h"19#include "clang/Analysis/DomainSpecific/CocoaConventions.h"20#include "clang/Analysis/SelectorExtras.h"21#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"22#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"23#include "clang/StaticAnalyzer/Core/Checker.h"24#include "clang/StaticAnalyzer/Core/CheckerManager.h"25#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"26#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"27#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"28#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"29#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"30#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"31#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"32#include "llvm/ADT/STLExtras.h"33#include "llvm/ADT/SmallString.h"34#include "llvm/ADT/StringMap.h"35#include "llvm/Support/raw_ostream.h"36#include <optional>3738using namespace clang;39using namespace ento;40using namespace llvm;4142namespace {43class APIMisuse : public BugType {44public:45APIMisuse(const CheckerBase *checker, const char *name)46: BugType(checker, name, categories::AppleAPIMisuse) {}47};48} // end anonymous namespace4950//===----------------------------------------------------------------------===//51// Utility functions.52//===----------------------------------------------------------------------===//5354static StringRef GetReceiverInterfaceName(const ObjCMethodCall &msg) {55if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())56return ID->getIdentifier()->getName();57return StringRef();58}5960enum FoundationClass {61FC_None,62FC_NSArray,63FC_NSDictionary,64FC_NSEnumerator,65FC_NSNull,66FC_NSOrderedSet,67FC_NSSet,68FC_NSString69};7071static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID,72bool IncludeSuperclasses = true) {73static llvm::StringMap<FoundationClass> Classes;74if (Classes.empty()) {75Classes["NSArray"] = FC_NSArray;76Classes["NSDictionary"] = FC_NSDictionary;77Classes["NSEnumerator"] = FC_NSEnumerator;78Classes["NSNull"] = FC_NSNull;79Classes["NSOrderedSet"] = FC_NSOrderedSet;80Classes["NSSet"] = FC_NSSet;81Classes["NSString"] = FC_NSString;82}8384// FIXME: Should we cache this at all?85FoundationClass result = Classes.lookup(ID->getIdentifier()->getName());86if (result == FC_None && IncludeSuperclasses)87if (const ObjCInterfaceDecl *Super = ID->getSuperClass())88return findKnownClass(Super);8990return result;91}9293//===----------------------------------------------------------------------===//94// NilArgChecker - Check for prohibited nil arguments to ObjC method calls.95//===----------------------------------------------------------------------===//9697namespace {98class NilArgChecker : public Checker<check::PreObjCMessage,99check::PostStmt<ObjCDictionaryLiteral>,100check::PostStmt<ObjCArrayLiteral>,101EventDispatcher<ImplicitNullDerefEvent>> {102mutable std::unique_ptr<APIMisuse> BT;103104mutable llvm::SmallDenseMap<Selector, unsigned, 16> StringSelectors;105mutable Selector ArrayWithObjectSel;106mutable Selector AddObjectSel;107mutable Selector InsertObjectAtIndexSel;108mutable Selector ReplaceObjectAtIndexWithObjectSel;109mutable Selector SetObjectAtIndexedSubscriptSel;110mutable Selector ArrayByAddingObjectSel;111mutable Selector DictionaryWithObjectForKeySel;112mutable Selector SetObjectForKeySel;113mutable Selector SetObjectForKeyedSubscriptSel;114mutable Selector RemoveObjectForKeySel;115116void warnIfNilExpr(const Expr *E, const char *Msg, CheckerContext &C) const;117118void warnIfNilArg(CheckerContext &C, const ObjCMethodCall &msg, unsigned Arg,119FoundationClass Class, bool CanBeSubscript = false) const;120121void generateBugReport(ExplodedNode *N, StringRef Msg, SourceRange Range,122const Expr *Expr, CheckerContext &C) const;123124public:125void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;126void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const;127void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const;128};129} // end anonymous namespace130131void NilArgChecker::warnIfNilExpr(const Expr *E,132const char *Msg,133CheckerContext &C) const {134auto Location = C.getSVal(E).getAs<Loc>();135if (!Location)136return;137138auto [NonNull, Null] = C.getState()->assume(*Location);139140// If it's known to be null.141if (!NonNull && Null) {142if (ExplodedNode *N = C.generateErrorNode()) {143generateBugReport(N, Msg, E->getSourceRange(), E, C);144return;145}146}147148// If it might be null, assume that it cannot after this operation.149if (Null) {150// One needs to make sure the pointer is non-null to be used here.151if (ExplodedNode *N = C.generateSink(Null, C.getPredecessor())) {152dispatchEvent({*Location, /*IsLoad=*/false, N, &C.getBugReporter(),153/*IsDirectDereference=*/false});154}155C.addTransition(NonNull);156}157}158159void NilArgChecker::warnIfNilArg(CheckerContext &C,160const ObjCMethodCall &msg,161unsigned int Arg,162FoundationClass Class,163bool CanBeSubscript) const {164// Check if the argument is nil.165ProgramStateRef State = C.getState();166if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue())167return;168169// 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.174if (ExplodedNode *N = C.generateErrorNode()) {175SmallString<128> sbuf;176llvm::raw_svector_ostream os(sbuf);177178if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) {179180if (Class == FC_NSArray) {181os << "Array element cannot be nil";182} else if (Class == FC_NSDictionary) {183if (Arg == 0) {184os << "Value stored into '";185os << GetReceiverInterfaceName(msg) << "' cannot be nil";186} else {187assert(Arg == 1);188os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil";189}190} else191llvm_unreachable("Missing foundation class for the subscript expr");192193} else {194if (Class == FC_NSDictionary) {195if (Arg == 0)196os << "Value argument ";197else {198assert(Arg == 1);199os << "Key argument ";200}201os << "to '";202msg.getSelector().print(os);203os << "' cannot be nil";204} else {205os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '";206msg.getSelector().print(os);207os << "' cannot be nil";208}209}210211generateBugReport(N, os.str(), msg.getArgSourceRange(Arg),212msg.getArgExpr(Arg), C);213}214}215216void NilArgChecker::generateBugReport(ExplodedNode *N,217StringRef Msg,218SourceRange Range,219const Expr *E,220CheckerContext &C) const {221if (!BT)222BT.reset(new APIMisuse(this, "nil argument"));223224auto R = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N);225R->addRange(Range);226bugreporter::trackExpressionValue(N, E, *R);227C.emitReport(std::move(R));228}229230void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,231CheckerContext &C) const {232const ObjCInterfaceDecl *ID = msg.getReceiverInterface();233if (!ID)234return;235236FoundationClass Class = findKnownClass(ID);237238static const unsigned InvalidArgIndex = UINT_MAX;239unsigned Arg = InvalidArgIndex;240bool CanBeSubscript = false;241242if (Class == FC_NSString) {243Selector S = msg.getSelector();244245if (S.isUnarySelector())246return;247248if (StringSelectors.empty()) {249ASTContext &Ctx = C.getASTContext();250Selector Sels[] = {251getKeywordSelector(Ctx, "caseInsensitiveCompare"),252getKeywordSelector(Ctx, "compare"),253getKeywordSelector(Ctx, "compare", "options"),254getKeywordSelector(Ctx, "compare", "options", "range"),255getKeywordSelector(Ctx, "compare", "options", "range", "locale"),256getKeywordSelector(Ctx, "componentsSeparatedByCharactersInSet"),257getKeywordSelector(Ctx, "initWithFormat"),258getKeywordSelector(Ctx, "localizedCaseInsensitiveCompare"),259getKeywordSelector(Ctx, "localizedCompare"),260getKeywordSelector(Ctx, "localizedStandardCompare"),261};262for (Selector KnownSel : Sels)263StringSelectors[KnownSel] = 0;264}265auto I = StringSelectors.find(S);266if (I == StringSelectors.end())267return;268Arg = I->second;269} else if (Class == FC_NSArray) {270Selector S = msg.getSelector();271272if (S.isUnarySelector())273return;274275if (ArrayWithObjectSel.isNull()) {276ASTContext &Ctx = C.getASTContext();277ArrayWithObjectSel = getKeywordSelector(Ctx, "arrayWithObject");278AddObjectSel = getKeywordSelector(Ctx, "addObject");279InsertObjectAtIndexSel =280getKeywordSelector(Ctx, "insertObject", "atIndex");281ReplaceObjectAtIndexWithObjectSel =282getKeywordSelector(Ctx, "replaceObjectAtIndex", "withObject");283SetObjectAtIndexedSubscriptSel =284getKeywordSelector(Ctx, "setObject", "atIndexedSubscript");285ArrayByAddingObjectSel = getKeywordSelector(Ctx, "arrayByAddingObject");286}287288if (S == ArrayWithObjectSel || S == AddObjectSel ||289S == InsertObjectAtIndexSel || S == ArrayByAddingObjectSel) {290Arg = 0;291} else if (S == SetObjectAtIndexedSubscriptSel) {292Arg = 0;293CanBeSubscript = true;294} else if (S == ReplaceObjectAtIndexWithObjectSel) {295Arg = 1;296}297} else if (Class == FC_NSDictionary) {298Selector S = msg.getSelector();299300if (S.isUnarySelector())301return;302303if (DictionaryWithObjectForKeySel.isNull()) {304ASTContext &Ctx = C.getASTContext();305DictionaryWithObjectForKeySel =306getKeywordSelector(Ctx, "dictionaryWithObject", "forKey");307SetObjectForKeySel = getKeywordSelector(Ctx, "setObject", "forKey");308SetObjectForKeyedSubscriptSel =309getKeywordSelector(Ctx, "setObject", "forKeyedSubscript");310RemoveObjectForKeySel = getKeywordSelector(Ctx, "removeObjectForKey");311}312313if (S == DictionaryWithObjectForKeySel || S == SetObjectForKeySel) {314Arg = 0;315warnIfNilArg(C, msg, /* Arg */1, Class);316} else if (S == SetObjectForKeyedSubscriptSel) {317CanBeSubscript = true;318Arg = 1;319} else if (S == RemoveObjectForKeySel) {320Arg = 0;321}322}323324// If argument is '0', report a warning.325if ((Arg != InvalidArgIndex))326warnIfNilArg(C, msg, Arg, Class, CanBeSubscript);327}328329void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL,330CheckerContext &C) const {331unsigned NumOfElements = AL->getNumElements();332for (unsigned i = 0; i < NumOfElements; ++i) {333warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C);334}335}336337void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,338CheckerContext &C) const {339unsigned NumOfElements = DL->getNumElements();340for (unsigned i = 0; i < NumOfElements; ++i) {341ObjCDictionaryElement Element = DL->getKeyValueElement(i);342warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C);343warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C);344}345}346347//===----------------------------------------------------------------------===//348// Checking for mismatched types passed to CFNumberCreate/CFNumberGetValue.349//===----------------------------------------------------------------------===//350351namespace {352class CFNumberChecker : public Checker< check::PreStmt<CallExpr> > {353mutable std::unique_ptr<APIMisuse> BT;354mutable IdentifierInfo *ICreate = nullptr, *IGetValue = nullptr;355public:356CFNumberChecker() = default;357358void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;359};360} // end anonymous namespace361362enum CFNumberType {363kCFNumberSInt8Type = 1,364kCFNumberSInt16Type = 2,365kCFNumberSInt32Type = 3,366kCFNumberSInt64Type = 4,367kCFNumberFloat32Type = 5,368kCFNumberFloat64Type = 6,369kCFNumberCharType = 7,370kCFNumberShortType = 8,371kCFNumberIntType = 9,372kCFNumberLongType = 10,373kCFNumberLongLongType = 11,374kCFNumberFloatType = 12,375kCFNumberDoubleType = 13,376kCFNumberCFIndexType = 14,377kCFNumberNSIntegerType = 15,378kCFNumberCGFloatType = 16379};380381static std::optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) {382static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 };383384if (i < kCFNumberCharType)385return FixedSize[i-1];386387QualType T;388389switch (i) {390case kCFNumberCharType: T = Ctx.CharTy; break;391case kCFNumberShortType: T = Ctx.ShortTy; break;392case kCFNumberIntType: T = Ctx.IntTy; break;393case kCFNumberLongType: T = Ctx.LongTy; break;394case kCFNumberLongLongType: T = Ctx.LongLongTy; break;395case kCFNumberFloatType: T = Ctx.FloatTy; break;396case kCFNumberDoubleType: T = Ctx.DoubleTy; break;397case kCFNumberCFIndexType:398case kCFNumberNSIntegerType:399case kCFNumberCGFloatType:400// FIXME: We need a way to map from names to Type*.401default:402return std::nullopt;403}404405return Ctx.getTypeSize(T);406}407408#if 0409static const char* GetCFNumberTypeStr(uint64_t i) {410static const char* Names[] = {411"kCFNumberSInt8Type",412"kCFNumberSInt16Type",413"kCFNumberSInt32Type",414"kCFNumberSInt64Type",415"kCFNumberFloat32Type",416"kCFNumberFloat64Type",417"kCFNumberCharType",418"kCFNumberShortType",419"kCFNumberIntType",420"kCFNumberLongType",421"kCFNumberLongLongType",422"kCFNumberFloatType",423"kCFNumberDoubleType",424"kCFNumberCFIndexType",425"kCFNumberNSIntegerType",426"kCFNumberCGFloatType"427};428429return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType";430}431#endif432433void CFNumberChecker::checkPreStmt(const CallExpr *CE,434CheckerContext &C) const {435ProgramStateRef state = C.getState();436const FunctionDecl *FD = C.getCalleeDecl(CE);437if (!FD)438return;439440ASTContext &Ctx = C.getASTContext();441if (!ICreate) {442ICreate = &Ctx.Idents.get("CFNumberCreate");443IGetValue = &Ctx.Idents.get("CFNumberGetValue");444}445if (!(FD->getIdentifier() == ICreate || FD->getIdentifier() == IGetValue) ||446CE->getNumArgs() != 3)447return;448449// Get the value of the "theType" argument.450SVal TheTypeVal = C.getSVal(CE->getArg(1));451452// FIXME: We really should allow ranges of valid theType values, and453// bifurcate the state appropriately.454std::optional<nonloc::ConcreteInt> V =455dyn_cast<nonloc::ConcreteInt>(TheTypeVal);456if (!V)457return;458459uint64_t NumberKind = V->getValue().getLimitedValue();460std::optional<uint64_t> OptCFNumberSize = GetCFNumberSize(Ctx, NumberKind);461462// FIXME: In some cases we can emit an error.463if (!OptCFNumberSize)464return;465466uint64_t CFNumberSize = *OptCFNumberSize;467468// Look at the value of the integer being passed by reference. Essentially469// we want to catch cases where the value passed in is not equal to the470// size of the type being created.471SVal TheValueExpr = C.getSVal(CE->getArg(2));472473// FIXME: Eventually we should handle arbitrary locations. We can do this474// by having an enhanced memory model that does low-level typing.475std::optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>();476if (!LV)477return;478479const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts());480if (!R)481return;482483QualType T = Ctx.getCanonicalType(R->getValueType());484485// FIXME: If the pointee isn't an integer type, should we flag a warning?486// People can do weird stuff with pointers.487488if (!T->isIntegralOrEnumerationType())489return;490491uint64_t PrimitiveTypeSize = Ctx.getTypeSize(T);492493if (PrimitiveTypeSize == CFNumberSize)494return;495496// FIXME: We can actually create an abstract "CFNumber" object that has497// the bits initialized to the provided values.498ExplodedNode *N = C.generateNonFatalErrorNode();499if (N) {500SmallString<128> sbuf;501llvm::raw_svector_ostream os(sbuf);502bool isCreate = (FD->getIdentifier() == ICreate);503504if (isCreate) {505os << (PrimitiveTypeSize == 8 ? "An " : "A ")506<< PrimitiveTypeSize << "-bit integer is used to initialize a "507<< "CFNumber object that represents "508<< (CFNumberSize == 8 ? "an " : "a ")509<< CFNumberSize << "-bit integer; ";510} else {511os << "A CFNumber object that represents "512<< (CFNumberSize == 8 ? "an " : "a ")513<< CFNumberSize << "-bit integer is used to initialize "514<< (PrimitiveTypeSize == 8 ? "an " : "a ")515<< PrimitiveTypeSize << "-bit integer; ";516}517518if (PrimitiveTypeSize < CFNumberSize)519os << (CFNumberSize - PrimitiveTypeSize)520<< " bits of the CFNumber value will "521<< (isCreate ? "be garbage." : "overwrite adjacent storage.");522else523os << (PrimitiveTypeSize - CFNumberSize)524<< " bits of the integer value will be "525<< (isCreate ? "lost." : "garbage.");526527if (!BT)528BT.reset(new APIMisuse(this, "Bad use of CFNumber APIs"));529530auto report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N);531report->addRange(CE->getArg(2)->getSourceRange());532C.emitReport(std::move(report));533}534}535536//===----------------------------------------------------------------------===//537// CFRetain/CFRelease/CFMakeCollectable/CFAutorelease checking for null arguments.538//===----------------------------------------------------------------------===//539540namespace {541class CFRetainReleaseChecker : public Checker<check::PreCall> {542mutable APIMisuse BT{this, "null passed to CF memory management function"};543const CallDescriptionSet ModelledCalls = {544{CDM::CLibrary, {"CFRetain"}, 1},545{CDM::CLibrary, {"CFRelease"}, 1},546{CDM::CLibrary, {"CFMakeCollectable"}, 1},547{CDM::CLibrary, {"CFAutorelease"}, 1},548};549550public:551void checkPreCall(const CallEvent &Call, CheckerContext &C) const;552};553} // end anonymous namespace554555void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call,556CheckerContext &C) const {557// Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease.558if (!ModelledCalls.contains(Call))559return;560561// Get the argument's value.562SVal ArgVal = Call.getArgSVal(0);563std::optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>();564if (!DefArgVal)565return;566567// Is it null?568ProgramStateRef state = C.getState();569ProgramStateRef stateNonNull, stateNull;570std::tie(stateNonNull, stateNull) = state->assume(*DefArgVal);571572if (!stateNonNull) {573ExplodedNode *N = C.generateErrorNode(stateNull);574if (!N)575return;576577SmallString<64> Str;578raw_svector_ostream OS(Str);579OS << "Null pointer argument in call to "580<< cast<FunctionDecl>(Call.getDecl())->getName();581582auto report = std::make_unique<PathSensitiveBugReport>(BT, OS.str(), N);583report->addRange(Call.getArgSourceRange(0));584bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report);585C.emitReport(std::move(report));586return;587}588589// From here on, we know the argument is non-null.590C.addTransition(stateNonNull);591}592593//===----------------------------------------------------------------------===//594// Check for sending 'retain', 'release', or 'autorelease' directly to a Class.595//===----------------------------------------------------------------------===//596597namespace {598class ClassReleaseChecker : public Checker<check::PreObjCMessage> {599mutable Selector releaseS;600mutable Selector retainS;601mutable Selector autoreleaseS;602mutable Selector drainS;603mutable std::unique_ptr<BugType> BT;604605public:606void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;607};608} // end anonymous namespace609610void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,611CheckerContext &C) const {612if (!BT) {613BT.reset(new APIMisuse(614this, "message incorrectly sent to class instead of class instance"));615616ASTContext &Ctx = C.getASTContext();617releaseS = GetNullarySelector("release", Ctx);618retainS = GetNullarySelector("retain", Ctx);619autoreleaseS = GetNullarySelector("autorelease", Ctx);620drainS = GetNullarySelector("drain", Ctx);621}622623if (msg.isInstanceMessage())624return;625const ObjCInterfaceDecl *Class = msg.getReceiverInterface();626assert(Class);627628Selector S = msg.getSelector();629if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))630return;631632if (ExplodedNode *N = C.generateNonFatalErrorNode()) {633SmallString<200> buf;634llvm::raw_svector_ostream os(buf);635636os << "The '";637S.print(os);638os << "' message should be sent to instances "639"of class '" << Class->getName()640<< "' and not the class directly";641642auto report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N);643report->addRange(msg.getSourceRange());644C.emitReport(std::move(report));645}646}647648//===----------------------------------------------------------------------===//649// Check for passing non-Objective-C types to variadic methods that expect650// only Objective-C types.651//===----------------------------------------------------------------------===//652653namespace {654class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> {655mutable Selector arrayWithObjectsS;656mutable Selector dictionaryWithObjectsAndKeysS;657mutable Selector setWithObjectsS;658mutable Selector orderedSetWithObjectsS;659mutable Selector initWithObjectsS;660mutable Selector initWithObjectsAndKeysS;661mutable std::unique_ptr<BugType> BT;662663bool isVariadicMessage(const ObjCMethodCall &msg) const;664665public:666void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;667};668} // end anonymous namespace669670/// isVariadicMessage - Returns whether the given message is a variadic message,671/// where all arguments must be Objective-C types.672bool673VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const {674const ObjCMethodDecl *MD = msg.getDecl();675676if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))677return false;678679Selector S = msg.getSelector();680681if (msg.isInstanceMessage()) {682// FIXME: Ideally we'd look at the receiver interface here, but that's not683// useful for init, because alloc returns 'id'. In theory, this could lead684// to false positives, for example if there existed a class that had an685// initWithObjects: implementation that does accept non-Objective-C pointer686// types, but the chance of that happening is pretty small compared to the687// gains that this analysis gives.688const ObjCInterfaceDecl *Class = MD->getClassInterface();689690switch (findKnownClass(Class)) {691case FC_NSArray:692case FC_NSOrderedSet:693case FC_NSSet:694return S == initWithObjectsS;695case FC_NSDictionary:696return S == initWithObjectsAndKeysS;697default:698return false;699}700} else {701const ObjCInterfaceDecl *Class = msg.getReceiverInterface();702703switch (findKnownClass(Class)) {704case FC_NSArray:705return S == arrayWithObjectsS;706case FC_NSOrderedSet:707return S == orderedSetWithObjectsS;708case FC_NSSet:709return S == setWithObjectsS;710case FC_NSDictionary:711return S == dictionaryWithObjectsAndKeysS;712default:713return false;714}715}716}717718void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg,719CheckerContext &C) const {720if (!BT) {721BT.reset(new APIMisuse(this,722"Arguments passed to variadic method aren't all "723"Objective-C pointer types"));724725ASTContext &Ctx = C.getASTContext();726arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);727dictionaryWithObjectsAndKeysS =728GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);729setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);730orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx);731732initWithObjectsS = GetUnarySelector("initWithObjects", Ctx);733initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx);734}735736if (!isVariadicMessage(msg))737return;738739// We are not interested in the selector arguments since they have740// well-defined types, so the compiler will issue a warning for them.741unsigned variadicArgsBegin = msg.getSelector().getNumArgs();742743// We're not interested in the last argument since it has to be nil or the744// compiler would have issued a warning for it elsewhere.745unsigned variadicArgsEnd = msg.getNumArgs() - 1;746747if (variadicArgsEnd <= variadicArgsBegin)748return;749750// Verify that all arguments have Objective-C types.751std::optional<ExplodedNode *> errorNode;752753for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {754QualType ArgTy = msg.getArgExpr(I)->getType();755if (ArgTy->isObjCObjectPointerType())756continue;757758// Block pointers are treaded as Objective-C pointers.759if (ArgTy->isBlockPointerType())760continue;761762// Ignore pointer constants.763if (isa<loc::ConcreteInt>(msg.getArgSVal(I)))764continue;765766// Ignore pointer types annotated with 'NSObject' attribute.767if (C.getASTContext().isObjCNSObjectType(ArgTy))768continue;769770// Ignore CF references, which can be toll-free bridged.771if (coreFoundation::isCFObjectRef(ArgTy))772continue;773774// Generate only one error node to use for all bug reports.775if (!errorNode)776errorNode = C.generateNonFatalErrorNode();777778if (!*errorNode)779continue;780781SmallString<128> sbuf;782llvm::raw_svector_ostream os(sbuf);783784StringRef TypeName = GetReceiverInterfaceName(msg);785if (!TypeName.empty())786os << "Argument to '" << TypeName << "' method '";787else788os << "Argument to method '";789790msg.getSelector().print(os);791os << "' should be an Objective-C pointer type, not '";792ArgTy.print(os, C.getLangOpts());793os << "'";794795auto R =796std::make_unique<PathSensitiveBugReport>(*BT, os.str(), *errorNode);797R->addRange(msg.getArgSourceRange(I));798C.emitReport(std::move(R));799}800}801802//===----------------------------------------------------------------------===//803// Improves the modeling of loops over Cocoa collections.804//===----------------------------------------------------------------------===//805806// The map from container symbol to the container count symbol.807// We currently will remember the last container count symbol encountered.808REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef)809REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool)810811namespace {812class ObjCLoopChecker813: public Checker<check::PostStmt<ObjCForCollectionStmt>,814check::PostObjCMessage,815check::DeadSymbols,816check::PointerEscape > {817mutable IdentifierInfo *CountSelectorII = nullptr;818819bool isCollectionCountMethod(const ObjCMethodCall &M,820CheckerContext &C) const;821822public:823ObjCLoopChecker() = default;824void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const;825void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;826void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;827ProgramStateRef checkPointerEscape(ProgramStateRef State,828const InvalidatedSymbols &Escaped,829const CallEvent *Call,830PointerEscapeKind Kind) const;831};832} // end anonymous namespace833834static bool isKnownNonNilCollectionType(QualType T) {835const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();836if (!PT)837return false;838839const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();840if (!ID)841return false;842843switch (findKnownClass(ID)) {844case FC_NSArray:845case FC_NSDictionary:846case FC_NSEnumerator:847case FC_NSOrderedSet:848case FC_NSSet:849return true;850default:851return false;852}853}854855/// Assumes that the collection is non-nil.856///857/// If the collection is known to be nil, returns NULL to indicate an infeasible858/// path.859static ProgramStateRef checkCollectionNonNil(CheckerContext &C,860ProgramStateRef State,861const ObjCForCollectionStmt *FCS) {862if (!State)863return nullptr;864865SVal CollectionVal = C.getSVal(FCS->getCollection());866std::optional<DefinedSVal> KnownCollection =867CollectionVal.getAs<DefinedSVal>();868if (!KnownCollection)869return State;870871ProgramStateRef StNonNil, StNil;872std::tie(StNonNil, StNil) = State->assume(*KnownCollection);873if (StNil && !StNonNil) {874// The collection is nil. This path is infeasible.875return nullptr;876}877878return StNonNil;879}880881/// Assumes that the collection elements are non-nil.882///883/// This only applies if the collection is one of those known not to contain884/// nil values.885static ProgramStateRef checkElementNonNil(CheckerContext &C,886ProgramStateRef State,887const ObjCForCollectionStmt *FCS) {888if (!State)889return nullptr;890891// See if the collection is one where we /know/ the elements are non-nil.892if (!isKnownNonNilCollectionType(FCS->getCollection()->getType()))893return State;894895const LocationContext *LCtx = C.getLocationContext();896const Stmt *Element = FCS->getElement();897898// FIXME: Copied from ExprEngineObjC.899std::optional<Loc> ElementLoc;900if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) {901const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl());902assert(ElemDecl->getInit() == nullptr);903ElementLoc = State->getLValue(ElemDecl, LCtx);904} else {905ElementLoc = State->getSVal(Element, LCtx).getAs<Loc>();906}907908if (!ElementLoc)909return State;910911// Go ahead and assume the value is non-nil.912SVal Val = State->getSVal(*ElementLoc);913return State->assume(cast<DefinedOrUnknownSVal>(Val), true);914}915916/// Returns NULL state if the collection is known to contain elements917/// (or is known not to contain elements if the Assumption parameter is false.)918static ProgramStateRef919assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,920SymbolRef CollectionS, bool Assumption) {921if (!State || !CollectionS)922return State;923924const SymbolRef *CountS = State->get<ContainerCountMap>(CollectionS);925if (!CountS) {926const bool *KnownNonEmpty = State->get<ContainerNonEmptyMap>(CollectionS);927if (!KnownNonEmpty)928return State->set<ContainerNonEmptyMap>(CollectionS, Assumption);929return (Assumption == *KnownNonEmpty) ? State : nullptr;930}931932SValBuilder &SvalBuilder = C.getSValBuilder();933SVal CountGreaterThanZeroVal =934SvalBuilder.evalBinOp(State, BO_GT,935nonloc::SymbolVal(*CountS),936SvalBuilder.makeIntVal(0, (*CountS)->getType()),937SvalBuilder.getConditionType());938std::optional<DefinedSVal> CountGreaterThanZero =939CountGreaterThanZeroVal.getAs<DefinedSVal>();940if (!CountGreaterThanZero) {941// The SValBuilder cannot construct a valid SVal for this condition.942// This means we cannot properly reason about it.943return State;944}945946return State->assume(*CountGreaterThanZero, Assumption);947}948949static ProgramStateRef950assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,951const ObjCForCollectionStmt *FCS,952bool Assumption) {953if (!State)954return nullptr;955956SymbolRef CollectionS = C.getSVal(FCS->getCollection()).getAsSymbol();957return assumeCollectionNonEmpty(C, State, CollectionS, Assumption);958}959960/// If the fist block edge is a back edge, we are reentering the loop.961static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N,962const ObjCForCollectionStmt *FCS) {963if (!N)964return false;965966ProgramPoint P = N->getLocation();967if (std::optional<BlockEdge> BE = P.getAs<BlockEdge>()) {968return BE->getSrc()->getLoopTarget() == FCS;969}970971// Keep looking for a block edge.972for (const ExplodedNode *N : N->preds()) {973if (alreadyExecutedAtLeastOneLoopIteration(N, FCS))974return true;975}976977return false;978}979980void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS,981CheckerContext &C) const {982ProgramStateRef State = C.getState();983984// Check if this is the branch for the end of the loop.985if (!ExprEngine::hasMoreIteration(State, FCS, C.getLocationContext())) {986if (!alreadyExecutedAtLeastOneLoopIteration(C.getPredecessor(), FCS))987State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/false);988989// Otherwise, this is a branch that goes through the loop body.990} else {991State = checkCollectionNonNil(C, State, FCS);992State = checkElementNonNil(C, State, FCS);993State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true);994}995996if (!State)997C.generateSink(C.getState(), C.getPredecessor());998else if (State != C.getState())999C.addTransition(State);1000}10011002bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M,1003CheckerContext &C) const {1004Selector S = M.getSelector();1005// Initialize the identifiers on first use.1006if (!CountSelectorII)1007CountSelectorII = &C.getASTContext().Idents.get("count");10081009// If the method returns collection count, record the value.1010return S.isUnarySelector() &&1011(S.getIdentifierInfoForSlot(0) == CountSelectorII);1012}10131014void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M,1015CheckerContext &C) const {1016if (!M.isInstanceMessage())1017return;10181019const ObjCInterfaceDecl *ClassID = M.getReceiverInterface();1020if (!ClassID)1021return;10221023FoundationClass Class = findKnownClass(ClassID);1024if (Class != FC_NSDictionary &&1025Class != FC_NSArray &&1026Class != FC_NSSet &&1027Class != FC_NSOrderedSet)1028return;10291030SymbolRef ContainerS = M.getReceiverSVal().getAsSymbol();1031if (!ContainerS)1032return;10331034// If we are processing a call to "count", get the symbolic value returned by1035// a call to "count" and add it to the map.1036if (!isCollectionCountMethod(M, C))1037return;10381039const Expr *MsgExpr = M.getOriginExpr();1040SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol();1041if (CountS) {1042ProgramStateRef State = C.getState();10431044C.getSymbolManager().addSymbolDependency(ContainerS, CountS);1045State = State->set<ContainerCountMap>(ContainerS, CountS);10461047if (const bool *NonEmpty = State->get<ContainerNonEmptyMap>(ContainerS)) {1048State = State->remove<ContainerNonEmptyMap>(ContainerS);1049State = assumeCollectionNonEmpty(C, State, ContainerS, *NonEmpty);1050}10511052C.addTransition(State);1053}1054}10551056static SymbolRef getMethodReceiverIfKnownImmutable(const CallEvent *Call) {1057const ObjCMethodCall *Message = dyn_cast_or_null<ObjCMethodCall>(Call);1058if (!Message)1059return nullptr;10601061const ObjCMethodDecl *MD = Message->getDecl();1062if (!MD)1063return nullptr;10641065const ObjCInterfaceDecl *StaticClass;1066if (isa<ObjCProtocolDecl>(MD->getDeclContext())) {1067// We can't find out where the method was declared without doing more work.1068// Instead, see if the receiver is statically typed as a known immutable1069// collection.1070StaticClass = Message->getOriginExpr()->getReceiverInterface();1071} else {1072StaticClass = MD->getClassInterface();1073}10741075if (!StaticClass)1076return nullptr;10771078switch (findKnownClass(StaticClass, /*IncludeSuper=*/false)) {1079case FC_None:1080return nullptr;1081case FC_NSArray:1082case FC_NSDictionary:1083case FC_NSEnumerator:1084case FC_NSNull:1085case FC_NSOrderedSet:1086case FC_NSSet:1087case FC_NSString:1088break;1089}10901091return Message->getReceiverSVal().getAsSymbol();1092}10931094ProgramStateRef1095ObjCLoopChecker::checkPointerEscape(ProgramStateRef State,1096const InvalidatedSymbols &Escaped,1097const CallEvent *Call,1098PointerEscapeKind Kind) const {1099SymbolRef ImmutableReceiver = getMethodReceiverIfKnownImmutable(Call);11001101// Remove the invalidated symbols from the collection count map.1102for (SymbolRef Sym : Escaped) {1103// Don't invalidate this symbol's count if we know the method being called1104// is declared on an immutable class. This isn't completely correct if the1105// receiver is also passed as an argument, but in most uses of NSArray,1106// NSDictionary, etc. this isn't likely to happen in a dangerous way.1107if (Sym == ImmutableReceiver)1108continue;11091110// The symbol escaped. Pessimistically, assume that the count could have1111// changed.1112State = State->remove<ContainerCountMap>(Sym);1113State = State->remove<ContainerNonEmptyMap>(Sym);1114}1115return State;1116}11171118void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper,1119CheckerContext &C) const {1120ProgramStateRef State = C.getState();11211122// Remove the dead symbols from the collection count map.1123ContainerCountMapTy Tracked = State->get<ContainerCountMap>();1124for (SymbolRef Sym : llvm::make_first_range(Tracked)) {1125if (SymReaper.isDead(Sym)) {1126State = State->remove<ContainerCountMap>(Sym);1127State = State->remove<ContainerNonEmptyMap>(Sym);1128}1129}11301131C.addTransition(State);1132}11331134namespace {1135/// \class ObjCNonNilReturnValueChecker1136/// The checker restricts the return values of APIs known to1137/// never (or almost never) return 'nil'.1138class ObjCNonNilReturnValueChecker1139: public Checker<check::PostObjCMessage,1140check::PostStmt<ObjCArrayLiteral>,1141check::PostStmt<ObjCDictionaryLiteral>,1142check::PostStmt<ObjCBoxedExpr> > {1143mutable bool Initialized = false;1144mutable Selector ObjectAtIndex;1145mutable Selector ObjectAtIndexedSubscript;1146mutable Selector NullSelector;11471148public:1149ObjCNonNilReturnValueChecker() = default;11501151ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr,1152ProgramStateRef State,1153CheckerContext &C) const;1154void assumeExprIsNonNull(const Expr *E, CheckerContext &C) const {1155C.addTransition(assumeExprIsNonNull(E, C.getState(), C));1156}11571158void checkPostStmt(const ObjCArrayLiteral *E, CheckerContext &C) const {1159assumeExprIsNonNull(E, C);1160}1161void checkPostStmt(const ObjCDictionaryLiteral *E, CheckerContext &C) const {1162assumeExprIsNonNull(E, C);1163}1164void checkPostStmt(const ObjCBoxedExpr *E, CheckerContext &C) const {1165assumeExprIsNonNull(E, C);1166}11671168void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;1169};1170} // end anonymous namespace11711172ProgramStateRef1173ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr,1174ProgramStateRef State,1175CheckerContext &C) const {1176SVal Val = C.getSVal(NonNullExpr);1177if (std::optional<DefinedOrUnknownSVal> DV =1178Val.getAs<DefinedOrUnknownSVal>())1179return State->assume(*DV, true);1180return State;1181}11821183void ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M,1184CheckerContext &C)1185const {1186ProgramStateRef State = C.getState();11871188if (!Initialized) {1189ASTContext &Ctx = C.getASTContext();1190ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx);1191ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx);1192NullSelector = GetNullarySelector("null", Ctx);1193}11941195// Check the receiver type.1196if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) {11971198// Assume that object returned from '[self init]' or '[super init]' is not1199// 'nil' if we are processing an inlined function/method.1200//1201// A defensive callee will (and should) check if the object returned by1202// '[super init]' is 'nil' before doing it's own initialization. However,1203// since 'nil' is rarely returned in practice, we should not warn when the1204// caller to the defensive constructor uses the object in contexts where1205// 'nil' is not accepted.1206if (!C.inTopFrame() && M.getDecl() &&1207M.getDecl()->getMethodFamily() == OMF_init &&1208M.isReceiverSelfOrSuper()) {1209State = assumeExprIsNonNull(M.getOriginExpr(), State, C);1210}12111212FoundationClass Cl = findKnownClass(Interface);12131214// Objects returned from1215// [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript]1216// are never 'nil'.1217if (Cl == FC_NSArray || Cl == FC_NSOrderedSet) {1218Selector Sel = M.getSelector();1219if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript) {1220// Go ahead and assume the value is non-nil.1221State = assumeExprIsNonNull(M.getOriginExpr(), State, C);1222}1223}12241225// Objects returned from [NSNull null] are not nil.1226if (Cl == FC_NSNull) {1227if (M.getSelector() == NullSelector) {1228// Go ahead and assume the value is non-nil.1229State = assumeExprIsNonNull(M.getOriginExpr(), State, C);1230}1231}1232}1233C.addTransition(State);1234}12351236//===----------------------------------------------------------------------===//1237// Check registration.1238//===----------------------------------------------------------------------===//12391240void ento::registerNilArgChecker(CheckerManager &mgr) {1241mgr.registerChecker<NilArgChecker>();1242}12431244bool ento::shouldRegisterNilArgChecker(const CheckerManager &mgr) {1245return true;1246}12471248void ento::registerCFNumberChecker(CheckerManager &mgr) {1249mgr.registerChecker<CFNumberChecker>();1250}12511252bool ento::shouldRegisterCFNumberChecker(const CheckerManager &mgr) {1253return true;1254}12551256void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {1257mgr.registerChecker<CFRetainReleaseChecker>();1258}12591260bool ento::shouldRegisterCFRetainReleaseChecker(const CheckerManager &mgr) {1261return true;1262}12631264void ento::registerClassReleaseChecker(CheckerManager &mgr) {1265mgr.registerChecker<ClassReleaseChecker>();1266}12671268bool ento::shouldRegisterClassReleaseChecker(const CheckerManager &mgr) {1269return true;1270}12711272void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {1273mgr.registerChecker<VariadicMethodTypeChecker>();1274}12751276bool ento::shouldRegisterVariadicMethodTypeChecker(const CheckerManager &mgr) {1277return true;1278}12791280void ento::registerObjCLoopChecker(CheckerManager &mgr) {1281mgr.registerChecker<ObjCLoopChecker>();1282}12831284bool ento::shouldRegisterObjCLoopChecker(const CheckerManager &mgr) {1285return true;1286}12871288void ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) {1289mgr.registerChecker<ObjCNonNilReturnValueChecker>();1290}12911292bool ento::shouldRegisterObjCNonNilReturnValueChecker(const CheckerManager &mgr) {1293return true;1294}129512961297