From 3cd89ad193834e766ce5dc24e260aa8615d0d5e1 Mon Sep 17 00:00:00 2001 From: Anna Zaks Date: Fri, 24 Feb 2012 23:56:53 +0000 Subject: [PATCH] [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 --- lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 76 +++++++++---- test/Analysis/malloc.mm | 105 ++++++++---------- test/Analysis/system-header-simulator-objc.h | 75 +++++++++++++ 3 files changed, 177 insertions(+), 79 deletions(-) create mode 100644 test/Analysis/system-header-simulator-objc.h diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index d6dc97a82c..9fe34f5525 100644 --- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/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 { - ASTContext &ASTC = State->getStateManager().getContext(); +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; - // If it's one of the allocation functions we can reason about, we model it's - // behavior explicitly. - if (isMemFunction(FD, ASTC)) { + 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 (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/test/Analysis/malloc.mm b/test/Analysis/malloc.mm index ef3d1dee70..efab640475 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 {} -+ (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/test/Analysis/system-header-simulator-objc.h b/test/Analysis/system-header-simulator-objc.h new file mode 100644 index 0000000000..9bfb7939e7 --- /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 {} ++ (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 -- 2.40.0