[analyzer] Malloc: A pointer might escape through CFContainers APIs,

funopen, setvbuf.

Teach the checker and the engine about these APIs to resolve malloc
false positives. As I am adding more of these APIs, it is clear that all
this should be factored out into a separate callback (for example,
region escapes). Malloc, KeyChainAPI and RetainRelease checkers could
all use it.

llvm-svn: 151737
This commit is contained in:
Anna Zaks 2012-02-29 18:42:47 +00:00
parent 64d6eb1195
commit e0c03cab58
7 changed files with 164 additions and 10 deletions

View File

@ -21,9 +21,11 @@
#include "clang/AST/ExprCXX.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/StringExtras.h"
namespace clang {
namespace ento {
using llvm::StrInStrNoCase;
/// \brief Represents both explicit ObjC message expressions and implicit
/// messages that are sent for handling properties in dot syntax.
@ -254,6 +256,33 @@ public:
assert(isObjCMessage());
return Msg.getReceiverSourceRange();
}
/// \brief Check if the name corresponds to a CoreFoundation or CoreGraphics
/// function that allows objects to escape.
///
/// Many methods allow a tracked object to escape. For example:
///
/// CFMutableDictionaryRef x = CFDictionaryCreateMutable(..., customDeallocator);
/// CFDictionaryAddValue(y, key, x);
///
/// We handle this and similar cases with the following heuristic. If the
/// function name contains "InsertValue", "SetValue", "AddValue",
/// "AppendValue", or "SetAttribute", then we assume that arguments may
/// escape.
//
// TODO: To reduce false negatives here, we should track the container
// allocation site and check if a proper deallocator was set there.
static bool isCFCGAllowingEscape(StringRef FName) {
if (FName[0] == 'C' && (FName[1] == 'F' || FName[1] == 'G'))
if (StrInStrNoCase(FName, "InsertValue") != StringRef::npos||
StrInStrNoCase(FName, "AddValue") != StringRef::npos ||
StrInStrNoCase(FName, "SetValue") != StringRef::npos ||
StrInStrNoCase(FName, "AppendValue") != StringRef::npos||
StrInStrNoCase(FName, "SetAttribute") != StringRef::npos) {
return true;
}
return false;
}
};
}

View File

@ -42,10 +42,8 @@ public:
RefState(Kind k, const Stmt *s) : K(k), S(s) {}
bool isAllocated() const { return K == AllocateUnchecked; }
//bool isFailed() const { return K == AllocateFailed; }
bool isReleased() const { return K == Released; }
//bool isEscaped() const { return K == Escaped; }
//bool isRelinquished() const { return K == Relinquished; }
const Stmt *getStmt() const { return S; }
bool operator==(const RefState &X) const {
@ -1122,6 +1120,43 @@ bool MallocChecker::doesNotFreeMemory(const CallOrObjCMessage *Call,
return false;
}
// PR12101
// Many CoreFoundation and CoreGraphics might allow a tracked object
// to escape.
if (Call->isCFCGAllowingEscape(FName))
return false;
// Associating streams with malloced buffers. The pointer can escape if
// 'closefn' is specified (and if that function does free memory).
// Currently, we do not inspect the 'closefn' function (PR12101).
if (FName == "funopen")
if (Call->getNumArgs() >= 4 && !Call->getArgSVal(4).isConstant(0))
return false;
// Do not warn on pointers passed to 'setbuf' when used with std streams,
// these leaks might be intentional when setting the buffer for stdio.
// http://stackoverflow.com/questions/2671151/who-frees-setvbuf-buffer
if (FName == "setbuf" || FName =="setbuffer" ||
FName == "setlinebuf" || FName == "setvbuf") {
if (Call->getNumArgs() >= 1)
if (const DeclRefExpr *Arg =
dyn_cast<DeclRefExpr>(Call->getArg(0)->IgnoreParenCasts()))
if (const VarDecl *D = dyn_cast<VarDecl>(Arg->getDecl()))
if (D->getCanonicalDecl()->getName().find("std")
!= StringRef::npos)
return false;
}
// A bunch of other functions, which take ownership of a pointer (See retain
// release checker). Not all the parameters here are invalidated, but the
// Malloc checker cannot differentiate between them. The right way of doing
// this would be to implement a pointer escapes callback.
if (FName == "CVPixelBufferCreateWithBytes" ||
FName == "CGBitmapContextCreateWithData" ||
FName == "CVPixelBufferCreateWithPlanarBytes") {
return false;
}
// Otherwise, assume that the function does not free memory.
// Most system calls, do not free the memory.
return true;

View File

@ -197,10 +197,15 @@ static void findPtrToConstParams(llvm::SmallSet<unsigned, 1> &PreserveArgs,
// value into thread local storage. The value can later be retrieved with
// 'void *ptheread_getspecific(pthread_key)'. So even thought the
// parameter is 'const void *', the region escapes through the call.
// - funopen - sets a buffer for future IO calls.
// - ObjC functions that end with "NoCopy" can free memory, of the passed
// in buffer.
// - Many CF containers allow objects to escape through custom
// allocators/deallocators upon container construction.
if (FName == "pthread_setspecific" ||
FName.endswith("NoCopy"))
FName == "funopen" ||
FName.endswith("NoCopy") ||
Call.isCFCGAllowingEscape(FName))
return;
}

View File

@ -677,7 +677,9 @@ void testStrdupContentIsDefined(const char *s, unsigned validIndex) {
free(s2);
}
// ----------------------------------------------------------------------------
// Test the system library functions to which the pointer can escape.
// This tests false positive suppression.
// For now, we assume memory passed to pthread_specific escapes.
// TODO: We could check that if a new pthread binding is set, the existing
@ -687,6 +689,46 @@ void testPthereadSpecificEscape(pthread_key_t key) {
pthread_setspecific(key, buf); // no warning
}
// PR12101: Test funopen().
static int releasePtr(void *_ctx) {
free(_ctx);
return 0;
}
FILE *useFunOpen() {
void *ctx = malloc(sizeof(int));
FILE *f = funopen(ctx, 0, 0, 0, releasePtr); // no warning
if (f == 0) {
free(ctx);
}
return f;
}
FILE *useFunOpenNoReleaseFunction() {
void *ctx = malloc(sizeof(int));
FILE *f = funopen(ctx, 0, 0, 0, 0);
if (f == 0) {
free(ctx);
}
return f; // expected-warning{{leak}}
}
// Test setbuf, setvbuf.
int my_main_no_warning() {
char *p = malloc(100);
setvbuf(stdout, p, 0, 100);
return 0;
}
int my_main_no_warning2() {
char *p = malloc(100);
setbuf(__stdoutp, p);
return 0;
}
int my_main_warn(FILE *f) {
char *p = malloc(100);
setvbuf(f, p, 0, 100);
return 0;// expected-warning {{leak}}
}
// ----------------------------------------------------------------------------
// Below are the known false positives.
// TODO: There should be no warning here. This one might be difficult to get rid of.
@ -706,6 +748,7 @@ void dependsOnValueOfPtr(int *g, unsigned f) {
return;
}
// ----------------------------------------------------------------------------
// False negatives.
// TODO: This requires tracking symbols stored inside the structs/arrays.

View File

@ -62,8 +62,7 @@ void testNSDatafFreeWhenDoneFN(NSUInteger dataLength) {
free(data); // false negative
}
// Test CF/NS...NoCopy. PR12100.
// Test CF/NS...NoCopy. PR12100: Pointers can escape when custom deallocators are provided.
void testNSDatafFreeWhenDone(NSUInteger dataLength) {
CFStringRef str;
char *bytes = (char*)malloc(12);
@ -83,3 +82,12 @@ void stringWithExternalContentsExample(void) {
CFRelease(mutStr);
//free(myBuffer);
}
// PR12101 : pointers can escape through custom deallocators set on creation of a container.
void TestCallbackReleasesMemory(CFDictionaryKeyCallBacks keyCallbacks) {
void *key = malloc(12);
void *val = malloc(12);
CFMutableDictionaryRef x = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &keyCallbacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(x, key, val);
return;// no-warning
}

View File

@ -92,6 +92,19 @@ typedef double NSTimeInterval;
- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(BOOL)b;
@end
typedef struct {
}
CFDictionaryKeyCallBacks;
extern const CFDictionaryKeyCallBacks kCFTypeDictionaryKeyCallBacks;
typedef struct {
}
CFDictionaryValueCallBacks;
extern const CFDictionaryValueCallBacks kCFTypeDictionaryValueCallBacks;
typedef const struct __CFDictionary * CFDictionaryRef;
typedef struct __CFDictionary * CFMutableDictionaryRef;
extern CFMutableDictionaryRef CFDictionaryCreateMutable(CFAllocatorRef allocator, CFIndex capacity, const CFDictionaryKeyCallBacks *keyCallBacks, const CFDictionaryValueCallBacks *valueCallBacks);
void CFDictionarySetValue(CFMutableDictionaryRef, const void *, const void *);
extern void CFRelease(CFTypeRef cf);

View File

@ -2,16 +2,37 @@
typedef struct _FILE FILE;
extern FILE *stdin;
int fscanf(FILE *restrict stream, const char *restrict format, ...);
extern FILE *stdout;
extern FILE *stderr;
// Include a variant of standard streams that occur in the pre-processed file.
extern FILE *__stdinp;
extern FILE *__stdoutp;
extern FILE *__stderrp;
int fscanf(FILE *restrict, const char *restrict, ...);
// Note, on some platforms errno macro gets replaced with a function call.
extern int errno;
unsigned long strlen(const char *);
char *strcpy(char *restrict s1, const char *restrict s2);
char *strcpy(char *restrict, const char *restrict);
typedef unsigned long __darwin_pthread_key_t;
typedef __darwin_pthread_key_t pthread_key_t;
int pthread_setspecific(pthread_key_t ,
const void *);
int pthread_setspecific(pthread_key_t, const void *);
typedef long long __int64_t;
typedef __int64_t __darwin_off_t;
typedef __darwin_off_t fpos_t;
void setbuf(FILE * restrict, char * restrict);
int setvbuf(FILE * restrict, char * restrict, int, size_t);
FILE *funopen(const void *,
int (*)(void *, char *, int),
int (*)(void *, const char *, int),
fpos_t (*)(void *, fpos_t, int),
int (*)(void *));