[analyzer] Malloc: reason about the ObjC messages and C++.

Assume none of the ObjC messages defined in system headers free memory,
except for the ones containing 'freeWhenDone' selector. Currently, just
assume that the region escapes to the messages with 'freeWhenDone'
(ideally, we want to treat it as 'free()').

For now, always assume that regions escape when passed to C++ methods.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@151410 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index d6dc97a..9fe34f5 100644
--- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -180,7 +180,8 @@
 
   /// 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 hasUnknownBehavior(const FunctionDecl *FD, ProgramStateRef State) const;
+  bool doesNotFreeMemory(const CallOrObjCMessage *Call,
+                         ProgramStateRef State) const;
 
   static bool SummarizeValue(raw_ostream &os, SVal V);
   static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR);
@@ -1053,38 +1054,75 @@
   return state;
 }
 
-// Check if the function is not known to us. So, for example, we could
+// Check if the function is known to us. So, for example, we could
 // conservatively assume it can free/reallocate it's pointer arguments.
 // (We assume that the pointers cannot escape through calls to system
 // functions not handled by this checker.)
-bool MallocChecker::hasUnknownBehavior(const FunctionDecl *FD,
-                                       ProgramStateRef State) const {
+bool MallocChecker::doesNotFreeMemory(const CallOrObjCMessage *Call,
+                                      ProgramStateRef State) const {
+  if (!Call)
+    return false;
+
+  // For now, assume that any C++ call can free memory.
+  // TODO: If we want to be more optimistic here, we'll need to make sure that
+  // regions escape to C++ containers. They seem to do that even now, but for
+  // mysterious reasons.
+  if (Call->isCXXCall())
+    return false;
+
+  const Decl *D = Call->getDecl();
+  if (!D)
+    return false;
+
   ASTContext &ASTC = State->getStateManager().getContext();
 
-  // If it's one of the allocation functions we can reason about, we model it's
-  // behavior explicitly.
-  if (isMemFunction(FD, ASTC)) {
-    return false;
+  // If it's one of the allocation functions we can reason about, we model
+  // it's behavior explicitly.
+  if (isa<FunctionDecl>(D) && isMemFunction(cast<FunctionDecl>(D), ASTC)) {
+    return true;
   }
 
-  // Most system calls, do not free the memory.
+  // If it's not a system call, assume it frees memory.
   SourceManager &SM = ASTC.getSourceManager();
-  if (SM.isInSystemHeader(FD->getLocation())) {
-    const IdentifierInfo *II = FD->getIdentifier();
+  if (!SM.isInSystemHeader(D->getLocation()))
+    return false;
 
+  // Process C functions.
+  if (const FunctionDecl *FD  = dyn_cast_or_null<FunctionDecl>(D)) {
     // White list the system functions whose arguments escape.
+    const IdentifierInfo *II = FD->getIdentifier();
     if (II) {
       StringRef FName = II->getName();
       if (FName.equals("pthread_setspecific"))
-        return true;
+        return false;
     }
 
     // Otherwise, assume that the function does not free memory.
-    return false;
+    // Most system calls, do not free the memory.
+    return true;
+
+  // Process ObjC functions.
+  } else if (const ObjCMethodDecl * ObjCD = dyn_cast<ObjCMethodDecl>(D)) {
+    Selector S = ObjCD->getSelector();
+
+    // White list the ObjC functions 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;
+      }
+    }
+
+    // Otherwise, assume that the function does not free memory.
+    // Most system calls, do not free the memory.
+    return true;
   }
 
   // Otherwise, assume that the function can free memory.
-  return true;
+  return false;
+
 }
 
 // If the symbol we are tracking is invalidated, but not explicitly (ex: the &p
@@ -1100,13 +1138,11 @@
     return State;
   llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols;
 
-  const FunctionDecl *FD = (Call ?
-                            dyn_cast_or_null<FunctionDecl>(Call->getDecl()) :0);
-
   // If it's a call which might free or reallocate memory, we assume that all
-  // regions (explicit and implicit) escaped. Otherwise, whitelist explicit
-  // pointers; we still can track them.
-  if (!(FD && hasUnknownBehavior(FD, State))) {
+  // regions (explicit and implicit) escaped.
+
+  // Otherwise, whitelist explicit pointers; we still can track them.
+  if (!Call || doesNotFreeMemory(Call, State)) {
     for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(),
         E = ExplicitRegions.end(); I != E; ++I) {
       if (const SymbolicRegion *R = (*I)->StripCasts()->getAs<SymbolicRegion>())
diff --git a/test/Analysis/malloc.mm b/test/Analysis/malloc.mm
index ef3d1de..efab640 100644
--- a/test/Analysis/malloc.mm
+++ b/test/Analysis/malloc.mm
@@ -1,61 +1,5 @@
 // RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.Malloc -analyzer-store=region -verify %s
-
-typedef unsigned int UInt32;
-typedef signed long CFIndex;
-typedef signed char BOOL;
-typedef unsigned long NSUInteger;
-@class NSString, Protocol;
-extern void NSLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
-typedef struct _NSZone NSZone;
-@class NSInvocation, NSMethodSignature, NSCoder, NSString, NSEnumerator;
-@protocol NSObject
-- (BOOL)isEqual:(id)object;
-- (id)retain;
-- (oneway void)release;
-- (id)autorelease;
-- (id)init;
-@end  @protocol NSCopying  - (id)copyWithZone:(NSZone *)zone;
-@end  @protocol NSMutableCopying  - (id)mutableCopyWithZone:(NSZone *)zone;
-@end  @protocol NSCoding  - (void)encodeWithCoder:(NSCoder *)aCoder;
-@end
-@interface NSObject <NSObject> {}
-+ (id)allocWithZone:(NSZone *)zone;
-+ (id)alloc;
-- (void)dealloc;
-@end
-@interface NSObject (NSCoderMethods)
-- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder;
-@end
-extern id NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone);
-typedef struct {
-}
-NSFastEnumerationState;
-@protocol NSFastEnumeration  - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len;
-@end           @class NSString, NSDictionary;
-@interface NSValue : NSObject <NSCopying, NSCoding>  - (void)getValue:(void *)value;
-@end  @interface NSNumber : NSValue  - (char)charValue;
-- (id)initWithInt:(int)value;
-@end   @class NSString;
-@interface NSArray : NSObject <NSCopying, NSMutableCopying, NSCoding, NSFastEnumeration>  - (NSUInteger)count;
-@end  @interface NSArray (NSArrayCreation)  + (id)array;
-@end       @interface NSAutoreleasePool : NSObject {
-}
-- (void)drain;
-@end extern NSString * const NSBundleDidLoadNotification;
-typedef double NSTimeInterval;
-@interface NSDate : NSObject <NSCopying, NSCoding>  - (NSTimeInterval)timeIntervalSinceReferenceDate;
-@end            typedef unsigned short unichar;
-@interface NSString : NSObject <NSCopying, NSMutableCopying, NSCoding>
-- (NSUInteger)length;
-- (NSString *)stringByAppendingString:(NSString *)aString;
-- ( const char *)UTF8String;
-- (id)initWithUTF8String:(const char *)nullTerminatedCString;
-+ (id)stringWithUTF8String:(const char *)nullTerminatedCString;
-@end        @class NSString, NSURL, NSError;
-@interface NSData : NSObject <NSCopying, NSMutableCopying, NSCoding>  - (NSUInteger)length;
-+ (id)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length;
-+ (id)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(BOOL)b;
-@end 
+#include "system-header-simulator-objc.h"
 
 typedef __typeof(sizeof(int)) size_t;
 void *malloc(size_t);
@@ -68,8 +12,51 @@
   free(data); // no warning
 }
 
-// False Negative
-void testNSDatafFreeWhenDone(NSUInteger dataLength) {
+void testNSDataFreeWhenDoneYES(NSUInteger dataLength) {
+  unsigned char *data = (unsigned char *)malloc(42);
+  NSData *nsdata = [NSData dataWithBytesNoCopy:data length:dataLength freeWhenDone:1]; // no-warning
+}
+
+void testNSDataFreeWhenDoneYES2(NSUInteger dataLength) {
+  unsigned char *data = (unsigned char *)malloc(42);
+  NSData *nsdata = [[NSData alloc] initWithBytesNoCopy:data length:dataLength freeWhenDone:1]; // no-warning
+}
+
+
+void testNSStringFreeWhenDoneYES(NSUInteger dataLength) {
+  unsigned char *data = (unsigned char *)malloc(42);
+  NSString *nsstr = [[NSString alloc] initWithBytesNoCopy:data length:dataLength encoding:NSUTF8StringEncoding freeWhenDone:1]; // no-warning
+}
+
+void testNSStringFreeWhenDoneYES2(NSUInteger dataLength) {
+  unichar *data = (unichar*)malloc(42);
+  NSString *nsstr = [[NSString alloc] initWithCharactersNoCopy:data length:dataLength freeWhenDone:1]; // no-warning
+}
+
+
+void testNSDataFreeWhenDoneNO(NSUInteger dataLength) {
+  unsigned char *data = (unsigned char *)malloc(42);
+  NSData *nsdata = [NSData dataWithBytesNoCopy:data length:dataLength freeWhenDone:0]; // expected-warning{{leak}}
+}
+
+void testNSDataFreeWhenDoneNO2(NSUInteger dataLength) {
+  unsigned char *data = (unsigned char *)malloc(42);
+  NSData *nsdata = [[NSData alloc] initWithBytesNoCopy:data length:dataLength freeWhenDone:0]; // expected-warning{{leak}}
+}
+
+
+void testNSStringFreeWhenDoneNO(NSUInteger dataLength) {
+  unsigned char *data = (unsigned char *)malloc(42);
+  NSString *nsstr = [[NSString alloc] initWithBytesNoCopy:data length:dataLength encoding:NSUTF8StringEncoding freeWhenDone:0]; // expected-warning{{leak}}
+}
+
+void testNSStringFreeWhenDoneNO2(NSUInteger dataLength) {
+  unichar *data = (unichar*)malloc(42);
+  NSString *nsstr = [[NSString alloc] initWithCharactersNoCopy:data length:dataLength freeWhenDone:0]; // expected-warning{{leak}}
+}
+
+// TODO: False Negative.
+void testNSDatafFreeWhenDoneFN(NSUInteger dataLength) {
   unsigned char *data = (unsigned char *)malloc(42);
   NSData *nsdata = [NSData dataWithBytesNoCopy:data length:dataLength freeWhenDone:1];
   free(data); // false negative
diff --git a/test/Analysis/system-header-simulator-objc.h b/test/Analysis/system-header-simulator-objc.h
new file mode 100644
index 0000000..9bfb793
--- /dev/null
+++ b/test/Analysis/system-header-simulator-objc.h
@@ -0,0 +1,75 @@
+#pragma clang system_header
+
+typedef unsigned int UInt32;
+typedef signed long CFIndex;
+typedef signed char BOOL;
+typedef unsigned long NSUInteger;
+@class NSString, Protocol;
+extern void NSLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
+typedef struct _NSZone NSZone;
+@class NSInvocation, NSMethodSignature, NSCoder, NSString, NSEnumerator;
+@protocol NSObject
+- (BOOL)isEqual:(id)object;
+- (id)retain;
+- (oneway void)release;
+- (id)autorelease;
+- (id)init;
+@end  @protocol NSCopying  - (id)copyWithZone:(NSZone *)zone;
+@end  @protocol NSMutableCopying  - (id)mutableCopyWithZone:(NSZone *)zone;
+@end  @protocol NSCoding  - (void)encodeWithCoder:(NSCoder *)aCoder;
+@end
+@interface NSObject <NSObject> {}
++ (id)allocWithZone:(NSZone *)zone;
++ (id)alloc;
+- (void)dealloc;
+@end
+@interface NSObject (NSCoderMethods)
+- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder;
+@end
+extern id NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone);
+typedef struct {
+}
+NSFastEnumerationState;
+@protocol NSFastEnumeration  - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len;
+@end           @class NSString, NSDictionary;
+@interface NSValue : NSObject <NSCopying, NSCoding>  - (void)getValue:(void *)value;
+@end  @interface NSNumber : NSValue  - (char)charValue;
+- (id)initWithInt:(int)value;
+@end   @class NSString;
+@interface NSArray : NSObject <NSCopying, NSMutableCopying, NSCoding, NSFastEnumeration>  - (NSUInteger)count;
+@end  @interface NSArray (NSArrayCreation)  + (id)array;
+@end       @interface NSAutoreleasePool : NSObject {
+}
+- (void)drain;
+@end extern NSString * const NSBundleDidLoadNotification;
+typedef double NSTimeInterval;
+@interface NSDate : NSObject <NSCopying, NSCoding>  - (NSTimeInterval)timeIntervalSinceReferenceDate;
+@end            typedef unsigned short unichar;
+enum {
+    NSASCIIStringEncoding = 1,
+    NSNEXTSTEPStringEncoding = 2,
+    NSJapaneseEUCStringEncoding = 3,
+    NSUTF8StringEncoding = 4,
+    NSISOLatin1StringEncoding = 5,
+    NSSymbolStringEncoding = 6,
+    NSNonLossyASCIIStringEncoding = 7,
+};
+typedef NSUInteger NSStringEncoding;
+
+@interface NSString : NSObject <NSCopying, NSMutableCopying, NSCoding>
+- (NSUInteger)length;
+- (NSString *)stringByAppendingString:(NSString *)aString;
+- ( const char *)UTF8String;
+- (id)initWithUTF8String:(const char *)nullTerminatedCString;
+- (id)initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer;
+- (id)initWithCharacters:(const unichar *)characters length:(NSUInteger)length;
+- (id)initWithBytes:(const void *)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding;
+- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding freeWhenDone:(BOOL)freeBuffer;
++ (id)stringWithUTF8String:(const char *)nullTerminatedCString;
+@end        @class NSString, NSURL, NSError;
+@interface NSData : NSObject <NSCopying, NSMutableCopying, NSCoding>  - (NSUInteger)length;
++ (id)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length;
++ (id)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(BOOL)b;
+- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)length;
+- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(BOOL)b;
+@end