diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 10601619c7a8..e9cf1eeb7114 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -17,6 +17,7 @@ add_clang_library(clangStaticAnalyzerCheckers CallAndMessageChecker.cpp CastSizeChecker.cpp CastToStructChecker.cpp + ObjCContaintersASTChecker.cpp CheckObjCDealloc.cpp CheckObjCInstMethSignature.cpp CheckSecuritySyntaxOnly.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/Checkers.td b/clang/lib/StaticAnalyzer/Checkers/Checkers.td index 696b97a90277..16fc53a24168 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/lib/StaticAnalyzer/Checkers/Checkers.td @@ -368,6 +368,10 @@ def RetainCountChecker : Checker<"RetainCount">, let ParentPackage = CocoaExperimental in { +def ObjCContainersASTChecker : Checker<"ContainerAPI">, + HelpText<"Check for common pitfalls when using 'CFArray', 'CFDictionary', 'CFSet' APIs">, + DescFile<"ObjCContainersASTChecker.cpp">; + def ObjCSelfInitChecker : Checker<"SelfInit">, HelpText<"Check that 'self' is properly initialized inside an initializer method">, DescFile<"ObjCSelfInitChecker.cpp">; diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp new file mode 100644 index 000000000000..e5c133427fdf --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp @@ -0,0 +1,163 @@ +//== ObjCContainersASTChecker.cpp - CoreFoundation containers API *- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// An AST checker that looks for common pitfalls when using 'CFArray', +// 'CFDictionary', 'CFSet' APIs. +// +//===----------------------------------------------------------------------===// +#include "ClangSACheckers.h" +#include "clang/Analysis/AnalysisContext.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace ento; + +namespace { +class WalkAST : public StmtVisitor { + BugReporter &BR; + AnalysisDeclContext* AC; + ASTContext &ASTC; + uint64_t PtrWidth; + + static const unsigned InvalidArgIndex = UINT_MAX; + + /// Check if the type has pointer size (very conservative). + inline bool isPointerSize(const Type *T) { + if (!T) + return true; + if (T->isIncompleteType()) + return true; + return (ASTC.getTypeSize(T) == PtrWidth); + } + + /// Check if the type is a pointer/array to pointer sized values. + inline bool hasPointerToPointerSizedType(const Expr *E) { + QualType T = E->getType(); + + // The type could be either a pointer or array. + const Type *TP = T.getTypePtr(); + QualType PointeeT = TP->getPointeeType(); + if (!PointeeT.isNull()) + return isPointerSize(PointeeT.getTypePtr()); + + if (const Type *TElem = TP->getArrayElementTypeNoTypeQual()) + return isPointerSize(TElem); + + // The type must be an array/pointer type. + + // This could be a null constant, which is allowed. + if (E->isNullPointerConstant(ASTC, Expr::NPC_ValueDependentIsNull)) + return true; + return false; + } + +public: + WalkAST(BugReporter &br, AnalysisDeclContext* ac) + : BR(br), AC(ac), ASTC(AC->getASTContext()), + PtrWidth(ASTC.getTargetInfo().getPointerWidth(0)) {} + + // Statement visitor methods. + void VisitChildren(Stmt *S); + void VisitStmt(Stmt *S) { VisitChildren(S); } + void VisitCallExpr(CallExpr *CE); +}; +} // end anonymous namespace + +static StringRef getCalleeName(CallExpr *CE) { + const FunctionDecl *FD = CE->getDirectCallee(); + if (!FD) + return StringRef(); + + IdentifierInfo *II = FD->getIdentifier(); + if (!II) // if no identifier, not a simple C function + return StringRef(); + + return II->getName(); +} + +void WalkAST::VisitCallExpr(CallExpr *CE) { + StringRef Name = getCalleeName(CE); + if (Name.empty()) + return; + + const Expr *Arg = 0; + unsigned ArgNum = InvalidArgIndex; + + if (Name.equals("CFArrayCreate") || Name.equals("CFSetCreate")) { + ArgNum = 1; + Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); + if (hasPointerToPointerSizedType(Arg)) + return; + } + + if (Arg == 0 && Name.equals("CFDictionaryCreate")) { + // Check first argument. + ArgNum = 1; + Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); + if (hasPointerToPointerSizedType(Arg)) { + // Check second argument. + ArgNum = 2; + Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); + if (hasPointerToPointerSizedType(Arg)) + // Both are good, return. + return; + } + } + + if (ArgNum != InvalidArgIndex) { + assert(ArgNum == 1 || ArgNum == 2); + + llvm::SmallString<256> BufName; + llvm::raw_svector_ostream OsName(BufName); + assert(ArgNum == 1 || ArgNum == 2); + OsName << " Invalid use of '" << Name << "'" ; + + llvm::SmallString<256> Buf; + llvm::raw_svector_ostream Os(Buf); + Os << " The "<< ((ArgNum == 1) ? "first" : "second") << " argument to '" + << Name << "' must be a C array of pointer-sized values, not '" + << Arg->getType().getAsString() << "'"; + + SourceRange R = Arg->getSourceRange(); + PathDiagnosticLocation CELoc = + PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); + BR.EmitBasicReport(OsName.str(), "Core Foundation/Objective-C API", + Os.str(), CELoc, &R, 1); + } + + // Recurse and check children. + VisitChildren(CE); +} + +void WalkAST::VisitChildren(Stmt *S) { + for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I) + if (Stmt *child = *I) + Visit(child); +} + +namespace { +class ObjCContainersASTChecker : public Checker { +public: + + void checkASTCodeBody(const Decl *D, AnalysisManager& Mgr, + BugReporter &BR) const { + WalkAST walker(BR, Mgr.getAnalysisDeclContext(D)); + walker.Visit(D->getBody()); + } +}; +} + +void ento::registerObjCContainersASTChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} diff --git a/clang/test/Analysis/CFContainers.mm b/clang/test/Analysis/CFContainers.mm new file mode 100644 index 000000000000..9ce142a14ee5 --- /dev/null +++ b/clang/test/Analysis/CFContainers.mm @@ -0,0 +1,126 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=experimental.osx.cocoa.ContainerAPI -analyzer-store=region -verify %s + +typedef const struct __CFAllocator * CFAllocatorRef; +typedef const struct __CFString * CFStringRef; +typedef unsigned char Boolean; +typedef signed long CFIndex; +extern +const CFAllocatorRef kCFAllocatorDefault; +typedef const void * (*CFArrayRetainCallBack)(CFAllocatorRef allocator, const void *value); +typedef void (*CFArrayReleaseCallBack)(CFAllocatorRef allocator, const void *value); +typedef CFStringRef (*CFArrayCopyDescriptionCallBack)(const void *value); +typedef Boolean (*CFArrayEqualCallBack)(const void *value1, const void *value2); +typedef struct { + CFIndex version; + CFArrayRetainCallBack retain; + CFArrayReleaseCallBack release; + CFArrayCopyDescriptionCallBack copyDescription; + CFArrayEqualCallBack equal; +} CFArrayCallBacks; +typedef const struct __CFArray * CFArrayRef; +CFArrayRef CFArrayCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues, const CFArrayCallBacks *callBacks); +typedef const struct __CFString * CFStringRef; +enum { + 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, + kCFNumberMaxType = 16 +}; +typedef CFIndex CFNumberType; +typedef const struct __CFNumber * CFNumberRef; +typedef CFIndex CFComparisonResult; +typedef const struct __CFDictionary * CFDictionaryRef; +typedef const void * (*CFDictionaryRetainCallBack)(CFAllocatorRef allocator, const void *value); +typedef void (*CFDictionaryReleaseCallBack)(CFAllocatorRef allocator, const void *value); +typedef CFStringRef (*CFDictionaryCopyDescriptionCallBack)(const void *value); +typedef Boolean (*CFDictionaryEqualCallBack)(const void *value1, const void *value2); +typedef Boolean (*CFArrayEqualCallBack)(const void *value1, const void *value2); +typedef Boolean (*CFSetEqualCallBack)(const void *value1, const void *value2); +typedef const void * (*CFSetRetainCallBack)(CFAllocatorRef allocator, const void *value); +typedef void (*CFSetReleaseCallBack)(CFAllocatorRef allocator, const void *value); +typedef CFStringRef (*CFSetCopyDescriptionCallBack)(const void *value); +typedef struct { + CFIndex version; + CFSetRetainCallBack retain; + CFSetReleaseCallBack release; + CFSetCopyDescriptionCallBack copyDescription; + CFSetEqualCallBack equal; +} CFSetCallBacks; +typedef struct { + CFIndex version; + CFDictionaryRetainCallBack retain; + CFDictionaryReleaseCallBack release; + CFDictionaryCopyDescriptionCallBack copyDescription; + CFDictionaryEqualCallBack equal; +} CFDictionaryKeyCallBacks; +typedef struct { + CFIndex version; + CFDictionaryRetainCallBack retain; + CFDictionaryReleaseCallBack release; + CFDictionaryCopyDescriptionCallBack copyDescription; + CFDictionaryEqualCallBack equal; +} CFDictionaryValueCallBacks; +CFDictionaryRef CFDictionaryCreate(CFAllocatorRef allocator, const void **keys, const void **values, CFIndex numValues, const CFDictionaryKeyCallBacks *keyCallBacks, const CFDictionaryValueCallBacks *valueCallBacks); +extern +const CFDictionaryValueCallBacks kCFTypeDictionaryValueCallBacks; +typedef const struct __CFSet * CFSetRef; +extern +const CFSetCallBacks kCFTypeSetCallBacks; +extern +const CFDictionaryKeyCallBacks kCFCopyStringDictionaryKeyCallBacks; +CFDictionaryRef CFDictionaryCreate(CFAllocatorRef allocator, const void **keys, const void **values, CFIndex numValues, const +CFDictionaryKeyCallBacks *keyCallBacks, const CFDictionaryValueCallBacks *valueCallBacks); +CFNumberRef CFNumberCreate(CFAllocatorRef allocator, CFNumberType theType, const void *valuePtr); +extern +CFSetRef CFSetCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues, const CFSetCallBacks *callBacks); +#define CFSTR(cStr) ((CFStringRef) __builtin___CFStringMakeConstantString ("" cStr "")) +#define NULL __null + +// Done with the headers. +// Test experimental.osx.cocoa.ContainerAPI checker. +void testContainers(int **xNoWarn, CFIndex count) { + int x[] = { 1, 2, 3 }; + CFArrayRef foo = CFArrayCreate(kCFAllocatorDefault, (const void **) x, sizeof(x) / sizeof(x[0]), 0);// expected-warning {{The first argument to 'CFArrayCreate' must be a C array of pointer-sized}} + + CFArrayRef fooNoWarn = CFArrayCreate(kCFAllocatorDefault, (const void **) xNoWarn, sizeof(xNoWarn) / sizeof(xNoWarn[0]), 0); // no warning + CFArrayRef fooNoWarn2 = CFArrayCreate(kCFAllocatorDefault, 0, sizeof(xNoWarn) / sizeof(xNoWarn[0]), 0);// no warning, passing in 0 + CFArrayRef fooNoWarn3 = CFArrayCreate(kCFAllocatorDefault, NULL, sizeof(xNoWarn) / sizeof(xNoWarn[0]), 0);// no warning, passing in NULL + + CFSetRef set = CFSetCreate(NULL, (const void **)x, 3, &kCFTypeSetCallBacks); // expected-warning {{The first argument to 'CFSetCreate' must be a C array of pointer-sized values}} + CFArrayRef* pairs = new CFArrayRef[count]; + CFSetRef fSet = CFSetCreate(kCFAllocatorDefault, (const void**) pairs, count - 1, &kCFTypeSetCallBacks);// no warning +} + +void CreateDict(int *elems) { + const short days28 = 28; + const short days30 = 30; + const short days31 = 31; + CFIndex numValues = 6; + CFStringRef keys[6]; + CFNumberRef values[6]; + keys[0] = CFSTR("January"); values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &days31); + keys[1] = CFSTR("February"); values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &days28); + keys[2] = CFSTR("March"); values[2] = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &days31); + keys[3] = CFSTR("April"); values[3] = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &days30); + keys[4] = CFSTR("May"); values[4] = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &days31); + keys[5] = CFSTR("June"); values[5] = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &days30); + + const CFDictionaryKeyCallBacks keyCB = kCFCopyStringDictionaryKeyCallBacks; + const CFDictionaryValueCallBacks valCB = kCFTypeDictionaryValueCallBacks; + CFDictionaryRef dict1 = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, numValues, &keyCB, &valCB); // no warning + CFDictionaryRef dict2 = CFDictionaryCreate(kCFAllocatorDefault, (const void**)elems[0], (const void**)values, numValues, &keyCB, &valCB); //expected-warning {{The first argument to 'CFDictionaryCreate' must be a C array of}} + CFDictionaryRef dict3 = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)elems, numValues, &keyCB, &valCB); // expected-warning {{The second argument to 'CFDictionaryCreate' must be a C array of pointer-sized values}} +} \ No newline at end of file