Newer
Older
Ted Kremenek
committed
//== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- C++ -*--
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines BasicObjCFoundationChecks, a class that encapsulates
// a set of simple checks to run on Objective-C code using Apple's Foundation
// classes.
//
//===----------------------------------------------------------------------===//
Ted Kremenek
committed
#include "BasicObjCFoundationChecks.h"
#include "clang/GR/PathSensitive/ExplodedGraph.h"
#include "clang/GR/PathSensitive/CheckerVisitor.h"
#include "clang/GR/PathSensitive/GRExprEngine.h"
#include "clang/GR/PathSensitive/GRState.h"
#include "clang/GR/BugReporter/BugType.h"
#include "clang/GR/PathSensitive/MemRegion.h"
#include "clang/GR/PathSensitive/CheckerVisitor.h"
#include "clang/GR/Checkers/LocalCheckers.h"
Ted Kremenek
committed
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
Ted Kremenek
committed
#include "clang/AST/ASTContext.h"
using namespace clang;
Ted Kremenek
committed
namespace {
class APIMisuse : public BugType {
public:
APIMisuse(const char* name) : BugType(name, "API Misuse (Apple)") {}
};
} // end anonymous namespace
//===----------------------------------------------------------------------===//
// Utility functions.
//===----------------------------------------------------------------------===//
Steve Naroff
committed
static const ObjCInterfaceType* GetReceiverType(const ObjCMessageExpr* ME) {
QualType T;
switch (ME->getReceiverKind()) {
case ObjCMessageExpr::Instance:
T = ME->getInstanceReceiver()->getType();
break;
case ObjCMessageExpr::SuperInstance:
T = ME->getSuperType();
break;
case ObjCMessageExpr::Class:
case ObjCMessageExpr::SuperClass:
return 0;
if (const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>())
Steve Naroff
committed
return PT->getInterfaceType();
return NULL;
Ted Kremenek
committed
}
Steve Naroff
committed
static const char* GetReceiverNameType(const ObjCMessageExpr* ME) {
if (const ObjCInterfaceType *ReceiverType = GetReceiverType(ME))
return ReceiverType->getDecl()->getIdentifier()->getNameStart();
return NULL;
Ted Kremenek
committed
}
static bool isNSString(llvm::StringRef ClassName) {
return ClassName == "NSString" || ClassName == "NSMutableString";
}
static inline bool isNil(SVal X) {
return isa<loc::ConcreteInt>(X);
}
//===----------------------------------------------------------------------===//
// NilArgChecker - Check for prohibited nil arguments to ObjC method calls.
//===----------------------------------------------------------------------===//
Ted Kremenek
committed
namespace {
class NilArgChecker : public CheckerVisitor<NilArgChecker> {
APIMisuse *BT;
void WarnNilArg(CheckerContext &C, const ObjCMessageExpr* ME, unsigned Arg);
public:
NilArgChecker() : BT(0) {}
static void *getTag() { static int x = 0; return &x; }
void PreVisitObjCMessageExpr(CheckerContext &C, const ObjCMessageExpr *ME);
};
}
void NilArgChecker::WarnNilArg(CheckerContext &C,
const clang::ObjCMessageExpr *ME,
unsigned int Arg)
{
if (!BT)
BT = new APIMisuse("nil argument");
if (ExplodedNode *N = C.generateSink()) {
llvm::SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
Ted Kremenek
committed
os << "Argument to '" << GetReceiverNameType(ME) << "' method '"
<< ME->getSelector().getAsString() << "' cannot be nil";
RangedBugReport *R = new RangedBugReport(*BT, os.str(), N);
Ted Kremenek
committed
R->addRange(ME->getArg(Arg)->getSourceRange());
C.EmitReport(R);
Ted Kremenek
committed
}
Ted Kremenek
committed
}
void NilArgChecker::PreVisitObjCMessageExpr(CheckerContext &C,
const ObjCMessageExpr *ME)
{
Steve Naroff
committed
const ObjCInterfaceType *ReceiverType = GetReceiverType(ME);
Ted Kremenek
committed
if (!ReceiverType)
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
return;
if (isNSString(ReceiverType->getDecl()->getIdentifier()->getName())) {
Selector S = ME->getSelector();
if (S.isUnarySelector())
return;
// FIXME: This is going to be really slow doing these checks with
// lexical comparisons.
std::string NameStr = S.getAsString();
llvm::StringRef Name(NameStr);
assert(!Name.empty());
// FIXME: Checking for initWithFormat: will not work in most cases
// yet because [NSString alloc] returns id, not NSString*. We will
// need support for tracking expected-type information in the analyzer
// to find these errors.
if (Name == "caseInsensitiveCompare:" ||
Name == "compare:" ||
Name == "compare:options:" ||
Name == "compare:options:range:" ||
Name == "compare:options:range:locale:" ||
Name == "componentsSeparatedByCharactersInSet:" ||
Name == "initWithFormat:") {
if (isNil(C.getState()->getSVal(ME->getArg(0))))
WarnNilArg(C, ME, 0);
}
Ted Kremenek
committed
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Error reporting.
//===----------------------------------------------------------------------===//
namespace {
class CFNumberCreateChecker : public CheckerVisitor<CFNumberCreateChecker> {
Ted Kremenek
committed
APIMisuse* BT;
Ted Kremenek
committed
IdentifierInfo* II;
public:
CFNumberCreateChecker() : BT(0), II(0) {}
~CFNumberCreateChecker() {}
static void *getTag() { static int x = 0; return &x; }
void PreVisitCallExpr(CheckerContext &C, const CallExpr *CE);
Ted Kremenek
committed
private:
void EmitError(const TypedRegion* R, const Expr* Ex,
uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind);
Ted Kremenek
committed
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
};
} // 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
};
namespace {
template<typename T>
class Optional {
bool IsKnown;
T Val;
public:
Optional() : IsKnown(false), Val(0) {}
Optional(const T& val) : IsKnown(true), Val(val) {}
Ted Kremenek
committed
bool isKnown() const { return IsKnown; }
const T& getValue() const {
assert (isKnown());
return Val;
}
operator const T&() const {
return getValue();
}
};
}
static Optional<uint64_t> GetCFNumberSize(ASTContext& Ctx, uint64_t i) {
static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 };
Ted Kremenek
committed
if (i < kCFNumberCharType)
return FixedSize[i-1];
Ted Kremenek
committed
QualType T;
Ted Kremenek
committed
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*.
Ted Kremenek
committed
default:
return Optional<uint64_t>();
}
Ted Kremenek
committed
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"
};
Ted Kremenek
committed
return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType";
}
#endif
void CFNumberCreateChecker::PreVisitCallExpr(CheckerContext &C,
const CallExpr *CE)
{
const Expr* Callee = CE->getCallee();
const GRState *state = C.getState();
SVal CallV = state->getSVal(Callee);
const FunctionDecl* FD = CallV.getAsFunctionDecl();
Ted Kremenek
committed
if (!FD)
return;
ASTContext &Ctx = C.getASTContext();
if (!II)
II = &Ctx.Idents.get("CFNumberCreate");
if (FD->getIdentifier() != II || CE->getNumArgs() != 3)
return;
Ted Kremenek
committed
// Get the value of the "theType" argument.
SVal TheTypeVal = state->getSVal(CE->getArg(1));
// FIXME: We really should allow ranges of valid theType values, and
// bifurcate the state appropriately.
Zhongxing Xu
committed
nonloc::ConcreteInt* V = dyn_cast<nonloc::ConcreteInt>(&TheTypeVal);
Ted Kremenek
committed
if (!V)
return;
Ted Kremenek
committed
uint64_t NumberKind = V->getValue().getLimitedValue();
Optional<uint64_t> TargetSize = GetCFNumberSize(Ctx, NumberKind);
Ted Kremenek
committed
// FIXME: In some cases we can emit an error.
if (!TargetSize.isKnown())
return;
Ted Kremenek
committed
// 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 = state->getSVal(CE->getArg(2));
Ted Kremenek
committed
// FIXME: Eventually we should handle arbitrary locations. We can do this
// by having an enhanced memory model that does low-level typing.
Zhongxing Xu
committed
loc::MemRegionVal* LV = dyn_cast<loc::MemRegionVal>(&TheValueExpr);
Ted Kremenek
committed
if (!LV)
return;
const TypedRegion* R = dyn_cast<TypedRegion>(LV->StripCasts());
Ted Kremenek
committed
if (!R)
return;
Ted Kremenek
committed
QualType T = Ctx.getCanonicalType(R->getValueType());
Ted Kremenek
committed
// FIXME: If the pointee isn't an integer type, should we flag a warning?
// People can do weird stuff with pointers.
return;
Ted Kremenek
committed
uint64_t SourceSize = Ctx.getTypeSize(T);
Ted Kremenek
committed
// CHECK: is SourceSize == TargetSize
if (SourceSize == TargetSize)
return;
// Generate an error. Only generate a sink if 'SourceSize < TargetSize';
// otherwise generate a regular node.
//
Ted Kremenek
committed
// FIXME: We can actually create an abstract "CFNumber" object that has
// the bits initialized to the provided values.
//
if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink()
: C.generateNode()) {
llvm::SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
os << (SourceSize == 8 ? "An " : "A ")
<< SourceSize << " bit integer is used to initialize a CFNumber "
"object that represents "
<< (TargetSize == 8 ? "an " : "a ")
<< TargetSize << " bit integer. ";
if (SourceSize < TargetSize)
os << (TargetSize - SourceSize)
<< " bits of the CFNumber value will be garbage." ;
else
os << (SourceSize - TargetSize)
<< " bits of the input integer will be lost.";
Ted Kremenek
committed
if (!BT)
BT = new APIMisuse("Bad use of CFNumberCreate");
RangedBugReport *report = new RangedBugReport(*BT, os.str(), N);
report->addRange(CE->getArg(2)->getSourceRange());
C.EmitReport(report);
}
Ted Kremenek
committed
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// CFRetain/CFRelease checking for null arguments.
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
namespace {
class CFRetainReleaseChecker : public CheckerVisitor<CFRetainReleaseChecker> {
Ted Kremenek
committed
APIMisuse *BT;
IdentifierInfo *Retain, *Release;
public:
CFRetainReleaseChecker(): BT(0), Retain(0), Release(0) {}
static void *getTag() { static int x = 0; return &x; }
void PreVisitCallExpr(CheckerContext& C, const CallExpr* CE);
Ted Kremenek
committed
};
} // end anonymous namespace
void CFRetainReleaseChecker::PreVisitCallExpr(CheckerContext& C,
const CallExpr* CE) {
Ted Kremenek
committed
// If the CallExpr doesn't have exactly 1 argument just give up checking.
if (CE->getNumArgs() != 1)
return;
// Get the function declaration of the callee.
const GRState* state = C.getState();
Ted Kremenek
committed
const FunctionDecl* FD = X.getAsFunctionDecl();
Ted Kremenek
committed
if (!FD)
return;
if (!BT) {
ASTContext &Ctx = C.getASTContext();
Retain = &Ctx.Idents.get("CFRetain");
Release = &Ctx.Idents.get("CFRelease");
BT = new APIMisuse("null passed to CFRetain/CFRelease");
}
// Check if we called CFRetain/CFRelease.
const IdentifierInfo *FuncII = FD->getIdentifier();
Ted Kremenek
committed
if (!(FuncII == Retain || FuncII == Release))
return;
// FIXME: The rest of this just checks that the argument is non-null.
// It should probably be refactored and combined with AttrNonNullChecker.
// Get the argument's value.
const Expr *Arg = CE->getArg(0);
SVal ArgVal = state->getSVal(Arg);
DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal);
if (!DefArgVal)
return;
// Get a NULL value.
SValBuilder &svalBuilder = C.getSValBuilder();
DefinedSVal zero = cast<DefinedSVal>(svalBuilder.makeZeroVal(Arg->getType()));
// Make an expression asserting that they're equal.
DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal);
// Are they equal?
const GRState *stateTrue, *stateFalse;
llvm::tie(stateTrue, stateFalse) = state->assume(ArgIsNull);
if (stateTrue && !stateFalse) {
ExplodedNode *N = C.generateSink(stateTrue);
if (!N)
return;
Ted Kremenek
committed
const char *description = (FuncII == Retain)
? "Null pointer argument in call to CFRetain"
: "Null pointer argument in call to CFRelease";
EnhancedBugReport *report = new EnhancedBugReport(*BT, description, N);
report->addRange(Arg->getSourceRange());
report->addVisitorCreator(bugreporter::registerTrackNullOrUndefValue, Arg);
C.EmitReport(report);
return;
}
// From here on, we know the argument is non-null.
C.addTransition(stateFalse);
Ted Kremenek
committed
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
//===----------------------------------------------------------------------===//
namespace {
class ClassReleaseChecker : public CheckerVisitor<ClassReleaseChecker> {
Ted Kremenek
committed
Selector releaseS;
Selector retainS;
Selector autoreleaseS;
Selector drainS;
BugType *BT;
public:
ClassReleaseChecker()
: BT(0) {}
Ted Kremenek
committed
static void *getTag() { static int x = 0; return &x; }
void PreVisitObjCMessageExpr(CheckerContext &C, const ObjCMessageExpr *ME);
};
}
void ClassReleaseChecker::PreVisitObjCMessageExpr(CheckerContext &C,
const ObjCMessageExpr *ME) {
if (!BT) {
BT = new APIMisuse("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);
}
ObjCInterfaceDecl *Class = 0;
switch (ME->getReceiverKind()) {
case ObjCMessageExpr::Class:
Class = ME->getClassReceiver()->getAs<ObjCObjectType>()->getInterface();
break;
case ObjCMessageExpr::SuperClass:
Class = ME->getSuperType()->getAs<ObjCObjectType>()->getInterface();
break;
case ObjCMessageExpr::Instance:
case ObjCMessageExpr::SuperInstance:
Ted Kremenek
committed
return;
Ted Kremenek
committed
Selector S = ME->getSelector();
if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
Ted Kremenek
committed
return;
if (ExplodedNode *N = C.generateNode()) {
llvm::SmallString<200> buf;
llvm::raw_svector_ostream os(buf);
Ted Kremenek
committed
os << "The '" << S.getAsString() << "' message should be sent to instances "
"of class '" << Class->getName()
<< "' and not the class directly";
Ted Kremenek
committed
RangedBugReport *report = new RangedBugReport(*BT, os.str(), N);
report->addRange(ME->getSourceRange());
C.EmitReport(report);
}
Ted Kremenek
committed
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Check registration.
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
void clang::RegisterAppleChecks(GRExprEngine& Eng, const Decl &D) {
Eng.registerCheck(new NilArgChecker());
Eng.registerCheck(new CFNumberCreateChecker());
RegisterNSErrorChecks(Eng.getBugReporter(), Eng, D);
Ted Kremenek
committed
RegisterNSAutoreleasePoolChecks(Eng);
Eng.registerCheck(new CFRetainReleaseChecker());
Eng.registerCheck(new ClassReleaseChecker());