llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp

1296 lines
44 KiB
C++
Raw Normal View History

//== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- C++ -*--
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines BasicObjCFoundationChecks, a class that encapsulates
// a set of simple checks to run on Objective-C code using Apple's Foundation
// classes.
//
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/StmtObjC.h"
#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
#include "clang/Analysis/SelectorExtras.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace ento;
using namespace llvm;
namespace {
class APIMisuse : public BugType {
public:
APIMisuse(const CheckerBase *checker, const char *name)
: BugType(checker, name, "API Misuse (Apple)") {}
};
} // end anonymous namespace
//===----------------------------------------------------------------------===//
// Utility functions.
//===----------------------------------------------------------------------===//
static StringRef GetReceiverInterfaceName(const ObjCMethodCall &msg) {
if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())
return ID->getIdentifier()->getName();
return StringRef();
}
enum FoundationClass {
FC_None,
FC_NSArray,
FC_NSDictionary,
FC_NSEnumerator,
FC_NSNull,
FC_NSOrderedSet,
FC_NSSet,
FC_NSString
};
static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID,
bool IncludeSuperclasses = true) {
static llvm::StringMap<FoundationClass> Classes;
if (Classes.empty()) {
Classes["NSArray"] = FC_NSArray;
Classes["NSDictionary"] = FC_NSDictionary;
Classes["NSEnumerator"] = FC_NSEnumerator;
Classes["NSNull"] = FC_NSNull;
Classes["NSOrderedSet"] = FC_NSOrderedSet;
Classes["NSSet"] = FC_NSSet;
Classes["NSString"] = FC_NSString;
}
// FIXME: Should we cache this at all?
FoundationClass result = Classes.lookup(ID->getIdentifier()->getName());
if (result == FC_None && IncludeSuperclasses)
if (const ObjCInterfaceDecl *Super = ID->getSuperClass())
return findKnownClass(Super);
return result;
}
//===----------------------------------------------------------------------===//
// NilArgChecker - Check for prohibited nil arguments to ObjC method calls.
//===----------------------------------------------------------------------===//
namespace {
class NilArgChecker : public Checker<check::PreObjCMessage,
check::PostStmt<ObjCDictionaryLiteral>,
check::PostStmt<ObjCArrayLiteral> > {
mutable std::unique_ptr<APIMisuse> BT;
mutable llvm::SmallDenseMap<Selector, unsigned, 16> StringSelectors;
mutable Selector ArrayWithObjectSel;
mutable Selector AddObjectSel;
mutable Selector InsertObjectAtIndexSel;
mutable Selector ReplaceObjectAtIndexWithObjectSel;
mutable Selector SetObjectAtIndexedSubscriptSel;
mutable Selector ArrayByAddingObjectSel;
mutable Selector DictionaryWithObjectForKeySel;
mutable Selector SetObjectForKeySel;
mutable Selector SetObjectForKeyedSubscriptSel;
mutable Selector RemoveObjectForKeySel;
void warnIfNilExpr(const Expr *E,
const char *Msg,
CheckerContext &C) const;
void warnIfNilArg(CheckerContext &C,
const ObjCMethodCall &msg, unsigned Arg,
FoundationClass Class,
bool CanBeSubscript = false) const;
void generateBugReport(ExplodedNode *N,
StringRef Msg,
SourceRange Range,
const Expr *Expr,
CheckerContext &C) const;
public:
void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
void checkPostStmt(const ObjCDictionaryLiteral *DL,
CheckerContext &C) const;
void checkPostStmt(const ObjCArrayLiteral *AL,
CheckerContext &C) const;
};
} // end anonymous namespace
void NilArgChecker::warnIfNilExpr(const Expr *E,
const char *Msg,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
if (State->isNull(C.getSVal(E)).isConstrainedTrue()) {
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
if (ExplodedNode *N = C.generateErrorNode()) {
generateBugReport(N, Msg, E->getSourceRange(), E, C);
}
}
}
void NilArgChecker::warnIfNilArg(CheckerContext &C,
const ObjCMethodCall &msg,
unsigned int Arg,
FoundationClass Class,
bool CanBeSubscript) const {
// Check if the argument is nil.
ProgramStateRef State = C.getState();
if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue())
return;
// NOTE: We cannot throw non-fatal errors from warnIfNilExpr,
// because it's called multiple times from some callers, so it'd cause
// an unwanted state split if two or more non-fatal errors are thrown
// within the same checker callback. For now we don't want to, but
// it'll need to be fixed if we ever want to.
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
if (ExplodedNode *N = C.generateErrorNode()) {
SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) {
if (Class == FC_NSArray) {
os << "Array element cannot be nil";
} else if (Class == FC_NSDictionary) {
if (Arg == 0) {
os << "Value stored into '";
os << GetReceiverInterfaceName(msg) << "' cannot be nil";
} else {
assert(Arg == 1);
os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil";
}
} else
llvm_unreachable("Missing foundation class for the subscript expr");
} else {
if (Class == FC_NSDictionary) {
if (Arg == 0)
os << "Value argument ";
else {
assert(Arg == 1);
os << "Key argument ";
}
os << "to '";
msg.getSelector().print(os);
os << "' cannot be nil";
} else {
os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '";
msg.getSelector().print(os);
os << "' cannot be nil";
}
}
generateBugReport(N, os.str(), msg.getArgSourceRange(Arg),
msg.getArgExpr(Arg), C);
}
}
void NilArgChecker::generateBugReport(ExplodedNode *N,
StringRef Msg,
SourceRange Range,
const Expr *E,
CheckerContext &C) const {
if (!BT)
BT.reset(new APIMisuse(this, "nil argument"));
auto R = llvm::make_unique<BugReport>(*BT, Msg, N);
R->addRange(Range);
bugreporter::trackExpressionValue(N, E, *R);
C.emitReport(std::move(R));
}
void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
CheckerContext &C) const {
const ObjCInterfaceDecl *ID = msg.getReceiverInterface();
if (!ID)
return;
FoundationClass Class = findKnownClass(ID);
static const unsigned InvalidArgIndex = UINT_MAX;
unsigned Arg = InvalidArgIndex;
bool CanBeSubscript = false;
if (Class == FC_NSString) {
Selector S = msg.getSelector();
if (S.isUnarySelector())
return;
if (StringSelectors.empty()) {
ASTContext &Ctx = C.getASTContext();
Selector Sels[] = {
getKeywordSelector(Ctx, "caseInsensitiveCompare"),
getKeywordSelector(Ctx, "compare"),
getKeywordSelector(Ctx, "compare", "options"),
getKeywordSelector(Ctx, "compare", "options", "range"),
getKeywordSelector(Ctx, "compare", "options", "range", "locale"),
getKeywordSelector(Ctx, "componentsSeparatedByCharactersInSet"),
getKeywordSelector(Ctx, "initWithFormat"),
getKeywordSelector(Ctx, "localizedCaseInsensitiveCompare"),
getKeywordSelector(Ctx, "localizedCompare"),
getKeywordSelector(Ctx, "localizedStandardCompare"),
};
for (Selector KnownSel : Sels)
StringSelectors[KnownSel] = 0;
}
auto I = StringSelectors.find(S);
if (I == StringSelectors.end())
return;
Arg = I->second;
} else if (Class == FC_NSArray) {
Selector S = msg.getSelector();
if (S.isUnarySelector())
return;
if (ArrayWithObjectSel.isNull()) {
ASTContext &Ctx = C.getASTContext();
ArrayWithObjectSel = getKeywordSelector(Ctx, "arrayWithObject");
AddObjectSel = getKeywordSelector(Ctx, "addObject");
InsertObjectAtIndexSel =
getKeywordSelector(Ctx, "insertObject", "atIndex");
ReplaceObjectAtIndexWithObjectSel =
getKeywordSelector(Ctx, "replaceObjectAtIndex", "withObject");
SetObjectAtIndexedSubscriptSel =
getKeywordSelector(Ctx, "setObject", "atIndexedSubscript");
ArrayByAddingObjectSel = getKeywordSelector(Ctx, "arrayByAddingObject");
}
if (S == ArrayWithObjectSel || S == AddObjectSel ||
S == InsertObjectAtIndexSel || S == ArrayByAddingObjectSel) {
Arg = 0;
} else if (S == SetObjectAtIndexedSubscriptSel) {
Arg = 0;
CanBeSubscript = true;
} else if (S == ReplaceObjectAtIndexWithObjectSel) {
Arg = 1;
}
} else if (Class == FC_NSDictionary) {
Selector S = msg.getSelector();
if (S.isUnarySelector())
return;
if (DictionaryWithObjectForKeySel.isNull()) {
ASTContext &Ctx = C.getASTContext();
DictionaryWithObjectForKeySel =
getKeywordSelector(Ctx, "dictionaryWithObject", "forKey");
SetObjectForKeySel = getKeywordSelector(Ctx, "setObject", "forKey");
SetObjectForKeyedSubscriptSel =
getKeywordSelector(Ctx, "setObject", "forKeyedSubscript");
RemoveObjectForKeySel = getKeywordSelector(Ctx, "removeObjectForKey");
}
if (S == DictionaryWithObjectForKeySel || S == SetObjectForKeySel) {
Arg = 0;
warnIfNilArg(C, msg, /* Arg */1, Class);
} else if (S == SetObjectForKeyedSubscriptSel) {
CanBeSubscript = true;
Arg = 1;
} else if (S == RemoveObjectForKeySel) {
Arg = 0;
}
}
// If argument is '0', report a warning.
if ((Arg != InvalidArgIndex))
warnIfNilArg(C, msg, Arg, Class, CanBeSubscript);
}
void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL,
CheckerContext &C) const {
unsigned NumOfElements = AL->getNumElements();
for (unsigned i = 0; i < NumOfElements; ++i) {
warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C);
}
}
void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
CheckerContext &C) const {
unsigned NumOfElements = DL->getNumElements();
for (unsigned i = 0; i < NumOfElements; ++i) {
ObjCDictionaryElement Element = DL->getKeyValueElement(i);
warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C);
warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C);
}
}
//===----------------------------------------------------------------------===//
// Checking for mismatched types passed to CFNumberCreate/CFNumberGetValue.
//===----------------------------------------------------------------------===//
namespace {
class CFNumberChecker : public Checker< check::PreStmt<CallExpr> > {
mutable std::unique_ptr<APIMisuse> BT;
mutable IdentifierInfo *ICreate, *IGetValue;
public:
CFNumberChecker() : ICreate(nullptr), IGetValue(nullptr) {}
void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
private:
void EmitError(const TypedRegion* R, const Expr *Ex,
uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind);
};
} // end anonymous namespace
enum CFNumberType {
kCFNumberSInt8Type = 1,
kCFNumberSInt16Type = 2,
kCFNumberSInt32Type = 3,
kCFNumberSInt64Type = 4,
kCFNumberFloat32Type = 5,
kCFNumberFloat64Type = 6,
kCFNumberCharType = 7,
kCFNumberShortType = 8,
kCFNumberIntType = 9,
kCFNumberLongType = 10,
kCFNumberLongLongType = 11,
kCFNumberFloatType = 12,
kCFNumberDoubleType = 13,
kCFNumberCFIndexType = 14,
kCFNumberNSIntegerType = 15,
kCFNumberCGFloatType = 16
};
static Optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) {
static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 };
if (i < kCFNumberCharType)
return FixedSize[i-1];
QualType T;
switch (i) {
case kCFNumberCharType: T = Ctx.CharTy; break;
case kCFNumberShortType: T = Ctx.ShortTy; break;
case kCFNumberIntType: T = Ctx.IntTy; break;
case kCFNumberLongType: T = Ctx.LongTy; break;
case kCFNumberLongLongType: T = Ctx.LongLongTy; break;
case kCFNumberFloatType: T = Ctx.FloatTy; break;
case kCFNumberDoubleType: T = Ctx.DoubleTy; break;
case kCFNumberCFIndexType:
case kCFNumberNSIntegerType:
case kCFNumberCGFloatType:
// FIXME: We need a way to map from names to Type*.
default:
return None;
}
return Ctx.getTypeSize(T);
}
#if 0
static const char* GetCFNumberTypeStr(uint64_t i) {
static const char* Names[] = {
"kCFNumberSInt8Type",
"kCFNumberSInt16Type",
"kCFNumberSInt32Type",
"kCFNumberSInt64Type",
"kCFNumberFloat32Type",
"kCFNumberFloat64Type",
"kCFNumberCharType",
"kCFNumberShortType",
"kCFNumberIntType",
"kCFNumberLongType",
"kCFNumberLongLongType",
"kCFNumberFloatType",
"kCFNumberDoubleType",
"kCFNumberCFIndexType",
"kCFNumberNSIntegerType",
"kCFNumberCGFloatType"
};
return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType";
}
#endif
void CFNumberChecker::checkPreStmt(const CallExpr *CE,
CheckerContext &C) const {
ProgramStateRef state = C.getState();
const FunctionDecl *FD = C.getCalleeDecl(CE);
if (!FD)
return;
ASTContext &Ctx = C.getASTContext();
if (!ICreate) {
ICreate = &Ctx.Idents.get("CFNumberCreate");
IGetValue = &Ctx.Idents.get("CFNumberGetValue");
}
if (!(FD->getIdentifier() == ICreate || FD->getIdentifier() == IGetValue) ||
CE->getNumArgs() != 3)
return;
// Get the value of the "theType" argument.
SVal TheTypeVal = C.getSVal(CE->getArg(1));
// FIXME: We really should allow ranges of valid theType values, and
// bifurcate the state appropriately.
Optional<nonloc::ConcreteInt> V = TheTypeVal.getAs<nonloc::ConcreteInt>();
if (!V)
return;
uint64_t NumberKind = V->getValue().getLimitedValue();
Optional<uint64_t> OptCFNumberSize = GetCFNumberSize(Ctx, NumberKind);
// FIXME: In some cases we can emit an error.
if (!OptCFNumberSize)
return;
uint64_t CFNumberSize = *OptCFNumberSize;
// Look at the value of the integer being passed by reference. Essentially
// we want to catch cases where the value passed in is not equal to the
// size of the type being created.
SVal TheValueExpr = C.getSVal(CE->getArg(2));
// FIXME: Eventually we should handle arbitrary locations. We can do this
// by having an enhanced memory model that does low-level typing.
Optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>();
if (!LV)
return;
const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts());
if (!R)
return;
QualType T = Ctx.getCanonicalType(R->getValueType());
// FIXME: If the pointee isn't an integer type, should we flag a warning?
// People can do weird stuff with pointers.
if (!T->isIntegralOrEnumerationType())
return;
uint64_t PrimitiveTypeSize = Ctx.getTypeSize(T);
if (PrimitiveTypeSize == CFNumberSize)
return;
// FIXME: We can actually create an abstract "CFNumber" object that has
// the bits initialized to the provided values.
ExplodedNode *N = C.generateNonFatalErrorNode();
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
if (N) {
SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
bool isCreate = (FD->getIdentifier() == ICreate);
if (isCreate) {
os << (PrimitiveTypeSize == 8 ? "An " : "A ")
<< PrimitiveTypeSize << "-bit integer is used to initialize a "
<< "CFNumber object that represents "
<< (CFNumberSize == 8 ? "an " : "a ")
<< CFNumberSize << "-bit integer; ";
} else {
os << "A CFNumber object that represents "
<< (CFNumberSize == 8 ? "an " : "a ")
<< CFNumberSize << "-bit integer is used to initialize "
<< (PrimitiveTypeSize == 8 ? "an " : "a ")
<< PrimitiveTypeSize << "-bit integer; ";
}
if (PrimitiveTypeSize < CFNumberSize)
os << (CFNumberSize - PrimitiveTypeSize)
<< " bits of the CFNumber value will "
<< (isCreate ? "be garbage." : "overwrite adjacent storage.");
else
os << (PrimitiveTypeSize - CFNumberSize)
<< " bits of the integer value will be "
<< (isCreate ? "lost." : "garbage.");
if (!BT)
BT.reset(new APIMisuse(this, "Bad use of CFNumber APIs"));
auto report = llvm::make_unique<BugReport>(*BT, os.str(), N);
report->addRange(CE->getArg(2)->getSourceRange());
C.emitReport(std::move(report));
}
}
//===----------------------------------------------------------------------===//
// CFRetain/CFRelease/CFMakeCollectable/CFAutorelease checking for null arguments.
//===----------------------------------------------------------------------===//
namespace {
class CFRetainReleaseChecker : public Checker<check::PreCall> {
mutable APIMisuse BT{this, "null passed to CF memory management function"};
CallDescription CFRetain{"CFRetain", 1},
CFRelease{"CFRelease", 1},
CFMakeCollectable{"CFMakeCollectable", 1},
CFAutorelease{"CFAutorelease", 1};
public:
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
};
} // end anonymous namespace
void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
// TODO: Make this check part of CallDescription.
if (!Call.isGlobalCFunction())
return;
// Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease.
if (!(Call.isCalled(CFRetain) || Call.isCalled(CFRelease) ||
Call.isCalled(CFMakeCollectable) || Call.isCalled(CFAutorelease)))
return;
// Get the argument's value.
SVal ArgVal = Call.getArgSVal(0);
Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>();
if (!DefArgVal)
return;
// Is it null?
ProgramStateRef state = C.getState();
ProgramStateRef stateNonNull, stateNull;
std::tie(stateNonNull, stateNull) = state->assume(*DefArgVal);
if (!stateNonNull) {
ExplodedNode *N = C.generateErrorNode(stateNull);
if (!N)
return;
SmallString<64> Str;
raw_svector_ostream OS(Str);
OS << "Null pointer argument in call to "
<< cast<FunctionDecl>(Call.getDecl())->getName();
auto report = llvm::make_unique<BugReport>(BT, OS.str(), N);
report->addRange(Call.getArgSourceRange(0));
bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report);
C.emitReport(std::move(report));
return;
}
// From here on, we know the argument is non-null.
C.addTransition(stateNonNull);
}
//===----------------------------------------------------------------------===//
// Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
//===----------------------------------------------------------------------===//
namespace {
class ClassReleaseChecker : public Checker<check::PreObjCMessage> {
mutable Selector releaseS;
mutable Selector retainS;
mutable Selector autoreleaseS;
mutable Selector drainS;
mutable std::unique_ptr<BugType> BT;
public:
void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
};
} // end anonymous namespace
void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
CheckerContext &C) const {
if (!BT) {
BT.reset(new APIMisuse(
this, "message incorrectly sent to class instead of class instance"));
ASTContext &Ctx = C.getASTContext();
releaseS = GetNullarySelector("release", Ctx);
retainS = GetNullarySelector("retain", Ctx);
autoreleaseS = GetNullarySelector("autorelease", Ctx);
drainS = GetNullarySelector("drain", Ctx);
}
if (msg.isInstanceMessage())
return;
const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
assert(Class);
Overhaul the AST representation of Objective-C message send expressions, to improve source-location information, clarify the actual receiver of the message, and pave the way for proper C++ support. The ObjCMessageExpr node represents four different kinds of message sends in a single AST node: 1) Send to a object instance described by an expression (e.g., [x method:5]) 2) Send to a class described by the class name (e.g., [NSString method:5]) 3) Send to a superclass class (e.g, [super method:5] in class method) 4) Send to a superclass instance (e.g., [super method:5] in instance method) Previously these four cases where tangled together. Now, they have more distinct representations. Specific changes: 1) Unchanged; the object instance is represented by an Expr*. 2) Previously stored the ObjCInterfaceDecl* referring to the class receiving the message. Now stores a TypeSourceInfo* so that we know how the class was spelled. This both maintains typedef information and opens the door for more complicated C++ types (e.g., dependent types). There was an alternative, unused representation of these sends by naming the class via an IdentifierInfo *. In practice, we either had an ObjCInterfaceDecl *, from which we would get the IdentifierInfo *, or we fell into the case below... 3) Previously represented by a class message whose IdentifierInfo * referred to "super". Sema and CodeGen would use isStr("super") to determine if they had a send to super. Now represented as a "class super" send, where we have both the location of the "super" keyword and the ObjCInterfaceDecl* of the superclass we're targetting (statically). 4) Previously represented by an instance message whose receiver is a an ObjCSuperExpr, which Sema and CodeGen would check for via isa<ObjCSuperExpr>(). Now represented as an "instance super" send, where we have both the location of the "super" keyword and the ObjCInterfaceDecl* of the superclass we're targetting (statically). Note that ObjCSuperExpr only has one remaining use in the AST, which is for "super.prop" references. The new representation of ObjCMessageExpr is 2 pointers smaller than the old one, since it combines more storage. It also eliminates a leak when we loaded message-send expressions from a precompiled header. The representation also feels much cleaner to me; comments welcome! This patch attempts to maintain the same semantics we previously had with Objective-C message sends. In several places, there are massive changes that boil down to simply replacing a nested-if structure such as: if (message has a receiver expression) { // instance message if (isa<ObjCSuperExpr>(...)) { // send to super } else { // send to an object } } else { // class message if (name->isStr("super")) { // class send to super } else { // send to class } } with a switch switch (E->getReceiverKind()) { case ObjCMessageExpr::SuperInstance: ... case ObjCMessageExpr::Instance: ... case ObjCMessageExpr::SuperClass: ... case ObjCMessageExpr::Class:... } There are quite a few places (particularly in the checkers) where send-to-super is effectively ignored. I've placed FIXMEs in most of them, and attempted to address send-to-super in a reasonable way. This could use some review. llvm-svn: 101972
2010-04-21 08:45:42 +08:00
Selector S = msg.getSelector();
if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
return;
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
SmallString<200> buf;
llvm::raw_svector_ostream os(buf);
os << "The '";
S.print(os);
os << "' message should be sent to instances "
"of class '" << Class->getName()
<< "' and not the class directly";
auto report = llvm::make_unique<BugReport>(*BT, os.str(), N);
report->addRange(msg.getSourceRange());
C.emitReport(std::move(report));
}
}
//===----------------------------------------------------------------------===//
// Check for passing non-Objective-C types to variadic methods that expect
// only Objective-C types.
//===----------------------------------------------------------------------===//
namespace {
class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> {
mutable Selector arrayWithObjectsS;
mutable Selector dictionaryWithObjectsAndKeysS;
mutable Selector setWithObjectsS;
mutable Selector orderedSetWithObjectsS;
mutable Selector initWithObjectsS;
mutable Selector initWithObjectsAndKeysS;
mutable std::unique_ptr<BugType> BT;
bool isVariadicMessage(const ObjCMethodCall &msg) const;
public:
void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
};
} // end anonymous namespace
/// isVariadicMessage - Returns whether the given message is a variadic message,
/// where all arguments must be Objective-C types.
bool
VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const {
const ObjCMethodDecl *MD = msg.getDecl();
if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))
return false;
Selector S = msg.getSelector();
if (msg.isInstanceMessage()) {
// FIXME: Ideally we'd look at the receiver interface here, but that's not
// useful for init, because alloc returns 'id'. In theory, this could lead
// to false positives, for example if there existed a class that had an
// initWithObjects: implementation that does accept non-Objective-C pointer
// types, but the chance of that happening is pretty small compared to the
// gains that this analysis gives.
const ObjCInterfaceDecl *Class = MD->getClassInterface();
switch (findKnownClass(Class)) {
case FC_NSArray:
case FC_NSOrderedSet:
case FC_NSSet:
return S == initWithObjectsS;
case FC_NSDictionary:
return S == initWithObjectsAndKeysS;
default:
return false;
}
} else {
const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
switch (findKnownClass(Class)) {
case FC_NSArray:
return S == arrayWithObjectsS;
case FC_NSOrderedSet:
return S == orderedSetWithObjectsS;
case FC_NSSet:
return S == setWithObjectsS;
case FC_NSDictionary:
return S == dictionaryWithObjectsAndKeysS;
default:
return false;
}
}
}
void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
CheckerContext &C) const {
if (!BT) {
BT.reset(new APIMisuse(this,
"Arguments passed to variadic method aren't all "
"Objective-C pointer types"));
ASTContext &Ctx = C.getASTContext();
arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);
dictionaryWithObjectsAndKeysS =
GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);
setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);
orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx);
initWithObjectsS = GetUnarySelector("initWithObjects", Ctx);
initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx);
}
if (!isVariadicMessage(msg))
return;
// We are not interested in the selector arguments since they have
// well-defined types, so the compiler will issue a warning for them.
unsigned variadicArgsBegin = msg.getSelector().getNumArgs();
// We're not interested in the last argument since it has to be nil or the
// compiler would have issued a warning for it elsewhere.
unsigned variadicArgsEnd = msg.getNumArgs() - 1;
if (variadicArgsEnd <= variadicArgsBegin)
return;
// Verify that all arguments have Objective-C types.
Optional<ExplodedNode*> errorNode;
for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {
QualType ArgTy = msg.getArgExpr(I)->getType();
if (ArgTy->isObjCObjectPointerType())
continue;
// Block pointers are treaded as Objective-C pointers.
if (ArgTy->isBlockPointerType())
continue;
// Ignore pointer constants.
if (msg.getArgSVal(I).getAs<loc::ConcreteInt>())
continue;
// Ignore pointer types annotated with 'NSObject' attribute.
if (C.getASTContext().isObjCNSObjectType(ArgTy))
continue;
// Ignore CF references, which can be toll-free bridged.
if (coreFoundation::isCFObjectRef(ArgTy))
continue;
// Generate only one error node to use for all bug reports.
if (!errorNode.hasValue())
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
errorNode = C.generateNonFatalErrorNode();
if (!errorNode.getValue())
continue;
SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
StringRef TypeName = GetReceiverInterfaceName(msg);
if (!TypeName.empty())
os << "Argument to '" << TypeName << "' method '";
else
os << "Argument to method '";
msg.getSelector().print(os);
os << "' should be an Objective-C pointer type, not '";
ArgTy.print(os, C.getLangOpts());
os << "'";
auto R = llvm::make_unique<BugReport>(*BT, os.str(), errorNode.getValue());
R->addRange(msg.getArgSourceRange(I));
C.emitReport(std::move(R));
}
}
//===----------------------------------------------------------------------===//
// Improves the modeling of loops over Cocoa collections.
//===----------------------------------------------------------------------===//
// The map from container symbol to the container count symbol.
// We currently will remember the last container count symbol encountered.
REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef)
REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool)
namespace {
class ObjCLoopChecker
: public Checker<check::PostStmt<ObjCForCollectionStmt>,
check::PostObjCMessage,
check::DeadSymbols,
check::PointerEscape > {
mutable IdentifierInfo *CountSelectorII;
bool isCollectionCountMethod(const ObjCMethodCall &M,
CheckerContext &C) const;
public:
ObjCLoopChecker() : CountSelectorII(nullptr) {}
void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const;
void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
ProgramStateRef checkPointerEscape(ProgramStateRef State,
const InvalidatedSymbols &Escaped,
const CallEvent *Call,
PointerEscapeKind Kind) const;
};
} // end anonymous namespace
static bool isKnownNonNilCollectionType(QualType T) {
const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();
if (!PT)
return false;
const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();
if (!ID)
return false;
switch (findKnownClass(ID)) {
case FC_NSArray:
case FC_NSDictionary:
case FC_NSEnumerator:
case FC_NSOrderedSet:
case FC_NSSet:
return true;
default:
return false;
}
}
/// Assumes that the collection is non-nil.
///
/// If the collection is known to be nil, returns NULL to indicate an infeasible
/// path.
static ProgramStateRef checkCollectionNonNil(CheckerContext &C,
ProgramStateRef State,
const ObjCForCollectionStmt *FCS) {
if (!State)
return nullptr;
SVal CollectionVal = C.getSVal(FCS->getCollection());
Optional<DefinedSVal> KnownCollection = CollectionVal.getAs<DefinedSVal>();
if (!KnownCollection)
return State;
ProgramStateRef StNonNil, StNil;
std::tie(StNonNil, StNil) = State->assume(*KnownCollection);
if (StNil && !StNonNil) {
// The collection is nil. This path is infeasible.
return nullptr;
}
return StNonNil;
}
/// Assumes that the collection elements are non-nil.
///
/// This only applies if the collection is one of those known not to contain
/// nil values.
static ProgramStateRef checkElementNonNil(CheckerContext &C,
ProgramStateRef State,
const ObjCForCollectionStmt *FCS) {
if (!State)
return nullptr;
// See if the collection is one where we /know/ the elements are non-nil.
if (!isKnownNonNilCollectionType(FCS->getCollection()->getType()))
return State;
const LocationContext *LCtx = C.getLocationContext();
const Stmt *Element = FCS->getElement();
// FIXME: Copied from ExprEngineObjC.
Optional<Loc> ElementLoc;
if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) {
const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl());
assert(ElemDecl->getInit() == nullptr);
ElementLoc = State->getLValue(ElemDecl, LCtx);
} else {
ElementLoc = State->getSVal(Element, LCtx).getAs<Loc>();
}
if (!ElementLoc)
return State;
// Go ahead and assume the value is non-nil.
SVal Val = State->getSVal(*ElementLoc);
return State->assume(Val.castAs<DefinedOrUnknownSVal>(), true);
}
/// Returns NULL state if the collection is known to contain elements
/// (or is known not to contain elements if the Assumption parameter is false.)
static ProgramStateRef
assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,
SymbolRef CollectionS, bool Assumption) {
if (!State || !CollectionS)
return State;
const SymbolRef *CountS = State->get<ContainerCountMap>(CollectionS);
if (!CountS) {
const bool *KnownNonEmpty = State->get<ContainerNonEmptyMap>(CollectionS);
if (!KnownNonEmpty)
return State->set<ContainerNonEmptyMap>(CollectionS, Assumption);
return (Assumption == *KnownNonEmpty) ? State : nullptr;
}
SValBuilder &SvalBuilder = C.getSValBuilder();
SVal CountGreaterThanZeroVal =
SvalBuilder.evalBinOp(State, BO_GT,
nonloc::SymbolVal(*CountS),
SvalBuilder.makeIntVal(0, (*CountS)->getType()),
SvalBuilder.getConditionType());
Optional<DefinedSVal> CountGreaterThanZero =
CountGreaterThanZeroVal.getAs<DefinedSVal>();
if (!CountGreaterThanZero) {
// The SValBuilder cannot construct a valid SVal for this condition.
// This means we cannot properly reason about it.
return State;
}
return State->assume(*CountGreaterThanZero, Assumption);
}
static ProgramStateRef
assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,
const ObjCForCollectionStmt *FCS,
bool Assumption) {
if (!State)
return nullptr;
SymbolRef CollectionS = C.getSVal(FCS->getCollection()).getAsSymbol();
return assumeCollectionNonEmpty(C, State, CollectionS, Assumption);
}
/// If the fist block edge is a back edge, we are reentering the loop.
static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N,
const ObjCForCollectionStmt *FCS) {
if (!N)
return false;
ProgramPoint P = N->getLocation();
if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) {
return BE->getSrc()->getLoopTarget() == FCS;
}
// Keep looking for a block edge.
for (ExplodedNode::const_pred_iterator I = N->pred_begin(),
E = N->pred_end(); I != E; ++I) {
if (alreadyExecutedAtLeastOneLoopIteration(*I, FCS))
return true;
}
return false;
}
void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
// Check if this is the branch for the end of the loop.
SVal CollectionSentinel = C.getSVal(FCS);
if (CollectionSentinel.isZeroConstant()) {
if (!alreadyExecutedAtLeastOneLoopIteration(C.getPredecessor(), FCS))
State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/false);
// Otherwise, this is a branch that goes through the loop body.
} else {
State = checkCollectionNonNil(C, State, FCS);
State = checkElementNonNil(C, State, FCS);
State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true);
}
if (!State)
[analyzer] Add generateErrorNode() APIs to CheckerContext. The analyzer trims unnecessary nodes from the exploded graph before reporting path diagnostics. However, in some cases it can trim all nodes (including the error node), leading to an assertion failure (see https://llvm.org/bugs/show_bug.cgi?id=24184). This commit addresses the issue by adding two new APIs to CheckerContext to explicitly create error nodes. Unless the client provides a custom tag, these APIs tag the node with the checker's tag -- preventing it from being trimmed. The generateErrorNode() method creates a sink error node, while generateNonFatalErrorNode() creates an error node for a path that should continue being explored. The intent is that one of these two methods should be used whenever a checker creates an error node. This commit updates the checkers to use these APIs. These APIs (unlike addTransition() and generateSink()) do not take an explicit Pred node. This is because there are not any error nodes in the checkers that were created with an explicit different than the default (the CheckerContext's Pred node). It also changes generateSink() to require state and pred nodes (previously these were optional) to reduce confusion. Additionally, there were several cases where checkers did check whether a generated node could be null; we now explicitly check for null in these places. This commit also includes a test case written by Ying Yi as part of http://reviews.llvm.org/D12163 (that patch originally addressed this issue but was reverted because it introduced false positive regressions). Differential Revision: http://reviews.llvm.org/D12780 llvm-svn: 247859
2015-09-17 06:03:05 +08:00
C.generateSink(C.getState(), C.getPredecessor());
else if (State != C.getState())
C.addTransition(State);
}
bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M,
CheckerContext &C) const {
Selector S = M.getSelector();
// Initialize the identifiers on first use.
if (!CountSelectorII)
CountSelectorII = &C.getASTContext().Idents.get("count");
// If the method returns collection count, record the value.
return S.isUnarySelector() &&
(S.getIdentifierInfoForSlot(0) == CountSelectorII);
}
void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M,
CheckerContext &C) const {
if (!M.isInstanceMessage())
return;
const ObjCInterfaceDecl *ClassID = M.getReceiverInterface();
if (!ClassID)
return;
FoundationClass Class = findKnownClass(ClassID);
if (Class != FC_NSDictionary &&
Class != FC_NSArray &&
Class != FC_NSSet &&
Class != FC_NSOrderedSet)
return;
SymbolRef ContainerS = M.getReceiverSVal().getAsSymbol();
if (!ContainerS)
return;
// If we are processing a call to "count", get the symbolic value returned by
// a call to "count" and add it to the map.
if (!isCollectionCountMethod(M, C))
return;
const Expr *MsgExpr = M.getOriginExpr();
SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol();
if (CountS) {
ProgramStateRef State = C.getState();
C.getSymbolManager().addSymbolDependency(ContainerS, CountS);
State = State->set<ContainerCountMap>(ContainerS, CountS);
if (const bool *NonEmpty = State->get<ContainerNonEmptyMap>(ContainerS)) {
State = State->remove<ContainerNonEmptyMap>(ContainerS);
State = assumeCollectionNonEmpty(C, State, ContainerS, *NonEmpty);
}
C.addTransition(State);
}
}
static SymbolRef getMethodReceiverIfKnownImmutable(const CallEvent *Call) {
const ObjCMethodCall *Message = dyn_cast_or_null<ObjCMethodCall>(Call);
if (!Message)
return nullptr;
const ObjCMethodDecl *MD = Message->getDecl();
if (!MD)
return nullptr;
const ObjCInterfaceDecl *StaticClass;
if (isa<ObjCProtocolDecl>(MD->getDeclContext())) {
// We can't find out where the method was declared without doing more work.
// Instead, see if the receiver is statically typed as a known immutable
// collection.
StaticClass = Message->getOriginExpr()->getReceiverInterface();
} else {
StaticClass = MD->getClassInterface();
}
if (!StaticClass)
return nullptr;
switch (findKnownClass(StaticClass, /*IncludeSuper=*/false)) {
case FC_None:
return nullptr;
case FC_NSArray:
case FC_NSDictionary:
case FC_NSEnumerator:
case FC_NSNull:
case FC_NSOrderedSet:
case FC_NSSet:
case FC_NSString:
break;
}
return Message->getReceiverSVal().getAsSymbol();
}
ProgramStateRef
ObjCLoopChecker::checkPointerEscape(ProgramStateRef State,
const InvalidatedSymbols &Escaped,
const CallEvent *Call,
PointerEscapeKind Kind) const {
SymbolRef ImmutableReceiver = getMethodReceiverIfKnownImmutable(Call);
// Remove the invalidated symbols form the collection count map.
for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
E = Escaped.end();
I != E; ++I) {
SymbolRef Sym = *I;
// Don't invalidate this symbol's count if we know the method being called
// is declared on an immutable class. This isn't completely correct if the
// receiver is also passed as an argument, but in most uses of NSArray,
// NSDictionary, etc. this isn't likely to happen in a dangerous way.
if (Sym == ImmutableReceiver)
continue;
// The symbol escaped. Pessimistically, assume that the count could have
// changed.
State = State->remove<ContainerCountMap>(Sym);
State = State->remove<ContainerNonEmptyMap>(Sym);
}
return State;
}
void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
// Remove the dead symbols from the collection count map.
ContainerCountMapTy Tracked = State->get<ContainerCountMap>();
for (ContainerCountMapTy::iterator I = Tracked.begin(),
E = Tracked.end(); I != E; ++I) {
SymbolRef Sym = I->first;
if (SymReaper.isDead(Sym)) {
State = State->remove<ContainerCountMap>(Sym);
State = State->remove<ContainerNonEmptyMap>(Sym);
}
}
C.addTransition(State);
}
namespace {
/// \class ObjCNonNilReturnValueChecker
/// The checker restricts the return values of APIs known to
/// never (or almost never) return 'nil'.
class ObjCNonNilReturnValueChecker
: public Checker<check::PostObjCMessage,
check::PostStmt<ObjCArrayLiteral>,
check::PostStmt<ObjCDictionaryLiteral>,
check::PostStmt<ObjCBoxedExpr> > {
mutable bool Initialized;
mutable Selector ObjectAtIndex;
mutable Selector ObjectAtIndexedSubscript;
mutable Selector NullSelector;
public:
ObjCNonNilReturnValueChecker() : Initialized(false) {}
ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr,
ProgramStateRef State,
CheckerContext &C) const;
void assumeExprIsNonNull(const Expr *E, CheckerContext &C) const {
C.addTransition(assumeExprIsNonNull(E, C.getState(), C));
}
void checkPostStmt(const ObjCArrayLiteral *E, CheckerContext &C) const {
assumeExprIsNonNull(E, C);
}
void checkPostStmt(const ObjCDictionaryLiteral *E, CheckerContext &C) const {
assumeExprIsNonNull(E, C);
}
void checkPostStmt(const ObjCBoxedExpr *E, CheckerContext &C) const {
assumeExprIsNonNull(E, C);
}
void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
};
} // end anonymous namespace
ProgramStateRef
ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr,
ProgramStateRef State,
CheckerContext &C) const {
SVal Val = C.getSVal(NonNullExpr);
if (Optional<DefinedOrUnknownSVal> DV = Val.getAs<DefinedOrUnknownSVal>())
return State->assume(*DV, true);
return State;
}
void ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M,
CheckerContext &C)
const {
ProgramStateRef State = C.getState();
if (!Initialized) {
ASTContext &Ctx = C.getASTContext();
ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx);
ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx);
NullSelector = GetNullarySelector("null", Ctx);
}
// Check the receiver type.
if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) {
// Assume that object returned from '[self init]' or '[super init]' is not
// 'nil' if we are processing an inlined function/method.
//
// A defensive callee will (and should) check if the object returned by
// '[super init]' is 'nil' before doing it's own initialization. However,
// since 'nil' is rarely returned in practice, we should not warn when the
// caller to the defensive constructor uses the object in contexts where
// 'nil' is not accepted.
if (!C.inTopFrame() && M.getDecl() &&
M.getDecl()->getMethodFamily() == OMF_init &&
M.isReceiverSelfOrSuper()) {
State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
}
FoundationClass Cl = findKnownClass(Interface);
// Objects returned from
// [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript]
// are never 'nil'.
if (Cl == FC_NSArray || Cl == FC_NSOrderedSet) {
Selector Sel = M.getSelector();
if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript) {
// Go ahead and assume the value is non-nil.
State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
}
}
// Objects returned from [NSNull null] are not nil.
if (Cl == FC_NSNull) {
if (M.getSelector() == NullSelector) {
// Go ahead and assume the value is non-nil.
State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
}
}
}
C.addTransition(State);
}
//===----------------------------------------------------------------------===//
// Check registration.
//===----------------------------------------------------------------------===//
void ento::registerNilArgChecker(CheckerManager &mgr) {
mgr.registerChecker<NilArgChecker>();
}
bool ento::shouldRegisterNilArgChecker(const LangOptions &LO) {
return true;
}
void ento::registerCFNumberChecker(CheckerManager &mgr) {
mgr.registerChecker<CFNumberChecker>();
}
bool ento::shouldRegisterCFNumberChecker(const LangOptions &LO) {
return true;
}
void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
mgr.registerChecker<CFRetainReleaseChecker>();
}
bool ento::shouldRegisterCFRetainReleaseChecker(const LangOptions &LO) {
return true;
}
void ento::registerClassReleaseChecker(CheckerManager &mgr) {
mgr.registerChecker<ClassReleaseChecker>();
}
bool ento::shouldRegisterClassReleaseChecker(const LangOptions &LO) {
return true;
}
void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
mgr.registerChecker<VariadicMethodTypeChecker>();
}
bool ento::shouldRegisterVariadicMethodTypeChecker(const LangOptions &LO) {
return true;
}
void ento::registerObjCLoopChecker(CheckerManager &mgr) {
mgr.registerChecker<ObjCLoopChecker>();
}
bool ento::shouldRegisterObjCLoopChecker(const LangOptions &LO) {
return true;
}
void ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) {
mgr.registerChecker<ObjCNonNilReturnValueChecker>();
}
bool ento::shouldRegisterObjCNonNilReturnValueChecker(const LangOptions &LO) {
return true;
}