aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/StaticAnalyzer/Checkers/MallocChecker.cpp118
-rw-r--r--test/Analysis/Inputs/system-header-simulator-for-malloc.h34
-rw-r--r--test/Analysis/malloc.mm47
3 files changed, 130 insertions, 69 deletions
diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index f412c049c7..28a2999f04 100644
--- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -168,12 +168,13 @@ public:
private:
void initIdentifierInfo(ASTContext &C) const;
+ ///@{
/// Check if this is one of the functions which can allocate/reallocate memory
/// pointed to by one of its arguments.
bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const;
bool isFreeFunction(const FunctionDecl *FD, ASTContext &C) const;
bool isAllocationFunction(const FunctionDecl *FD, ASTContext &C) const;
-
+ ///@}
static ProgramStateRef MallocMemReturnsAttr(CheckerContext &C,
const CallExpr *CE,
const OwnershipAttr* Att);
@@ -218,10 +219,13 @@ private:
bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C,
const Stmt *S = 0) const;
- /// Check if the function is not known to us. So, for example, we could
- /// conservatively assume it can free/reallocate it's pointer arguments.
- bool doesNotFreeMemory(const CallEvent *Call,
- ProgramStateRef State) const;
+ /// Check if the function is known not to free memory, or if it is
+ /// "interesting" and should be modeled explicitly.
+ ///
+ /// We assume that pointers do not escape through calls to system functions
+ /// not handled by this checker.
+ bool doesNotFreeMemOrInteresting(const CallEvent *Call,
+ ProgramStateRef State) const;
static bool SummarizeValue(raw_ostream &os, SVal V);
static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR);
@@ -495,14 +499,30 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
C.addTransition(State);
}
-static bool isFreeWhenDoneSetToZero(const ObjCMethodCall &Call) {
+static bool isKnownDeallocObjCMethodName(const ObjCMethodCall &Call) {
+ // If the first selector piece is one of the names below, assume that the
+ // object takes ownership of the memory, promising to eventually deallocate it
+ // with free().
+ // Ex: [NSData dataWithBytesNoCopy:bytes length:10];
+ // (...unless a 'freeWhenDone' parameter is false, but that's checked later.)
+ StringRef FirstSlot = Call.getSelector().getNameForSlot(0);
+ if (FirstSlot == "dataWithBytesNoCopy" ||
+ FirstSlot == "initWithBytesNoCopy" ||
+ FirstSlot == "initWithCharactersNoCopy")
+ return true;
+
+ return false;
+}
+
+static Optional<bool> getFreeWhenDoneArg(const ObjCMethodCall &Call) {
Selector S = Call.getSelector();
+
+ // FIXME: We should not rely on fully-constrained symbols being folded.
for (unsigned i = 1; i < S.getNumArgs(); ++i)
if (S.getNameForSlot(i).equals("freeWhenDone"))
- if (Call.getArgSVal(i).isConstant(0))
- return true;
+ return !Call.getArgSVal(i).isZeroConstant();
- return false;
+ return None;
}
void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call,
@@ -510,25 +530,20 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call,
if (C.wasInlined)
return;
- // If the first selector is dataWithBytesNoCopy, assume that the memory will
- // be released with 'free' by the new object.
- // Ex: [NSData dataWithBytesNoCopy:bytes length:10];
- // Unless 'freeWhenDone' param set to 0.
- // TODO: Check that the memory was allocated with malloc.
- bool ReleasedAllocatedMemory = false;
- Selector S = Call.getSelector();
- if ((S.getNameForSlot(0) == "dataWithBytesNoCopy" ||
- S.getNameForSlot(0) == "initWithBytesNoCopy" ||
- S.getNameForSlot(0) == "initWithCharactersNoCopy") &&
- !isFreeWhenDoneSetToZero(Call)){
- unsigned int argIdx = 0;
- ProgramStateRef State = FreeMemAux(C, Call.getArgExpr(argIdx),
- Call.getOriginExpr(), C.getState(), true,
- ReleasedAllocatedMemory,
- /* RetNullOnFailure*/ true);
-
- C.addTransition(State);
- }
+ if (!isKnownDeallocObjCMethodName(Call))
+ return;
+
+ if (Optional<bool> FreeWhenDone = getFreeWhenDoneArg(Call))
+ if (!*FreeWhenDone)
+ return;
+
+ bool ReleasedAllocatedMemory;
+ ProgramStateRef State = FreeMemAux(C, Call.getArgExpr(0),
+ Call.getOriginExpr(), C.getState(),
+ /*Hold=*/true, ReleasedAllocatedMemory,
+ /*RetNullOnFailure=*/true);
+
+ C.addTransition(State);
}
ProgramStateRef MallocChecker::MallocMemReturnsAttr(CheckerContext &C,
@@ -1356,12 +1371,8 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state,
return state;
}
-// Check if the function is known to us. So, for example, we could
-// conservatively assume it can free/reallocate its pointer arguments.
-// (We assume that the pointers cannot escape through calls to system
-// functions not handled by this checker.)
-bool MallocChecker::doesNotFreeMemory(const CallEvent *Call,
- ProgramStateRef State) const {
+bool MallocChecker::doesNotFreeMemOrInteresting(const CallEvent *Call,
+ ProgramStateRef State) const {
assert(Call);
// For now, assume that any C++ call can free memory.
@@ -1378,24 +1389,23 @@ bool MallocChecker::doesNotFreeMemory(const CallEvent *Call,
if (!Call->isInSystemHeader() || Call->hasNonZeroCallbackArg())
return false;
- Selector S = Msg->getSelector();
-
- // Whitelist the ObjC methods which do free memory.
- // - Anything containing 'freeWhenDone' param set to 1.
- // Ex: dataWithBytesNoCopy:length:freeWhenDone.
- for (unsigned i = 1; i < S.getNumArgs(); ++i) {
- if (S.getNameForSlot(i).equals("freeWhenDone")) {
- if (Call->getArgSVal(i).isConstant(1))
- return false;
- else
- return true;
- }
- }
+ // If it's a method we know about, handle it explicitly post-call.
+ // This should happen before the "freeWhenDone" check below.
+ if (isKnownDeallocObjCMethodName(*Msg))
+ return true;
- // If the first selector ends with NoCopy, assume that the ownership is
- // transferred as well.
- // Ex: [NSData dataWithBytesNoCopy:bytes length:10];
- StringRef FirstSlot = S.getNameForSlot(0);
+ // If there's a "freeWhenDone" parameter, but the method isn't one we know
+ // about, we can't be sure that the object will use free() to deallocate the
+ // memory, so we can't model it explicitly. The best we can do is use it to
+ // decide whether the pointer escapes.
+ if (Optional<bool> FreeWhenDone = getFreeWhenDoneArg(*Msg))
+ return !*FreeWhenDone;
+
+ // If the first selector piece ends with "NoCopy", and there is no
+ // "freeWhenDone" parameter set to zero, we know ownership is being
+ // transferred. Again, though, we can't be sure that the object will use
+ // free() to deallocate the memory, so we can't model it explicitly.
+ StringRef FirstSlot = Msg->getSelector().getNameForSlot(0);
if (FirstSlot.endswith("NoCopy"))
return false;
@@ -1504,11 +1514,11 @@ ProgramStateRef MallocChecker::checkPointerEscape(ProgramStateRef State,
const InvalidatedSymbols &Escaped,
const CallEvent *Call,
PointerEscapeKind Kind) const {
- // If we know that the call does not free memory, keep tracking the top
- // level arguments.
+ // If we know that the call does not free memory, or we want to process the
+ // call later, keep tracking the top level arguments.
if ((Kind == PSK_DirectEscapeOnCall ||
Kind == PSK_IndirectEscapeOnCall) &&
- doesNotFreeMemory(Call, State)) {
+ doesNotFreeMemOrInteresting(Call, State)) {
return State;
}
diff --git a/test/Analysis/Inputs/system-header-simulator-for-malloc.h b/test/Analysis/Inputs/system-header-simulator-for-malloc.h
new file mode 100644
index 0000000000..e76455655e
--- /dev/null
+++ b/test/Analysis/Inputs/system-header-simulator-for-malloc.h
@@ -0,0 +1,34 @@
+// Like the compiler, the static analyzer treats some functions differently if
+// they come from a system header -- for example, it is assumed that system
+// functions do not arbitrarily free() their parameters, and that some bugs
+// found in system headers cannot be fixed by the user and should be
+// suppressed.
+#pragma clang system_header
+
+typedef __typeof(sizeof(int)) size_t;
+void *malloc(size_t);
+void *calloc(size_t, size_t);
+void free(void *);
+
+
+#if __OBJC__
+
+#import "system-header-simulator-objc.h"
+
+@interface Wrapper : NSData
+- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len;
+@end
+
+@implementation Wrapper
+- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len {
+ return [self initWithBytesNoCopy:bytes length:len freeWhenDone:1]; // no-warning
+}
+@end
+
+@interface CustomData : NSData
++ (id)somethingNoCopy:(char *)bytes;
++ (id)somethingNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer;
++ (id)something:(char *)bytes freeWhenDone:(BOOL)freeBuffer;
+@end
+
+#endif
diff --git a/test/Analysis/malloc.mm b/test/Analysis/malloc.mm
index f2a195ce1d..2f583b45fd 100644
--- a/test/Analysis/malloc.mm
+++ b/test/Analysis/malloc.mm
@@ -1,19 +1,6 @@
// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.Malloc -analyzer-store=region -verify -fblocks %s
-#include "Inputs/system-header-simulator-objc.h"
-
-typedef __typeof(sizeof(int)) size_t;
-void *malloc(size_t);
-void free(void *);
-
-@interface Wrapper : NSData
-- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len;
-@end
-
-@implementation Wrapper
-- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len {
- return [self initWithBytesNoCopy:bytes length:len freeWhenDone:1]; // no-warning
-}
-@end
+#import "Inputs/system-header-simulator-objc.h"
+#import "Inputs/system-header-simulator-for-malloc.h"
// Done with headers. Start testing.
void testNSDatafFreeWhenDoneNoError(NSUInteger dataLength) {
@@ -79,6 +66,11 @@ void testNSStringFreeWhenDoneNO2(NSUInteger dataLength) {
NSString *nsstr = [[NSString alloc] initWithCharactersNoCopy:data length:dataLength freeWhenDone:0]; // expected-warning{{leak}}
}
+void testOffsetFree() {
+ int *p = (int *)malloc(sizeof(int));
+ NSData *nsdata = [NSData dataWithBytesNoCopy:++p length:sizeof(int) freeWhenDone:1]; // expected-warning{{Argument to free() is offset by 4 bytes from the start of memory allocated by malloc()}}
+}
+
void testRelinquished1() {
void *data = malloc(42);
NSData *nsdata = [NSData dataWithBytesNoCopy:data length:42 freeWhenDone:1];
@@ -92,6 +84,31 @@ void testRelinquished2() {
[NSData dataWithBytesNoCopy:data length:42]; // expected-warning {{Attempt to free released memory}}
}
+void testNoCopy() {
+ char *p = (char *)calloc(sizeof(int), 1);
+ CustomData *w = [CustomData somethingNoCopy:p]; // no-warning
+}
+
+void testFreeWhenDone() {
+ char *p = (char *)calloc(sizeof(int), 1);
+ CustomData *w = [CustomData something:p freeWhenDone:1]; // no-warning
+}
+
+void testFreeWhenDonePositive() {
+ char *p = (char *)calloc(sizeof(int), 1);
+ CustomData *w = [CustomData something:p freeWhenDone:0]; // expected-warning{{leak}}
+}
+
+void testFreeWhenDoneNoCopy() {
+ int *p = (int *)malloc(sizeof(int));
+ CustomData *w = [CustomData somethingNoCopy:p length:sizeof(int) freeWhenDone:1]; // no-warning
+}
+
+void testFreeWhenDoneNoCopyPositive() {
+ int *p = (int *)malloc(sizeof(int));
+ CustomData *w = [CustomData somethingNoCopy:p length:sizeof(int) freeWhenDone:0]; // expected-warning{{leak}}
+}
+
// Test CF/NS...NoCopy. PR12100: Pointers can escape when custom deallocators are provided.
void testNSDatafFreeWhenDone(NSUInteger dataLength) {
CFStringRef str;