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.
//
//===----------------------------------------------------------------------===//
#include "ClangSACheckers.h"
#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
Ted Kremenek
committed
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h"
Ted Kremenek
committed
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
Ted Kremenek
committed
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
Ted Kremenek
committed
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
Ted Kremenek
committed
#include "clang/AST/ASTContext.h"
Benjamin Kramer
committed
#include "llvm/ADT/SmallString.h"
Ted Kremenek
committed
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.
//===----------------------------------------------------------------------===//
Anders Carlsson
committed
static const char* GetReceiverNameType(const ObjCMessage &msg) {
Argyrios Kyrtzidis
committed
if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())
Anders Carlsson
committed
return ID->getIdentifier()->getNameStart();
return 0;
Ted Kremenek
committed
}
Anders Carlsson
committed
static bool isReceiverClassOrSuperclass(const ObjCInterfaceDecl *ID,
Chris Lattner
committed
StringRef ClassName) {
Anders Carlsson
committed
if (ID->getIdentifier()->getName() == ClassName)
return true;
if (const ObjCInterfaceDecl *Super = ID->getSuperClass())
return isReceiverClassOrSuperclass(Super, ClassName);
Ted Kremenek
committed
Anders Carlsson
committed
return false;
static inline bool isNil(SVal X) {
return isa<loc::ConcreteInt>(X);
}
//===----------------------------------------------------------------------===//
// NilArgChecker - Check for prohibited nil arguments to ObjC method calls.
//===----------------------------------------------------------------------===//
Ted Kremenek
committed
class NilArgChecker : public Checker<check::PreObjCMessage> {
mutable OwningPtr<APIMisuse> BT;
void WarnNilArg(CheckerContext &C,
const ObjCMessage &msg, unsigned Arg) const;
void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
void NilArgChecker::WarnNilArg(CheckerContext &C,
Argyrios Kyrtzidis
committed
const ObjCMessage &msg,
unsigned int Arg) const
{
if (!BT)
BT.reset(new APIMisuse("nil argument"));
if (ExplodedNode *N = C.generateSink()) {
SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
Argyrios Kyrtzidis
committed
os << "Argument to '" << GetReceiverNameType(msg) << "' method '"
<< msg.getSelector().getAsString() << "' cannot be nil";
BugReport *R = new BugReport(*BT, os.str(), N);
Argyrios Kyrtzidis
committed
R->addRange(msg.getArgSourceRange(Arg));
C.EmitReport(R);
Ted Kremenek
committed
}
Ted Kremenek
committed
}
void NilArgChecker::checkPreObjCMessage(ObjCMessage msg,
CheckerContext &C) const {
Anders Carlsson
committed
const ObjCInterfaceDecl *ID = msg.getReceiverInterface();
if (!ID)
return;
Anders Carlsson
committed
if (isReceiverClassOrSuperclass(ID, "NSString")) {
Argyrios Kyrtzidis
committed
Selector S = msg.getSelector();
if (S.isUnarySelector())
return;
// FIXME: This is going to be really slow doing these checks with
// lexical comparisons.
std::string NameStr = S.getAsString();
Chris Lattner
committed
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:") {
Ted Kremenek
committed
if (isNil(msg.getArgSVal(0, C.getLocationContext(), C.getState())))
Argyrios Kyrtzidis
committed
WarnNilArg(C, msg, 0);
Ted Kremenek
committed
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Error reporting.
//===----------------------------------------------------------------------===//
namespace {
class CFNumberCreateChecker : public Checker< check::PreStmt<CallExpr> > {
mutable OwningPtr<APIMisuse> BT;
mutable IdentifierInfo* II;
Ted Kremenek
committed
public:
CFNumberCreateChecker() : II(0) {}
void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
Ted Kremenek
committed
private:
Ted Kremenek
committed
void EmitError(const TypedRegion* R, const Expr *Ex,
uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind);
Ted Kremenek
committed
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
};
} // 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();
}
};
}
Ted Kremenek
committed
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::checkPreStmt(const CallExpr *CE,
CheckerContext &C) const {
ProgramStateRef state = C.getState();
Anna Zaks
committed
const FunctionDecl *FD = C.getCalleeDecl(CE);
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.
Ted Kremenek
committed
const LocationContext *LCtx = C.getLocationContext();
SVal TheTypeVal = state->getSVal(CE->getArg(1), LCtx);
// 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.
Ted Kremenek
committed
SVal TheValueExpr = state->getSVal(CE->getArg(2), LCtx);
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 TypedValueRegion* R = dyn_cast<TypedValueRegion>(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.addTransition()) {
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.reset(new APIMisuse("Bad use of CFNumberCreate"));
BugReport *report = new BugReport(*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 Checker< check::PreStmt<CallExpr> > {
mutable OwningPtr<APIMisuse> BT;
mutable IdentifierInfo *Retain, *Release;
Ted Kremenek
committed
public:
CFRetainReleaseChecker(): Retain(0), Release(0) {}
Ted Kremenek
committed
void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
Ted Kremenek
committed
};
} // end anonymous namespace
Ted Kremenek
committed
void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE,
CheckerContext &C) const {
Ted Kremenek
committed
// If the CallExpr doesn't have exactly 1 argument just give up checking.
if (CE->getNumArgs() != 1)
return;
ProgramStateRef state = C.getState();
Anna Zaks
committed
const FunctionDecl *FD = C.getCalleeDecl(CE);
Ted Kremenek
committed
if (!FD)
return;
if (!BT) {
ASTContext &Ctx = C.getASTContext();
Retain = &Ctx.Idents.get("CFRetain");
Release = &Ctx.Idents.get("CFRelease");
BT.reset(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);
Ted Kremenek
committed
SVal ArgVal = state->getSVal(Arg, C.getLocationContext());
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?
ProgramStateRef 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";
BugReport *report = new BugReport(*BT, description, N);
report->addRange(Arg->getSourceRange());
Ted Kremenek
committed
report->addVisitor(bugreporter::getTrackNullOrUndefValueVisitor(N, Arg,
report));
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 Checker<check::PreObjCMessage> {
mutable Selector releaseS;
mutable Selector retainS;
mutable Selector autoreleaseS;
mutable Selector drainS;
mutable OwningPtr<BugType> BT;
Ted Kremenek
committed
public:
void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
Ted Kremenek
committed
};
}
void ClassReleaseChecker::checkPreObjCMessage(ObjCMessage msg,
CheckerContext &C) const {
if (!BT) {
BT.reset(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);
}
Argyrios Kyrtzidis
committed
if (msg.isInstanceMessage())
Ted Kremenek
committed
return;
Argyrios Kyrtzidis
committed
const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
assert(Class);
Argyrios Kyrtzidis
committed
Selector S = msg.getSelector();
if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
Ted Kremenek
committed
return;
if (ExplodedNode *N = C.addTransition()) {
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
BugReport *report = new BugReport(*BT, os.str(), N);
Argyrios Kyrtzidis
committed
report->addRange(msg.getSourceRange());
C.EmitReport(report);
}
Ted Kremenek
committed
}
Anders Carlsson
committed
//===----------------------------------------------------------------------===//
// 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 initWithObjectsS;
mutable Selector initWithObjectsAndKeysS;
mutable OwningPtr<BugType> BT;
Anders Carlsson
committed
bool isVariadicMessage(const ObjCMessage &msg) const;
public:
void checkPreObjCMessage(ObjCMessage msg, CheckerContext &C) const;
};
}
/// isVariadicMessage - Returns whether the given message is a variadic message,
/// where all arguments must be Objective-C types.
bool
VariadicMethodTypeChecker::isVariadicMessage(const ObjCMessage &msg) const {
const ObjCMethodDecl *MD = msg.getMethodDecl();
Ted Kremenek
committed
if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))
Anders Carlsson
committed
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
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();
// -[NSArray initWithObjects:]
if (isReceiverClassOrSuperclass(Class, "NSArray") &&
S == initWithObjectsS)
return true;
// -[NSDictionary initWithObjectsAndKeys:]
if (isReceiverClassOrSuperclass(Class, "NSDictionary") &&
S == initWithObjectsAndKeysS)
return true;
// -[NSSet initWithObjects:]
if (isReceiverClassOrSuperclass(Class, "NSSet") &&
S == initWithObjectsS)
return true;
} else {
const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
// -[NSArray arrayWithObjects:]
if (isReceiverClassOrSuperclass(Class, "NSArray") &&
S == arrayWithObjectsS)
return true;
// -[NSDictionary dictionaryWithObjectsAndKeys:]
if (isReceiverClassOrSuperclass(Class, "NSDictionary") &&
S == dictionaryWithObjectsAndKeysS)
return true;
// -[NSSet setWithObjects:]
if (isReceiverClassOrSuperclass(Class, "NSSet") &&
S == setWithObjectsS)
return true;
}
return false;
}
void VariadicMethodTypeChecker::checkPreObjCMessage(ObjCMessage msg,
CheckerContext &C) const {
if (!BT) {
BT.reset(new APIMisuse("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);
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.
Ted Kremenek
committed
llvm::Optional<ExplodedNode*> errorNode;
ProgramStateRef state = C.getState();
Ted Kremenek
committed
Anders Carlsson
committed
for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {
QualType ArgTy = msg.getArgType(I);
if (ArgTy->isObjCObjectPointerType())
continue;
Anders Carlsson
committed
// Block pointers are treaded as Objective-C pointers.
if (ArgTy->isBlockPointerType())
continue;
Ted Kremenek
committed
// Ignore pointer constants.
Ted Kremenek
committed
if (isa<loc::ConcreteInt>(msg.getArgSVal(I, C.getLocationContext(),
state)))
Ted Kremenek
committed
continue;
// Ignore pointer types annotated with 'NSObject' attribute.
if (C.getASTContext().isObjCNSObjectType(ArgTy))
continue;
// Ignore CF references, which can be toll-free bridged.
Ted Kremenek
committed
if (coreFoundation::isCFObjectRef(ArgTy))
continue;
Ted Kremenek
committed
Ted Kremenek
committed
// Generate only one error node to use for all bug reports.
if (!errorNode.hasValue()) {
errorNode = C.addTransition();
Ted Kremenek
committed
}
if (!errorNode.getValue())
Anders Carlsson
committed
continue;
SmallString<128> sbuf;
Anders Carlsson
committed
llvm::raw_svector_ostream os(sbuf);
if (const char *TypeName = GetReceiverNameType(msg))
os << "Argument to '" << TypeName << "' method '";
else
os << "Argument to method '";
os << msg.getSelector().getAsString()
<< "' should be an Objective-C pointer type, not '"
<< ArgTy.getAsString() << "'";
BugReport *R = new BugReport(*BT, os.str(),
Ted Kremenek
committed
errorNode.getValue());
Anders Carlsson
committed
R->addRange(msg.getArgSourceRange(I));
C.EmitReport(R);
}
}
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
// Check registration.
Ted Kremenek
committed
//===----------------------------------------------------------------------===//
void ento::registerNilArgChecker(CheckerManager &mgr) {
mgr.registerChecker<NilArgChecker>();
}
void ento::registerCFNumberCreateChecker(CheckerManager &mgr) {
mgr.registerChecker<CFNumberCreateChecker>();
}
void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
mgr.registerChecker<CFRetainReleaseChecker>();
Ted Kremenek
committed
}
void ento::registerClassReleaseChecker(CheckerManager &mgr) {
mgr.registerChecker<ClassReleaseChecker>();
Anders Carlsson
committed
void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
mgr.registerChecker<VariadicMethodTypeChecker>();
}