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"
Ted Kremenek
committed
#include "clang/Analysis/PathSensitive/ExplodedGraph.h"
#include "clang/Analysis/PathSensitive/GRSimpleAPICheck.h"
Ted Kremenek
committed
#include "clang/Analysis/PathSensitive/GRExprEngine.h"
#include "clang/Analysis/PathSensitive/GRState.h"
Ted Kremenek
committed
#include "clang/Analysis/PathSensitive/BugReporter.h"
Ted Kremenek
committed
#include "clang/Analysis/PathSensitive/MemRegion.h"
Ted Kremenek
committed
#include "clang/Analysis/PathDiagnostic.h"
Ted Kremenek
committed
#include "clang/Analysis/LocalCheckers.h"
Ted Kremenek
committed
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
Ted Kremenek
committed
#include "clang/AST/ASTContext.h"
#include "llvm/Support/Compiler.h"
#include <sstream>
Ted Kremenek
committed
using namespace clang;
Ted Kremenek
committed
static ObjCInterfaceType* GetReceiverType(ObjCMessageExpr* ME) {
Expr* Receiver = ME->getReceiver();
if (!Receiver)
return NULL;
QualType X = Receiver->getType();
Ted Kremenek
committed
if (X->isPointerType()) {
Type* TP = X.getTypePtr();
const PointerType* T = TP->getAsPointerType();
return dyn_cast<ObjCInterfaceType>(T->getPointeeType().getTypePtr());
}
// FIXME: Support ObjCQualifiedIdType?
return NULL;
Ted Kremenek
committed
}
static const char* GetReceiverNameType(ObjCMessageExpr* ME) {
ObjCInterfaceType* ReceiverType = GetReceiverType(ME);
return ReceiverType ? ReceiverType->getDecl()->getIdentifier()->getName()
: NULL;
}
Ted Kremenek
committed
namespace {
Ted Kremenek
committed
class VISIBILITY_HIDDEN APIMisuse : public BugTypeCacheLocation {
public:
const char* getCategory() const {
return "API Misuse (Apple)";
}
};
Ted Kremenek
committed
Ted Kremenek
committed
class VISIBILITY_HIDDEN NilArg : public APIMisuse {
Ted Kremenek
committed
public:
virtual ~NilArg() {}
virtual const char* getName() const {
return "nil argument";
Ted Kremenek
committed
}
Ted Kremenek
committed
class Report : public BugReport {
std::string Msg;
const char* s;
SourceRange R;
public:
Ted Kremenek
committed
Report(NilArg& Desc, ExplodedNode<GRState>* N,
Ted Kremenek
committed
ObjCMessageExpr* ME, unsigned Arg)
: BugReport(Desc, N) {
Expr* E = ME->getArg(Arg);
R = E->getSourceRange();
std::ostringstream os;
os << "Argument to '" << GetReceiverNameType(ME) << "' method '"
<< ME->getSelector().getAsString() << "' cannot be nil.";
Msg = os.str();
s = Msg.c_str();
}
virtual ~Report() {}
Ted Kremenek
committed
virtual const char* getDescription() const { return s; }
virtual void getRanges(BugReporter& BR,
const SourceRange*& B, const SourceRange*& E) {
Ted Kremenek
committed
B = &R;
E = B+1;
}
};
Ted Kremenek
committed
};
Ted Kremenek
committed
Ted Kremenek
committed
class VISIBILITY_HIDDEN BasicObjCFoundationChecks : public GRSimpleAPICheck {
NilArg Desc;
ASTContext &Ctx;
Ted Kremenek
committed
typedef std::vector<BugReport*> ErrorsTy;
ErrorsTy Errors;
Ted Kremenek
committed
Zhongxing Xu
committed
SVal GetSVal(const GRState* St, Expr* E) { return VMgr->GetSVal(St, E); }
Ted Kremenek
committed
bool isNSString(ObjCInterfaceType* T, const char* suffix);
bool AuditNSString(NodeTy* N, ObjCMessageExpr* ME);
Ted Kremenek
committed
void Warn(NodeTy* N, Expr* E, const std::string& s);
void WarnNilArg(NodeTy* N, Expr* E);
bool CheckNilArg(NodeTy* N, unsigned Arg);
Ted Kremenek
committed
public:
BasicObjCFoundationChecks(ASTContext& ctx, GRStateManager* vmgr)
Ted Kremenek
committed
: Ctx(ctx), VMgr(vmgr) {}
Ted Kremenek
committed
virtual ~BasicObjCFoundationChecks() {
for (ErrorsTy::iterator I = Errors.begin(), E = Errors.end(); I!=E; ++I)
Ted Kremenek
committed
delete *I;
Ted Kremenek
committed
}
Ted Kremenek
committed
virtual bool Audit(ExplodedNode<GRState>* N, GRStateManager&);
virtual void EmitWarnings(BugReporter& BR);
Ted Kremenek
committed
private:
Ted Kremenek
committed
void AddError(BugReport* R) {
Errors.push_back(R);
Ted Kremenek
committed
}
void WarnNilArg(NodeTy* N, ObjCMessageExpr* ME, unsigned Arg) {
Ted Kremenek
committed
AddError(new NilArg::Report(Desc, N, ME, Arg));
Ted Kremenek
committed
}
Ted Kremenek
committed
};
} // end anonymous namespace
Ted Kremenek
committed
GRSimpleAPICheck*
clang::CreateBasicObjCFoundationChecks(ASTContext& Ctx,
Ted Kremenek
committed
return new BasicObjCFoundationChecks(Ctx, VMgr);
}
Ted Kremenek
committed
bool BasicObjCFoundationChecks::Audit(ExplodedNode<GRState>* N,
GRStateManager&) {
ObjCMessageExpr* ME =
cast<ObjCMessageExpr>(cast<PostStmt>(N->getLocation()).getStmt());
ObjCInterfaceType* ReceiverType = GetReceiverType(ME);
Ted Kremenek
committed
if (!ReceiverType)
const char* name = ReceiverType->getDecl()->getIdentifier()->getName();
Ted Kremenek
committed
if (!name)
return false;
Ted Kremenek
committed
if (name[0] != 'N' || name[1] != 'S')
return false;
name += 2;
// FIXME: Make all of this faster.
if (isNSString(ReceiverType, name))
return AuditNSString(N, ME);
Ted Kremenek
committed
}
Zhongxing Xu
committed
static inline bool isNil(SVal X) {
return isa<loc::ConcreteInt>(X);
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Error reporting.
//===----------------------------------------------------------------------===//
void BasicObjCFoundationChecks::EmitWarnings(BugReporter& BR) {
Ted Kremenek
committed
for (ErrorsTy::iterator I=Errors.begin(), E=Errors.end(); I!=E; ++I)
BR.EmitWarning(**I);
}
bool BasicObjCFoundationChecks::CheckNilArg(NodeTy* N, unsigned Arg) {
ObjCMessageExpr* ME =
cast<ObjCMessageExpr>(cast<PostStmt>(N->getLocation()).getStmt());
Expr * E = ME->getArg(Arg);
Zhongxing Xu
committed
if (isNil(GetSVal(N->getState(), E))) {
Ted Kremenek
committed
WarnNilArg(N, ME, Arg);
return true;
}
return false;
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// NSString checking.
//===----------------------------------------------------------------------===//
bool BasicObjCFoundationChecks::isNSString(ObjCInterfaceType* T,
const char* suffix) {
return !strcmp("String", suffix) || !strcmp("MutableString", suffix);
}
bool BasicObjCFoundationChecks::AuditNSString(NodeTy* N,
ObjCMessageExpr* ME) {
Selector S = ME->getSelector();
if (S.isUnarySelector())
return false;
// FIXME: This is going to be really slow doing these checks with
// lexical comparisons.
std::string name = S.getAsString();
assert (!name.empty());
const char* cstr = &name[0];
unsigned len = name.size();
switch (len) {
default:
break;
Ted Kremenek
committed
case 8:
if (!strcmp(cstr, "compare:"))
return CheckNilArg(N, 0);
break;
Ted Kremenek
committed
case 15:
// 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 (!strcmp(cstr, "initWithFormat:"))
return CheckNilArg(N, 0);
break;
Ted Kremenek
committed
case 16:
if (!strcmp(cstr, "compare:options:"))
return CheckNilArg(N, 0);
break;
case 22:
if (!strcmp(cstr, "compare:options:range:"))
return CheckNilArg(N, 0);
break;
case 23:
if (!strcmp(cstr, "caseInsensitiveCompare:"))
return CheckNilArg(N, 0);
Ted Kremenek
committed
case 29:
if (!strcmp(cstr, "compare:options:range:locale:"))
return CheckNilArg(N, 0);
break;
case 37:
if (!strcmp(cstr, "componentsSeparatedByCharactersInSet:"))
return CheckNilArg(N, 0);
break;
Ted Kremenek
committed
}
return false;
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Error reporting.
//===----------------------------------------------------------------------===//
namespace {
Ted Kremenek
committed
class VISIBILITY_HIDDEN BadCFNumberCreate : public APIMisuse{
Ted Kremenek
committed
public:
typedef std::vector<BugReport*> AllErrorsTy;
AllErrorsTy AllErrors;
virtual const char* getName() const {
return "Bad use of CFNumberCreate";
}
virtual void EmitWarnings(BugReporter& BR) {
// FIXME: Refactor this method.
for (AllErrorsTy::iterator I=AllErrors.begin(), E=AllErrors.end(); I!=E;++I)
BR.EmitWarning(**I);
}
};
// FIXME: This entire class should be refactored into the common
// BugReporter classes.
class VISIBILITY_HIDDEN StrBugReport : public RangedBugReport {
std::string str;
const char* cstr;
public:
StrBugReport(BugType& D, ExplodedNode<GRState>* N, std::string s)
Ted Kremenek
committed
: RangedBugReport(D, N), str(s) {
cstr = str.c_str();
}
virtual const char* getDescription() const { return cstr; }
};
class VISIBILITY_HIDDEN AuditCFNumberCreate : public GRSimpleAPICheck {
// FIXME: Who should own this?
BadCFNumberCreate Desc;
// FIXME: Either this should be refactored into GRSimpleAPICheck, or
// it should always be passed with a call to Audit. The latter
// approach makes this class more stateless.
ASTContext& Ctx;
IdentifierInfo* II;
Ted Kremenek
committed
Zhongxing Xu
committed
SVal GetSVal(const GRState* St, Expr* E) { return VMgr->GetSVal(St, E); }
Ted Kremenek
committed
public:
AuditCFNumberCreate(ASTContext& ctx, GRStateManager* vmgr)
Ted Kremenek
committed
: Ctx(ctx), II(&Ctx.Idents.get("CFNumberCreate")), VMgr(vmgr) {}
virtual ~AuditCFNumberCreate() {}
virtual bool Audit(ExplodedNode<GRState>* N, GRStateManager&);
Ted Kremenek
committed
virtual void EmitWarnings(BugReporter& BR) {
Desc.EmitWarnings(BR);
}
private:
void AddError(const TypedRegion* R, Expr* Ex, ExplodedNode<GRState> *N,
Ted Kremenek
committed
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
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
};
namespace {
template<typename T>
class Optional {
bool IsKnown;
T Val;
public:
Optional() : IsKnown(false), Val(0) {}
Optional(const T& val) : IsKnown(true), Val(val) {}
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 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 Optional<uint64_t>();
}
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
bool AuditCFNumberCreate::Audit(ExplodedNode<GRState>* N,GRStateManager&){
Ted Kremenek
committed
CallExpr* CE = cast<CallExpr>(cast<PostStmt>(N->getLocation()).getStmt());
Expr* Callee = CE->getCallee();
Zhongxing Xu
committed
SVal CallV = GetSVal(N->getState(), Callee);
loc::FuncVal* FuncV = dyn_cast<loc::FuncVal>(&CallV);
Ted Kremenek
committed
if (!FuncV || FuncV->getDecl()->getIdentifier() != II || CE->getNumArgs()!=3)
return false;
// Get the value of the "theType" argument.
Zhongxing Xu
committed
SVal TheTypeVal = GetSVal(N->getState(), CE->getArg(1));
Ted Kremenek
committed
// 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 false;
uint64_t NumberKind = V->getValue().getLimitedValue();
Optional<uint64_t> TargetSize = GetCFNumberSize(Ctx, NumberKind);
// FIXME: In some cases we can emit an error.
if (!TargetSize.isKnown())
return false;
// 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.
Zhongxing Xu
committed
SVal TheValueExpr = GetSVal(N->getState(), 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 false;
const TypedRegion* R = dyn_cast<TypedRegion>(LV->getRegion());
Ted Kremenek
committed
while (const AnonTypedRegion* ATR = dyn_cast<AnonTypedRegion>(R)) {
R = dyn_cast<TypedRegion>(ATR->getSuperRegion());
if (!R) return false;
}
Ted Kremenek
committed
Ted Kremenek
committed
// FIXME: If the pointee isn't an integer type, should we flag a warning?
// People can do weird stuff with pointers.
if (!T->isIntegerType())
return false;
uint64_t SourceSize = Ctx.getTypeSize(T);
// CHECK: is SourceSize == TargetSize
if (SourceSize == TargetSize)
return false;
Ted Kremenek
committed
AddError(R, CE->getArg(2), N, SourceSize, TargetSize, NumberKind);
Ted Kremenek
committed
// FIXME: We can actually create an abstract "CFNumber" object that has
// the bits initialized to the provided values.
return SourceSize < TargetSize;
}
void AuditCFNumberCreate::AddError(const TypedRegion* R, Expr* Ex,
Ted Kremenek
committed
uint64_t SourceSize, uint64_t TargetSize,
uint64_t NumberKind) {
std::ostringstream os;
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.";
StrBugReport* B = new StrBugReport(Desc, N, os.str());
B->addRange(Ex->getSourceRange());
Desc.AllErrors.push_back(B);
}
GRSimpleAPICheck*
clang::CreateAuditCFNumberCreate(ASTContext& Ctx,
Ted Kremenek
committed
return new AuditCFNumberCreate(Ctx, VMgr);
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Check registration.
void clang::RegisterAppleChecks(GRExprEngine& Eng) {
ASTContext& Ctx = Eng.getContext();
GRStateManager* VMgr = &Eng.getStateManager();
Ted Kremenek
committed
Eng.AddCheck(CreateBasicObjCFoundationChecks(Ctx, VMgr),
Stmt::ObjCMessageExprClass);
Ted Kremenek
committed
Eng.AddCheck(CreateAuditCFNumberCreate(Ctx, VMgr),
Stmt::CallExprClass);
Eng.Register(CreateNSErrorCheck());