diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index d6dc97a82c19..9fe34f552529 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -180,7 +180,8 @@ private: /// 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 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state, 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(D) && isMemFunction(cast(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(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(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 @@ MallocChecker::checkRegionChanges(ProgramStateRef State, return State; llvm::SmallPtrSet WhitelistedSymbols; - const FunctionDecl *FD = (Call ? - dyn_cast_or_null(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::iterator I = ExplicitRegions.begin(), E = ExplicitRegions.end(); I != E; ++I) { if (const SymbolicRegion *R = (*I)->StripCasts()->getAs()) diff --git a/clang/test/Analysis/malloc.mm b/clang/test/Analysis/malloc.mm index ef3d1dee7054..efab640475f9 100644 --- a/clang/test/Analysis/malloc.mm +++ b/clang/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 {} -+ (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 - (void)getValue:(void *)value; -@end @interface NSNumber : NSValue - (char)charValue; -- (id)initWithInt:(int)value; -@end @class NSString; -@interface NSArray : NSObject - (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 - (NSTimeInterval)timeIntervalSinceReferenceDate; -@end typedef unsigned short unichar; -@interface NSString : NSObject -- (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 - (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 @@ void testNSDatafFreeWhenDoneNoError(NSUInteger dataLength) { 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/clang/test/Analysis/system-header-simulator-objc.h b/clang/test/Analysis/system-header-simulator-objc.h new file mode 100644 index 000000000000..9bfb7939e773 --- /dev/null +++ b/clang/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 {} ++ (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 - (void)getValue:(void *)value; +@end @interface NSNumber : NSValue - (char)charValue; +- (id)initWithInt:(int)value; +@end @class NSString; +@interface NSArray : NSObject - (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 - (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 +- (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 - (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