ObjCMessage Msg;
ProgramStateRef State;
const LocationContext *LCtx;
+
+ bool isCallbackArg(unsigned Idx, const Type *T) const;
+
public:
CallOrObjCMessage(const CallExpr *callE, ProgramStateRef state,
const LocationContext *lctx)
return Msg.getReceiverSourceRange();
}
+ /// \brief Check if one of the arguments might be a callback.
+ bool hasNonZeroCallbackArg() const;
+
+
/// \brief Check if the name corresponds to a CoreFoundation or CoreGraphics
/// function that allows objects to 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, "WithData") != StringRef::npos ||
- StrInStrNoCase(FName, "AppendValue") != StringRef::npos||
- StrInStrNoCase(FName, "SetAttribute") != StringRef::npos) {
- return true;
- }
- return false;
- }
+ static bool isCFCGAllowingEscape(StringRef FName);
};
}
if (FName.startswith("NS") && (FName.find("Insert") != StringRef::npos))
return false;
+ // If the call has a callback as an argument, assume the memory
+ // can be freed.
+ if (Call->hasNonZeroCallbackArg())
+ return false;
+
// Otherwise, assume that the function does not free memory.
// Most system calls, do not free the memory.
return true;
return false;
}
+ // If the call has a callback as an argument, assume the memory
+ // can be freed.
+ if (Call->hasNonZeroCallbackArg())
+ return false;
+
// Otherwise, assume that the function does not free memory.
// Most system calls, do not free the memory.
return true;
// allocators/deallocators upon container construction.
// - NSXXInsertXX, for example NSMapInsertIfAbsent, since they can
// be deallocated by NSMapRemove.
+ // - Any call that has a callback as one of the arguments.
if (FName == "pthread_setspecific" ||
FName == "funopen" ||
FName.endswith("NoCopy") ||
(FName.startswith("NS") &&
(FName.find("Insert") != StringRef::npos)) ||
- Call.isCFCGAllowingEscape(FName))
+ Call.isCFCGAllowingEscape(FName) ||
+ Call.hasNonZeroCallbackArg())
return;
}
if (const ObjCMethodDecl *MDecl = dyn_cast<ObjCMethodDecl>(CallDecl)) {
assert(MDecl->param_size() <= Call.getNumArgs());
unsigned Idx = 0;
+
+ if (Call.hasNonZeroCallbackArg())
+ return;
+
for (clang::ObjCMethodDecl::param_const_iterator
I = MDecl->param_begin(), E = MDecl->param_end(); I != E; ++I, ++Idx) {
if (isPointerToConst(*I))
return 0;
}
+bool CallOrObjCMessage::isCallbackArg(unsigned Idx, const Type *T) const {
+ // Should we dig into struct fields, arrays ect?
+ if (T->isBlockPointerType() || T->isFunctionPointerType())
+ if (!getArgSVal(Idx).isZeroConstant())
+ return true;
+ return false;
+}
+
+bool CallOrObjCMessage::hasNonZeroCallbackArg() const {
+ unsigned NumOfArgs = getNumArgs();
+
+ // Process ObjC message first.
+ if (!CallE) {
+ const ObjCMethodDecl *D = Msg.getMethodDecl();
+ unsigned Idx = 0;
+ for (ObjCMethodDecl::param_const_iterator I = D->param_begin(),
+ E = D->param_end(); I != E; ++I, ++Idx) {
+ if (NumOfArgs <= Idx)
+ break;
+
+ if (isCallbackArg(Idx, (*I)->getType().getTypePtr()))
+ return true;
+ }
+ return false;
+ }
+
+ // Else, assume we are dealing with a Function call.
+ const FunctionDecl *FD = 0;
+ if (const CXXConstructExpr *Ctor =
+ CallE.dyn_cast<const CXXConstructExpr *>())
+ FD = Ctor->getConstructor();
+
+ const CallExpr * CE = CallE.get<const CallExpr *>();
+ FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl());
+
+ // If calling using a function pointer, assume the function does not
+ // have a callback. TODO: We could check the types of the arguments here.
+ if (!FD)
+ return false;
+
+ unsigned Idx = 0;
+ for (FunctionDecl::param_const_iterator I = FD->param_begin(),
+ E = FD->param_end(); I != E; ++I, ++Idx) {
+ if (NumOfArgs <= Idx)
+ break;
+
+ if (isCallbackArg(Idx, (*I)->getType().getTypePtr()))
+ return true;
+ }
+ return false;
+}
+
+bool CallOrObjCMessage::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, "WithData") != StringRef::npos ||
+ StrInStrNoCase(FName, "AppendValue") != StringRef::npos||
+ StrInStrNoCase(FName, "SetAttribute") != StringRef::npos) {
+ return true;
+ }
+ return false;
+}
+
+
ptr = ptr; // expected-warning {{leak}}
}
+// Assume that functions which take a function pointer can free memory even if
+// they are defined in system headers and take the const pointer to the
+// allocated memory. (radar://11160612)
+int const_ptr_and_callback(int, const char*, int n, void(*)(void*));
+void r11160612_1() {
+ char *x = malloc(12);
+ const_ptr_and_callback(0, x, 12, free); // no - warning
+}
+
+// Null is passed as callback.
+void r11160612_2() {
+ char *x = malloc(12);
+ const_ptr_and_callback(0, x, 12, 0); // expected-warning {{leak}}
+}
+
+// Callback is passed to a function defined in a system header.
+void r11160612_4() {
+ char *x = malloc(12);
+ sqlite3_bind_text_my(0, x, 12, free); // no - warning
+}
+
// ----------------------------------------------------------------------------
// Below are the known false positives.
Foo aFunction() {
return malloc(10);
}
+
+// Assume that functions which take a function pointer can free memory even if
+// they are defined in system headers and take the const pointer to the
+// allocated memory. (radar://11160612)
+// Test default parameter.
+int const_ptr_and_callback_def_param(int, const char*, int n, void(*)(void*) = 0);
+void r11160612_3() {
+ char *x = (char*)malloc(12);
+ const_ptr_and_callback_def_param(0, x, 12);
+}
-// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.Malloc -analyzer-store=region -verify -Wno-objc-root-class %s
+// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.Malloc -analyzer-store=region -verify -Wno-objc-root-class -fblocks %s
#include "system-header-simulator-objc.h"
@class NSString;
void testCGDataProviderCreateWithData() {
void* b = calloc(8, 8);
CGDataProviderRef p = CGDataProviderCreateWithData(0, b, 8*8, releaseDataCallback);
+}
+
+// Assume that functions which take a function pointer can free memory even if
+// they are defined in system headers and take the const pointer to the
+// allocated memory. (radar://11160612)
+extern CGDataProviderRef UnknownFunWithCallback(void *info,
+ const void *data, size_t size,
+ CGDataProviderReleaseDataCallback releaseData)
+ __attribute__((visibility("default")));
+void testUnknownFunWithCallBack() {
+ void* b = calloc(8, 8);
+ CGDataProviderRef p = UnknownFunWithCallback(0, b, 8*8, releaseDataCallback);
+}
+
+// Test blocks.
+void acceptBlockParam(void *, void (^block)(void *), unsigned);
+void testCallWithBlockCallback() {
+ void *l = malloc(12);
+ acceptBlockParam(l, ^(void *i) { free(i); }, sizeof(char *));
+}
+
+// Test blocks in system headers.
+void testCallWithBlockCallbackInSystem() {
+ void *l = malloc(12);
+ SystemHeaderFunctionWithBlockParam(l, ^(void *i) { free(i); }, sizeof(char *));
}
\ No newline at end of file
extern CFMutableStringRef CFStringCreateMutableWithExternalCharactersNoCopy(CFAllocatorRef alloc, UniChar *chars, CFIndex numChars, CFIndex capacity, CFAllocatorRef externalCharactersAllocator);
extern CFStringRef CFStringCreateWithCStringNoCopy(CFAllocatorRef alloc, const char *cStr, CFStringEncoding encoding, CFAllocatorRef contentsDeallocator);
extern void CFStringAppend(CFMutableStringRef theString, CFStringRef appendedString);
+
+void SystemHeaderFunctionWithBlockParam(void *, void (^block)(void *), unsigned);
fpos_t (*)(void *, fpos_t, int),
int (*)(void *));
+int sqlite3_bind_text_my(int, const char*, int n, void(*)(void*));