From: Ted Kremenek Date: Fri, 24 Apr 2009 17:50:11 +0000 (+0000) Subject: retain/release checker: X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=de4d5339e0811c8c8226aee8e00dc9a25bd5ebcf;p=clang retain/release checker: - Fix summary lookup for class methods to now use the (optional) ObjCInterfaceDecl associated with a message expression. This removes a long-standing FIXME. - Partial fix for by stop tracking objects that are passed to [NSObject performSelector]. These methods are often used for delegates, which the analyzer doesn't reason about well yet. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@69982 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Analysis/CFRefCount.cpp b/lib/Analysis/CFRefCount.cpp index b63078b691..564f15bf5b 100644 --- a/lib/Analysis/CFRefCount.cpp +++ b/lib/Analysis/CFRefCount.cpp @@ -659,34 +659,51 @@ private: Selector S = GetNullarySelector(nullaryName, Ctx); ObjCMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ; } - - void addInstMethSummary(const char* Cls, RetainSummary* Summ, va_list argp) { - - IdentifierInfo* ClsII = &Ctx.Idents.get(Cls); + + Selector generateSelector(va_list argp) { llvm::SmallVector II; - + while (const char* s = va_arg(argp, const char*)) II.push_back(&Ctx.Idents.get(s)); - - Selector S = Ctx.Selectors.getSelector(II.size(), &II[0]); - ObjCMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ; + + return Ctx.Selectors.getSelector(II.size(), &II[0]); + } + + void addMethodSummary(IdentifierInfo *ClsII, ObjCMethodSummariesTy& Summaries, + RetainSummary* Summ, va_list argp) { + Selector S = generateSelector(argp); + Summaries[ObjCSummaryKey(ClsII, S)] = Summ; } void addInstMethSummary(const char* Cls, RetainSummary* Summ, ...) { va_list argp; va_start(argp, Summ); - addInstMethSummary(Cls, Summ, argp); + addMethodSummary(&Ctx.Idents.get(Cls), ObjCMethodSummaries, Summ, argp); va_end(argp); } - + + void addClsMethSummary(const char* Cls, RetainSummary* Summ, ...) { + va_list argp; + va_start(argp, Summ); + addMethodSummary(&Ctx.Idents.get(Cls),ObjCClassMethodSummaries, Summ, argp); + va_end(argp); + } + + void addClsMethSummary(IdentifierInfo *II, RetainSummary* Summ, ...) { + va_list argp; + va_start(argp, Summ); + addMethodSummary(II, ObjCClassMethodSummaries, Summ, argp); + va_end(argp); + } + void addPanicSummary(const char* Cls, ...) { RetainSummary* Summ = getPersistentSummary(0, RetEffect::MakeNoRet(), DoNothing, DoNothing, true); va_list argp; va_start (argp, Cls); - addInstMethSummary(Cls, Summ, argp); + addMethodSummary(&Ctx.Idents.get(Cls), ObjCMethodSummaries, Summ, argp); va_end(argp); - } + } public: @@ -1100,23 +1117,30 @@ RetainSummaryManager::getMethodSummary(ObjCMessageExpr* ME, } RetainSummary* -RetainSummaryManager::getClassMethodSummary(ObjCMessageExpr* ME) { - - // FIXME: Eventually we should properly do class method summaries, but - // it requires us being able to walk the type hierarchy. Unfortunately, - // we cannot do this with just an IdentifierInfo* for the class name. - IdentifierInfo* ClsName = ME->getClassName(); +RetainSummaryManager::getClassMethodSummary(ObjCMessageExpr *ME) { + Selector S = ME->getSelector(); + ObjCMethodSummariesTy::iterator I; + + if (ObjCInterfaceDecl *ID = ME->getClassInfo().first) { + // Lookup the method using the decl for the class @interface. + I = ObjCClassMethodSummaries.find(ID, S); + } + else { + // Fallback to using the class name. + IdentifierInfo *ClsName = ME->getClassName(); - // Look up a summary in our cache of Selectors -> Summaries. - ObjCMethodSummariesTy::iterator I = ObjCClassMethodSummaries.find(ClsName, S); + // Look up a summary in our cache of Selectors -> Summaries. + I = ObjCClassMethodSummaries.find(ClsName, S); + } if (I != ObjCClassMethodSummaries.end()) return I->second; RetainSummary* Summ = getCommonMethodSummary(ME, S.getIdentifierInfoForSlot(0)->getName()); - ObjCClassMethodSummaries[ObjCSummaryKey(ClsName, S)] = Summ; + + ObjCClassMethodSummaries[ObjCSummaryKey(ME->getClassName(), S)] = Summ; return Summ; } @@ -1146,6 +1170,28 @@ void RetainSummaryManager::InitializeClassMethodSummaries() { GetUnarySelector("addObject", Ctx), getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, Autorelease)); + + // Create the summaries for [NSObject performSelector...]. We treat + // these as 'stop tracking' for the arguments because they are often + // used for delegates that can release the object. When we have better + // inter-procedural analysis we can potentially do something better. This + // workaround is to remove false positives. + Summ = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, StopTracking); + IdentifierInfo *NSObjectII = &Ctx.Idents.get("NSObject"); + addClsMethSummary(NSObjectII, Summ, "performSelector", "withObject", + "afterDelay", NULL); + addClsMethSummary(NSObjectII, Summ, "performSelector", "withObject", + "afterDelay", "inModes", NULL); + addClsMethSummary(NSObjectII, Summ, "performSelectorOnMainThread", + "withObject", "waitUntilDone", NULL); + addClsMethSummary(NSObjectII, Summ, "performSelectorOnMainThread", + "withObject", "waitUntilDone", "modes", NULL); + addClsMethSummary(NSObjectII, Summ, "performSelector", "onThread", + "withObject", "waitUntilDone", NULL); + addClsMethSummary(NSObjectII, Summ, "performSelector", "onThread", + "withObject", "waitUntilDone", "modes", NULL); + addClsMethSummary(NSObjectII, Summ, "performSelectorInBackground", + "withObject", NULL); } void RetainSummaryManager::InitializeMethodSummaries() { @@ -2012,6 +2058,7 @@ void CFRefCount::EvalObjCMessageExpr(ExplodedNodeSet& Dst, else Summ = Summaries.getClassMethodSummary(ME); + EvalSummary(Dst, Eng, Builder, ME, ME->getReceiver(), Summ, ME->arg_begin(), ME->arg_end(), Pred); } diff --git a/test/Analysis/delegates.m b/test/Analysis/delegates.m new file mode 100644 index 0000000000..440f31113c --- /dev/null +++ b/test/Analysis/delegates.m @@ -0,0 +1,114 @@ +// RUN: clang-cc -analyze -checker-cfref -analyzer-store=basic -verify %s && +// RUN: clang-cc -analyze -checker-cfref -analyzer-store=region -verify %s + + +//===----------------------------------------------------------------------===// +// The following code is reduced using delta-debugging from +// Foundation.h (Mac OS X). +// +// It includes the basic definitions for the test cases below. +// Not directly including Foundation.h directly makes this test case +// both svelte and portable to non-Mac platforms. +//===----------------------------------------------------------------------===// + +typedef const void * CFTypeRef; +typedef const struct __CFString * CFStringRef; +typedef const struct __CFAllocator * CFAllocatorRef; +extern const CFAllocatorRef kCFAllocatorDefault; +extern CFTypeRef CFRetain(CFTypeRef cf); +void CFRelease(CFTypeRef cf); +typedef const struct __CFDictionary * CFDictionaryRef; +const void *CFDictionaryGetValue(CFDictionaryRef theDict, const void *key); +extern CFStringRef CFStringCreateWithFormat(CFAllocatorRef alloc, CFDictionaryRef formatOptions, CFStringRef format, ...); +typedef signed char BOOL; +typedef int NSInteger; +typedef unsigned int NSUInteger; +typedef struct objc_selector *SEL; +@class NSString, Protocol; +extern void NSLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2))); +typedef NSInteger NSComparisonResult; +typedef struct _NSZone NSZone; +@class NSInvocation, NSMethodSignature, NSCoder, NSString, NSEnumerator; +@protocol NSObject +- (BOOL)isEqual:(id)object; +- (oneway void)release; +- (Class)class; +- (id)retain; +@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)init; ++ (id)alloc; ++ (Class)class; +- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; +@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; +typedef struct _NSRange {} NSRange; +@interface NSArray : NSObject +- (NSUInteger)count; +@end +@interface NSMutableArray : NSArray +- (void)addObject:(id)anObject; +- (id)initWithCapacity:(NSUInteger)numItems; +@end +typedef unsigned short unichar; +@class NSData, NSArray, NSDictionary, NSCharacterSet, NSData, NSURL, NSError, NSLocale; +typedef NSUInteger NSStringCompareOptions; +@interface NSString : NSObject - (NSUInteger)length; +- (NSComparisonResult)compare:(NSString *)string; +- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask; +- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)compareRange; +- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)compareRange locale:(id)locale; +- (NSComparisonResult)caseInsensitiveCompare:(NSString *)string; +- (NSArray *)componentsSeparatedByCharactersInSet:(NSCharacterSet *)separator; +@end +@interface NSSimpleCString : NSString {} @end +@interface NSConstantString : NSSimpleCString @end +extern void *_NSConstantStringClassReference; + +//===----------------------------------------------------------------------===// +// Test cases. +//===----------------------------------------------------------------------===// + +// +// The analyzer doesn't perform any inter-procedural analysis, so delegates +// involving [NSObject performSelector...] tend to lead to false positives. +// For now the analyzer just stops tracking the reference count of the +// receiver until we have better support for delegates. + +@interface test_6062730 : NSObject ++ (void)postNotification:(NSString *)str; +- (void)foo; +- (void)bar; +@end + +@implementation test_6062730 +- (void) foo { + NSString *str = [[NSString alloc] init]; + [test_6062730 performSelectorOnMainThread:@selector(postNotification:) withObject:str waitUntilDone:1]; +} + +- (void) bar { + NSString *str = [[NSString alloc] init]; // expected-warning{{leak}} + // FIXME: We need to resolve [self class] to 'test_6062730'. + [[self class] performSelectorOnMainThread:@selector(postNotification:) withObject:str waitUntilDone:1]; +} + ++ (void) postNotification:(NSString *)str { + [str release]; // no-warning +} +@end +