diff options
author | thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-08-12 17:21:32 +0000 |
---|---|---|
committer | thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-08-12 17:21:32 +0000 |
commit | 7063d76a007fbf636250d7199d6f24ec487163b1 (patch) | |
tree | 5a1f2f0a4b597f62df3e2fe858d76d37b22bbe89 /Foundation | |
parent | 43982f07ba6a0a9839e32e774855c9d2068e9d5e (diff) |
- Added GTMNSMakeUncollectable for forcing objects to survive in a GC world.
- Added GTMCFAutorelease to make the [GTMNSMakeCollectable(cfFoo) autorelease] simpler and clearer, it's now just GTMCFAutorelease(cfFoo), and works in both GC and non-GC world.
- Added GTMIsGarbageCollectionEnabled to GTMGarbageCollection.h. See the note there for it's usage.
- Disabled the unittests for things on top of NSAppleScript in a GC world since Apple has bugs and it can crash. See the unittest for a note about it.
- GTMStackTrace now can figure out ObjC symbols. Downside it is now ObjC only.
- GTMFourCharCode can now be used with NSAppleEventDescriptors easily. typeType, typeKeyword, typeApplSignature, and typeEnumerated all get turned into GTMFourCharCodes.
- Fixed up crash in GTMLoggerRingBufferWriter when used with GC on.
- Significant updates to GTMNSAppleScript+Handler allowing you to list all handlers and properties (including inherited) and cleans up several errors in how scripting was being handled.
Diffstat (limited to 'Foundation')
35 files changed, 1585 insertions, 474 deletions
diff --git a/Foundation/GTMFourCharCode.m b/Foundation/GTMFourCharCode.m index 6b67cae..f5a08bf 100644 --- a/Foundation/GTMFourCharCode.m +++ b/Foundation/GTMFourCharCode.m @@ -25,7 +25,7 @@ @implementation GTMFourCharCode + (id)stringWithFourCharCode:(FourCharCode)code { - return [GTMNSMakeCollectable(UTCreateStringForOSType(code)) autorelease]; + return GTMCFAutorelease(UTCreateStringForOSType(code)); } + (id)fourCharCodeWithString:(NSString*)string { @@ -88,7 +88,7 @@ } - (NSString*)stringValue { - return [GTMNSMakeCollectable(UTCreateStringForOSType(code_)) autorelease]; + return GTMCFAutorelease(UTCreateStringForOSType(code_)); } - (NSNumber*)numberValue { diff --git a/Foundation/GTMGarbageCollection.h b/Foundation/GTMGarbageCollection.h index 4a7ac56..7f2873c 100644 --- a/Foundation/GTMGarbageCollection.h +++ b/Foundation/GTMGarbageCollection.h @@ -18,21 +18,40 @@ #import <Foundation/Foundation.h> +#import "GTMDefines.h" + // This allows us to easily move our code from GC to non GC. // They are no-ops unless we are require Leopard or above. // See // http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/index.html +// and +// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/Articles/gcCoreFoundation.html#//apple_ref/doc/uid/TP40006687-SW1 // for details. -// General use would be -// CFTypeRef type = ... -// return [GTMNSMakeCollectable(type) autorelease]; - -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#if (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050) && !GTM_IPHONE_SDK +// General use would be to call this through GTMCFAutorelease +// but there may be a reason the you want to make something collectable +// but not autoreleased, especially in pure GC code where you don't +// want to bother with the nop autorelease. FOUNDATION_STATIC_INLINE id GTMNSMakeCollectable(CFTypeRef cf) { return NSMakeCollectable(cf); } +// GTMNSMakeUncollectable is for global maps, etc. that we don't +// want released ever. You should still retain these in non-gc code. +FOUNDATION_STATIC_INLINE void GTMNSMakeUncollectable(id object) { + [[NSGarbageCollector defaultCollector] disableCollectorForPointer:object]; +} + +// Hopefully no code really needs this, but GTMIsGarbageCollectionEnabled is +// a common way to check at runtime if GC is on. +// There are some places where GC doesn't work w/ things w/in Apple's +// frameworks, so this is here so GTM unittests and detect it, and not run +// individual tests to work around bugs in Apple's frameworks. +FOUNDATION_STATIC_INLINE BOOL GTMIsGarbageCollectionEnabled(void) { + return ([NSGarbageCollector defaultCollector] != nil); +} + #else FOUNDATION_STATIC_INLINE id GTMNSMakeCollectable(CFTypeRef cf) { @@ -40,4 +59,19 @@ FOUNDATION_STATIC_INLINE id GTMNSMakeCollectable(CFTypeRef cf) { return (id)cf; } +FOUNDATION_STATIC_INLINE void GTMNSMakeUncollectable(id object) { +} + +FOUNDATION_STATIC_INLINE BOOL GTMIsGarbageCollectionEnabled(void) { + return NO; +} + #endif + +// GTMCFAutorelease makes a CF object collectable in GC mode, or adds it +// to the autorelease pool in non-GC mode. Either way it is taken care +// of. +FOUNDATION_STATIC_INLINE id GTMCFAutorelease(CFTypeRef cf) { + return [GTMNSMakeCollectable(cf) autorelease]; +} + diff --git a/Foundation/GTMGeometryUtils.h b/Foundation/GTMGeometryUtils.h index e8a078c..9691ffc 100644 --- a/Foundation/GTMGeometryUtils.h +++ b/Foundation/GTMGeometryUtils.h @@ -386,8 +386,6 @@ CG_INLINE NSRect GTMNSRectScale(NSRect inRect, CGFloat xScale, CGFloat yScale) { inRect.size.width * xScale, inRect.size.height * yScale); } - - /// Align rectangles // // Args: diff --git a/Foundation/GTMGeometryUtilsTest.m b/Foundation/GTMGeometryUtilsTest.m index ca817f8..5a59c8b 100644 --- a/Foundation/GTMGeometryUtilsTest.m +++ b/Foundation/GTMGeometryUtilsTest.m @@ -24,7 +24,7 @@ @implementation GTMGeometryUtilsTest -#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) +#if !GTM_IPHONE_SDK - (void)testGTMCGPointToNSPoint { CGPoint cgPoint = CGPointMake(15.1,6.2); NSPoint nsPoint = GTMCGPointToNSPoint(cgPoint); @@ -43,7 +43,6 @@ STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil); } - - (void)testGTMNSRectToCGRect { NSRect nsRect = NSMakeRect(4.6,3.2,22.1,45.0); CGRect cgRect = GTMNSRectToCGRect(nsRect); @@ -80,6 +79,99 @@ point = GTMNSMidMinY(rect); STAssertEqualsWithAccuracy(point.y, (CGFloat)0.0, (CGFloat)0.01, nil); STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); + + point = GTMNSCenter(rect); + STAssertEqualsWithAccuracy(point.y, (CGFloat)1.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); +} + +- (void)testGTMNSRectSize { + NSSize nsSize = GTMNSRectSize(NSMakeRect(1, 1, 10, 5)); + STAssertEqualsWithAccuracy(nsSize.width, (CGFloat)10.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(nsSize.height, (CGFloat)5.0, (CGFloat)0.01, nil); +} + +- (void)testGTMNSRectOfSize { + NSRect outRect = GTMNSRectOfSize(NSMakeSize(10, 5)); + NSRect expectedRect = NSMakeRect(0, 0, 10, 5); + STAssertEquals(outRect, expectedRect, nil); +} + +- (void)testGTMNSAlignRectangles { + typedef struct { + NSPoint expectedOrigin; + GTMRectAlignment alignment; + } TestData; + + TestData data[] = { + { {1,2}, GTMRectAlignTop }, + { {0,2}, GTMRectAlignTopLeft }, + { {2,2}, GTMRectAlignTopRight }, + { {0,1}, GTMRectAlignLeft }, + { {1,0}, GTMRectAlignBottom }, + { {0,0}, GTMRectAlignBottomLeft }, + { {2,0}, GTMRectAlignBottomRight }, + { {2,1}, GTMRectAlignRight }, + { {1,1}, GTMRectAlignCenter }, + }; + + NSRect rect1 = NSMakeRect(0, 0, 4, 4); + NSRect rect2 = NSMakeRect(0, 0, 2, 2); + + NSRect expectedRect; + expectedRect.size = NSMakeSize(2, 2); + + for (size_t i = 0; i < sizeof(data) / sizeof(TestData); i++) { + expectedRect.origin = data[i].expectedOrigin; + NSRect outRect = GTMNSAlignRectangles(rect2, rect1, data[i].alignment); + STAssertEquals(outRect, expectedRect, nil); + } +} + +- (void)testGTMNSScaleRectangleToSize { + NSRect rect = NSMakeRect(0.0f, 0.0f, 10.0f, 10.0f); + typedef struct { + NSSize size_; + NSSize newSize_; + } Test; + Test tests[] = { + { { 5.0, 10.0 }, { 5.0, 5.0 } }, + { { 10.0, 5.0 }, { 5.0, 5.0 } }, + { { 10.0, 10.0 }, { 10.0, 10.0 } }, + { { 11.0, 11.0, }, { 10.0, 10.0 } }, + { { 5.0, 2.0 }, { 2.0, 2.0 } }, + { { 2.0, 5.0 }, { 2.0, 2.0 } }, + { { 2.0, 2.0 }, { 2.0, 2.0 } }, + { { 0.0, 10.0 }, { 0.0, 0.0 } } + }; + + for (size_t i = 0; i < sizeof(tests) / sizeof(Test); ++i) { + NSRect result = GTMNSScaleRectangleToSize(rect, tests[i].size_, + GTMScaleProportionally); + STAssertEquals(result, GTMNSRectOfSize(tests[i].newSize_), @"failed on test %zd", i); + } + + NSRect result = GTMNSScaleRectangleToSize(NSZeroRect, tests[0].size_, + GTMScaleProportionally); + STAssertEquals(result, NSZeroRect, nil); + + result = GTMNSScaleRectangleToSize(rect, tests[0].size_, + GTMScaleToFit); + STAssertEquals(result, GTMNSRectOfSize(tests[0].size_), nil); + + result = GTMNSScaleRectangleToSize(rect, tests[0].size_, + GTMScaleNone); + STAssertEquals(result, rect, nil); +} + +- (void)testGTMNSDistanceBetweenPoints { + NSPoint pt1 = NSMakePoint(0, 0); + NSPoint pt2 = NSMakePoint(3, 4); + STAssertEquals(GTMNSDistanceBetweenPoints(pt1, pt2), (CGFloat)5.0, nil); + STAssertEquals(GTMNSDistanceBetweenPoints(pt2, pt1), (CGFloat)5.0, nil); + pt1 = NSMakePoint(1, 1); + pt2 = NSMakePoint(1, 1); + STAssertEquals(GTMNSDistanceBetweenPoints(pt1, pt2), (CGFloat)0.0, nil); } - (void)testGTMNSRectScaling { @@ -89,9 +181,9 @@ rect2, nil); } -#endif // #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) +#endif // !GTM_IPHONE_SDK -- (void)testGTMDistanceBetweenPoints { +- (void)testGTMCGDistanceBetweenPoints { CGPoint pt1 = CGPointMake(0, 0); CGPoint pt2 = CGPointMake(3, 4); STAssertEquals(GTMCGDistanceBetweenPoints(pt1, pt2), (CGFloat)5.0, nil); @@ -101,7 +193,7 @@ STAssertEquals(GTMCGDistanceBetweenPoints(pt1, pt2), (CGFloat)0.0, nil); } -- (void)testGTMAlignRectangles { +- (void)testGTMCGAlignRectangles { typedef struct { CGPoint expectedOrigin; GTMRectAlignment alignment; @@ -150,6 +242,22 @@ point = GTMCGMidMinY(rect); STAssertEqualsWithAccuracy(point.y, (CGFloat)0.0, (CGFloat)0.01, nil); STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); + + point = GTMCGCenter(rect); + STAssertEqualsWithAccuracy(point.y, (CGFloat)1.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); +} + +- (void)testGTMCGRectSize { + CGSize cgSize = GTMCGRectSize(CGRectMake(1, 1, 10, 5)); + STAssertEqualsWithAccuracy(cgSize.width, (CGFloat)10.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(cgSize.height, (CGFloat)5.0, (CGFloat)0.01, nil); +} + +- (void)testGTMCGRectOfSize { + CGRect outRect = GTMCGRectOfSize(CGSizeMake(10, 5)); + CGRect expectedRect = CGRectMake(0, 0, 10, 5); + STAssertEquals(outRect, expectedRect, nil); } - (void)testGTMCGRectScaling { @@ -158,8 +266,8 @@ STAssertEquals(GTMCGRectScale(rect, (CGFloat)0.2, (CGFloat)1.2), rect2, nil); } - -- (void)testGTMScaleRectangleToSize { + +- (void)testGTMCGScaleRectangleToSize { CGRect rect = CGRectMake(0.0f, 0.0f, 10.0f, 10.0f); typedef struct { CGSize size_; @@ -194,4 +302,5 @@ GTMScaleNone); STAssertEquals(result, rect, nil); } + @end diff --git a/Foundation/GTMHTTPFetcher.m b/Foundation/GTMHTTPFetcher.m index 96fa337..a73d752 100644 --- a/Foundation/GTMHTTPFetcher.m +++ b/Foundation/GTMHTTPFetcher.m @@ -20,6 +20,7 @@ #import "GTMHTTPFetcher.h" #import "GTMDebugSelectorValidation.h" +#import "GTMGarbageCollection.h" @interface GTMHTTPFetcher (GTMHTTPFetcherLoggingInternal) - (void)logFetchWithError:(NSError *)error; @@ -53,9 +54,9 @@ NSString* const kGTMLastModifiedHeader = @"Last-Modified"; NSString* const kGTMIfModifiedSinceHeader = @"If-Modified-Since"; -NSMutableArray* gGTMFetcherStaticCookies = nil; -Class gGTMFetcherConnectionClass = nil; -NSArray *gGTMFetcherDefaultRunLoopModes = nil; +static NSMutableArray* gGTMFetcherStaticCookies = nil; +static Class gGTMFetcherConnectionClass = nil; +static NSArray *gGTMFetcherDefaultRunLoopModes = nil; const NSTimeInterval kDefaultMaxRetryInterval = 60. * 10.; // 10 minutes @@ -81,6 +82,7 @@ const NSTimeInterval kDefaultMaxRetryInterval = 60. * 10.; // 10 minutes + (void)initialize { if (!gGTMFetcherStaticCookies) { gGTMFetcherStaticCookies = [[NSMutableArray alloc] init]; + GTMNSMakeUncollectable(gGTMFetcherStaticCookies); } } @@ -140,14 +142,16 @@ const NSTimeInterval kDefaultMaxRetryInterval = 60. * 10.; // 10 minutes GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(delegate, retrySEL_, @encode(BOOL), @encode(GTMHTTPFetcher *), @encode(BOOL), @encode(NSError *), NULL); if (connection_ != nil) { + // COV_NF_START - since we want the assert, we can't really test this _GTMDevAssert(connection_ != nil, @"fetch object %@ being reused; this should never happen", self); goto CannotBeginFetch; + // COV_NF_END } if (request_ == nil) { - _GTMDevAssert(request_ != nil, @"beginFetchWithDelegate requires a request"); + _GTMDevLog(@"beginFetchWithDelegate requires a request"); goto CannotBeginFetch; } @@ -254,9 +258,10 @@ const NSTimeInterval kDefaultMaxRetryInterval = 60. * 10.; // 10 minutes } if (!connection_) { - _GTMDevAssert(connection_ != nil, - @"beginFetchWithDelegate could not create a connection"); + // COV_NF_START - can't really create this case + _GTMDevLog(@"beginFetchWithDelegate could not create a connection"); goto CannotBeginFetch; + // COV_NF_END } // we'll retain the delegate only during the outstanding connection (similar @@ -1045,11 +1050,12 @@ CannotBeginFetch: + (void)setDefaultRunLoopModes:(NSArray *)modes { [gGTMFetcherDefaultRunLoopModes autorelease]; gGTMFetcherDefaultRunLoopModes = [modes retain]; + GTMNSMakeUncollectable(gGTMFetcherDefaultRunLoopModes); } + (Class)connectionClass { if (gGTMFetcherConnectionClass == nil) { - gGTMFetcherConnectionClass = [NSURLConnection class]; + gGTMFetcherConnectionClass = [NSURLConnection class]; } return gGTMFetcherConnectionClass; } @@ -1214,7 +1220,7 @@ CannotBeginFetch: } } else { - _GTMDevAssert(NO, @"Cookie incomplete: %@", newCookie); + _GTMDevAssert(NO, @"Cookie incomplete: %@", newCookie); // COV_NF_LINE } } } diff --git a/Foundation/GTMHTTPFetcherTest.m b/Foundation/GTMHTTPFetcherTest.m index 6bb99a1..3c461e0 100644 --- a/Foundation/GTMHTTPFetcherTest.m +++ b/Foundation/GTMHTTPFetcherTest.m @@ -51,8 +51,8 @@ - (BOOL)fixRequestFetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error; -- (void)testFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data; -- (void)testFetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error; +- (void)fetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data; +- (void)fetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error; @end @implementation GTMHTTPFetcherTest @@ -105,7 +105,6 @@ static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; } - (void)testValidFetch { - NSString *urlString = [self fileURLStringToTestFileName:kValidFileName]; GTMHTTPFetcher *fetcher = @@ -252,7 +251,6 @@ static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; } - (void)testRetryFetches { - GTMHTTPFetcher *fetcher; NSString *invalidFile = [kValidFileName stringByAppendingString:@"?status=503"]; @@ -362,8 +360,8 @@ static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; BOOL isFetching = [fetcher beginFetchWithDelegate:self - didFinishSelector:@selector(testFetcher:finishedWithData:) - didFailSelector:@selector(testFetcher:failedWithError:)]; + didFinishSelector:@selector(fetcher:finishedWithData:) + didFailSelector:@selector(fetcher:failedWithError:)]; STAssertTrue(isFetching, @"Begin fetch failed"); if (isFetching) { @@ -419,14 +417,14 @@ static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; -- (void)testFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data { +- (void)fetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data { fetchedData_ = [data copy]; fetchedStatus_ = [fetcher statusCode]; // this implicitly tests that the fetcher has kept the response fetchedRequest_ = [[fetcher request] retain]; fetchedResponse_ = [[fetcher response] retain]; } -- (void)testFetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error { +- (void)fetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error { // if it's a status error, don't hang onto the error, just the status/data if ([[error domain] isEqual:kGTMHTTPFetcherStatusDomain]) { fetchedData_ = [[[error userInfo] objectForKey:kGTMHTTPFetcherStatusDataKey] copy]; diff --git a/Foundation/GTMHTTPServer.h b/Foundation/GTMHTTPServer.h index c70178c..71522df 100644 --- a/Foundation/GTMHTTPServer.h +++ b/Foundation/GTMHTTPServer.h @@ -62,7 +62,7 @@ enum { // See comment at top of file for the intened use of this class. @interface GTMHTTPServer : NSObject { @private - id delegate_; + __weak id delegate_; uint16_t port_; BOOL localhostOnly_; NSFileHandle *listenHandle_; diff --git a/Foundation/GTMHTTPServer.m b/Foundation/GTMHTTPServer.m index daa6a4e..ffa294c 100644 --- a/Foundation/GTMHTTPServer.m +++ b/Foundation/GTMHTTPServer.m @@ -331,7 +331,7 @@ startFailed: response = [delegate_ httpServer:self handleRequest:request]; } @catch (NSException *e) { _GTMDevLog(@"Exception trying to handle http request: %@", e); - } + } // COV_NF_LINE - radar 5851992 only reachable w/ an uncaught exception which isn't testable if (!response) { [self closeConnection:connDict]; @@ -350,14 +350,16 @@ startFailed: } - (NSMutableDictionary *)lookupConnection:(NSFileHandle *)fileHandle { + NSMutableDictionary *result = nil; NSUInteger max = [connections_ count]; for (NSUInteger x = 0; x < max; ++x) { NSMutableDictionary *connDict = [connections_ objectAtIndex:x]; if (fileHandle == [connDict objectForKey:kFileHandle]) { - return connDict; + result = connDict; + break; } } - return nil; + return result; } - (void)closeConnection:(NSMutableDictionary *)connDict { @@ -367,6 +369,10 @@ startFailed: [center removeObserver:self name:NSFileHandleReadCompletionNotification object:connectionHandle]; + // in a non GC world, we're fine just letting the connect get closed when + // the object is release when it comes out of connections_, but in a GC world + // it won't get cleaned up + [connectionHandle closeFile]; // remove it from the list [connections_ removeObject:connDict]; @@ -380,11 +386,11 @@ startFailed: NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle]; NSData *serialized = [response serializedData]; [connectionHandle writeData:serialized]; - } @catch (NSException *e) { + } @catch (NSException *e) { // COV_NF_START - causing an exception here is to hard in a test // TODO: let the delegate know about the exception (but do it on the main // thread) _GTMDevLog(@"exception while sending reply: %@", e); - } + } // COV_NF_END // back to the main thread to close things down [self performSelectorOnMainThread:@selector(sentResponse:) @@ -421,28 +427,30 @@ startFailed: } - (void)dealloc { - CFRelease(message_); + if (message_) { + CFRelease(message_); + } [super dealloc]; } - (NSString *)version { - return [GTMNSMakeCollectable(CFHTTPMessageCopyVersion(message_)) autorelease]; + return GTMCFAutorelease(CFHTTPMessageCopyVersion(message_)); } - (NSURL *)URL { - return [GTMNSMakeCollectable(CFHTTPMessageCopyRequestURL(message_)) autorelease]; + return GTMCFAutorelease(CFHTTPMessageCopyRequestURL(message_)); } - (NSString *)method { - return [GTMNSMakeCollectable(CFHTTPMessageCopyRequestMethod(message_)) autorelease]; + return GTMCFAutorelease(CFHTTPMessageCopyRequestMethod(message_)); } - (NSData *)body { - return [GTMNSMakeCollectable(CFHTTPMessageCopyBody(message_)) autorelease]; + return GTMCFAutorelease(CFHTTPMessageCopyBody(message_)); } - (NSDictionary *)allHeaderFieldValues { - return GTMNSMakeCollectable(CFHTTPMessageCopyAllHeaderFields(message_)); + return GTMCFAutorelease(CFHTTPMessageCopyAllHeaderFields(message_)); } - (NSString *)description { @@ -471,7 +479,7 @@ startFailed: if (key) { value = CFHTTPMessageCopyHeaderFieldValue(message_, (CFStringRef)key); } - return [GTMNSMakeCollectable(value) autorelease]; + return GTMCFAutorelease(value); } - (UInt32)contentLength { @@ -578,7 +586,7 @@ startFailed: } - (NSData *)serializedData { - return [GTMNSMakeCollectable(CFHTTPMessageCopySerializedMessage(message_)) autorelease]; + return GTMCFAutorelease(CFHTTPMessageCopySerializedMessage(message_)); } @end diff --git a/Foundation/GTMHTTPServerTest.m b/Foundation/GTMHTTPServerTest.m index d96d54e..fd962af 100644 --- a/Foundation/GTMHTTPServerTest.m +++ b/Foundation/GTMHTTPServerTest.m @@ -52,9 +52,8 @@ // helper that throws while handling its request @interface TestThrowingServerDelegate : TestServerDelegate -// since this method ALWAYS throws, we can mark it as noreturn - (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server - handleRequest:(GTMHTTPRequestMessage *)request __attribute__ ((noreturn)); + handleRequest:(GTMHTTPRequestMessage *)request; @end // The timings used for waiting for replies @@ -369,8 +368,10 @@ const NSTimeInterval kSendChunkInterval = 0.05; // make sure we see the request at this point STAssertEquals([server activeRequestCount], (NSUInteger)1, @"should have started the request by now"); - // drop the pool to close the connection - [localPool release]; + // force the connection closed and drop the pool to get all the cleanup to + // happen. + [handle closeFile]; + [localPool drain]; // spin the run loop so it should see the close loopIntervalDate = [NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval]; [[NSRunLoop currentRunLoop] runUntilDate:loopIntervalDate]; diff --git a/Foundation/GTMLogger+ASLTest.m b/Foundation/GTMLogger+ASLTest.m index 97f7e29..cd32484 100644 --- a/Foundation/GTMLogger+ASLTest.m +++ b/Foundation/GTMLogger+ASLTest.m @@ -68,7 +68,6 @@ static NSMutableArray *gDummyLog; // weak // dummy writer will save the messages w/ @level concatenated. The "level" // will be the ASL level, not the GTMLogger level. GTMLogASLWriter will log // all - NSArray *log = gDummyLog; NSArray *expected = [NSArray arrayWithObjects: @"unknown@5", @"debug@5", @@ -77,7 +76,7 @@ static NSMutableArray *gDummyLog; // weak @"assert@1", nil]; - STAssertEqualObjects(log, expected, nil); + STAssertEqualObjects(gDummyLog, expected, nil); gDummyLog = nil; } diff --git a/Foundation/GTMLogger.m b/Foundation/GTMLogger.m index cf1d2c8..4da1034 100644 --- a/Foundation/GTMLogger.m +++ b/Foundation/GTMLogger.m @@ -341,7 +341,7 @@ static GTMLogger *gSharedLogger = nil; NULL, // format options (CFStringRef)fmt, args); - return [GTMNSMakeCollectable(cfmsg) autorelease]; + return GTMCFAutorelease(cfmsg); } @end // GTMLogBasicFormatter diff --git a/Foundation/GTMLoggerRingBufferWriter.h b/Foundation/GTMLoggerRingBufferWriter.h index e749a33..30e58ff 100644 --- a/Foundation/GTMLoggerRingBufferWriter.h +++ b/Foundation/GTMLoggerRingBufferWriter.h @@ -17,6 +17,7 @@ // #import "GTMLogger.h" +#import "GTMDefines.h" typedef struct GTMRingBufferPair GTMRingBufferPair; @@ -51,39 +52,39 @@ typedef struct GTMRingBufferPair GTMRingBufferPair; @private id<GTMLogWriter> writer_; GTMRingBufferPair *buffer_; - int capacity_; - int nextIndex_; // Index of the next element of |buffer_| to fill. - int totalLogged_; // This > 0 and |nextIndex_| == 0 means we've wrapped. + NSUInteger capacity_; + NSUInteger nextIndex_; // Index of the next element of |buffer_| to fill. + NSUInteger totalLogged_; // This > 0 and |nextIndex_| == 0 means we've wrapped. } -// Returns an autoreleased ring buffer writer. If |capacity| is -// non-positive, or |writer| is nil, then nil is returned. -+ (id)ringBufferWriterWithCapacity:(int)capacity +// Returns an autoreleased ring buffer writer. If |writer| is nil, +// then nil is returned. ++ (id)ringBufferWriterWithCapacity:(NSUInteger)capacity writer:(id<GTMLogWriter>)loggerWriter; -// Designated initializer. If |capacity| is non-positive, or |writer| -// is nil, then nil is returned. If you just use -init, nil will be returned. -- (id)initWithCapacity:(int)capacity +// Designated initializer. If |writer| is nil, then nil is returned. +// If you just use -init, nil will be returned. +- (id)initWithCapacity:(NSUInteger)capacity writer:(id<GTMLogWriter>)loggerWriter; // How many messages will be logged before older messages get dropped // on the floor. -- (int)capacity; +- (NSUInteger)capacity; // The log writer that will get the buffered log messages if/when they // need to be displayed. - (id<GTMLogWriter>)writer; // How many log messages are currently in the buffer. -- (int)count; +- (NSUInteger)count; // How many have been dropped on the floor since creation, or the last // reset. -- (int)droppedLogCount; +- (NSUInteger)droppedLogCount; // The total number of messages processed since creation, or the last // reset. -- (int)totalLogged; +- (NSUInteger)totalLogged; // Purge the contents and reset the counters. - (void)reset; diff --git a/Foundation/GTMLoggerRingBufferWriter.m b/Foundation/GTMLoggerRingBufferWriter.m index f5502b8..4a8c7c6 100644 --- a/Foundation/GTMLoggerRingBufferWriter.m +++ b/Foundation/GTMLoggerRingBufferWriter.m @@ -18,16 +18,13 @@ #import "GTMLoggerRingBufferWriter.h" -// Define a trivial assertion macro to avoid dependencies -#ifdef DEBUG -#define GTMLOGGER_ASSERT(expr) assert(expr) -#else -#define GTMLOGGER_ASSERT(expr) -#endif - // Holds a message and a level. struct GTMRingBufferPair { - NSString *logMessage_; + // Explicitly using CFStringRef instead of NSString because in a GC world, the + // NSString will be collected because there is no way for the GC to know that + // there is a strong reference to the NSString in this data structure. By + // using a CFStringRef we can CFRetain it, and avoid the problem. + CFStringRef logMessage_; GTMLoggerLevel level_; }; @@ -52,7 +49,7 @@ typedef void (GTMRingBufferPairCallback)(GTMLoggerRingBufferWriter *rbw, @implementation GTMLoggerRingBufferWriter -+ (id)ringBufferWriterWithCapacity:(int)capacity ++ (id)ringBufferWriterWithCapacity:(NSUInteger)capacity writer:(id<GTMLogWriter>)writer { GTMLoggerRingBufferWriter *rbw = [[[self alloc] initWithCapacity:capacity @@ -62,24 +59,21 @@ typedef void (GTMRingBufferPairCallback)(GTMLoggerRingBufferWriter *rbw, } // ringBufferWriterWithCapacity -- (id)initWithCapacity:(int)capacity +- (id)initWithCapacity:(NSUInteger)capacity writer:(id<GTMLogWriter>)writer { if ((self = [super init])) { - if (capacity > 0) { - writer_ = [writer retain]; - capacity_ = capacity; + writer_ = [writer retain]; + capacity_ = capacity; - buffer_ = calloc(capacity_, sizeof(GTMRingBufferPair)); + buffer_ = (GTMRingBufferPair *)calloc(capacity_, sizeof(GTMRingBufferPair)); - nextIndex_ = 0; - } + nextIndex_ = 0; - if (buffer_ == NULL || writer_ == nil) { + if (capacity_ == 0 || !buffer_ || !writer_) { [self release]; - return nil; + self = nil; } } - return self; } // initWithCapacity @@ -94,14 +88,16 @@ typedef void (GTMRingBufferPairCallback)(GTMLoggerRingBufferWriter *rbw, [self reset]; [writer_ release]; - free(buffer_); + if (buffer_) { + free(buffer_); + } [super dealloc]; } // dealloc -- (int)capacity { +- (NSUInteger)capacity { return capacity_; } // capacity @@ -111,8 +107,8 @@ typedef void (GTMRingBufferPairCallback)(GTMLoggerRingBufferWriter *rbw, } // writer -- (int)count { - int count = 0; +- (NSUInteger)count { + NSUInteger count = 0; @synchronized(self) { if ((nextIndex_ == 0 && totalLogged_ > 0) || totalLogged_ >= capacity_) { @@ -128,21 +124,23 @@ typedef void (GTMRingBufferPairCallback)(GTMLoggerRingBufferWriter *rbw, } // count -- (int)droppedLogCount { - int droppedCount = 0; +- (NSUInteger)droppedLogCount { + NSUInteger droppedCount = 0; @synchronized(self) { - droppedCount = totalLogged_ - capacity_; + if (capacity_ > totalLogged_) { + droppedCount = 0; + } else { + droppedCount = totalLogged_ - capacity_; + } } - if (droppedCount < 0) droppedCount = 0; - return droppedCount; } // droppedLogCount -- (int)totalLogged { +- (NSUInteger)totalLogged { return totalLogged_; } // totalLogged @@ -179,9 +177,11 @@ typedef void (GTMRingBufferPairCallback)(GTMLoggerRingBufferWriter *rbw, // the structure. static void ResetCallback(GTMLoggerRingBufferWriter *rbw, GTMRingBufferPair *pair) { - [pair->logMessage_ release]; + if (pair->logMessage_) { + CFRelease(pair->logMessage_); + } pair->logMessage_ = nil; - pair->level_ = 0; + pair->level_ = kGTMLoggerLevelUnknown; } // ResetCallback @@ -200,7 +200,7 @@ static void ResetCallback(GTMLoggerRingBufferWriter *rbw, // ring buffer's |writer_|. static void PrintContentsCallback(GTMLoggerRingBufferWriter *rbw, GTMRingBufferPair *pair) { - [[rbw writer] logMessage:pair->logMessage_ level:pair->level_]; + [[rbw writer] logMessage:(NSString*)pair->logMessage_ level:pair->level_]; } // PrintContentsCallback @@ -213,21 +213,21 @@ static void PrintContentsCallback(GTMLoggerRingBufferWriter *rbw, // Assumes caller will do any necessary synchronization. - (void)addMessage:(NSString *)message level:(GTMLoggerLevel)level { - int newIndex = nextIndex_; - nextIndex_ = (nextIndex_ + 1) % capacity_; - - ++totalLogged_; - - // Sanity check - GTMLOGGER_ASSERT(buffer_ != NULL); - GTMLOGGER_ASSERT(nextIndex_ >= 0 && nextIndex_ < capacity_); - GTMLOGGER_ASSERT(newIndex >= 0 && newIndex < capacity_); - - // Now store the goodies. - GTMRingBufferPair *pair = buffer_ + newIndex; - [pair->logMessage_ release]; - pair->logMessage_ = [message copy]; - pair->level_ = level; + NSUInteger newIndex = nextIndex_; + nextIndex_ = (nextIndex_ + 1) % capacity_; + + ++totalLogged_; + + // Now store the goodies. + GTMRingBufferPair *pair = buffer_ + newIndex; + if (pair->logMessage_) { + CFRelease(pair->logMessage_); + pair->logMessage_ = nil; + } + if (message) { + pair->logMessage_ = CFStringCreateCopy(kCFAllocatorDefault, (CFStringRef)message); + } + pair->level_ = level; } // addMessage @@ -235,7 +235,7 @@ static void PrintContentsCallback(GTMLoggerRingBufferWriter *rbw, // From the GTMLogWriter protocol. - (void)logMessage:(NSString *)message level:(GTMLoggerLevel)level { @synchronized(self) { - [self addMessage:message level:level]; + [self addMessage:(NSString*)message level:level]; if (level >= kGTMLoggerLevelError) { [self dumpContents]; diff --git a/Foundation/GTMLoggerRingBufferWriterTest.m b/Foundation/GTMLoggerRingBufferWriterTest.m index 6e64cc0..1c5d72b 100644 --- a/Foundation/GTMLoggerRingBufferWriterTest.m +++ b/Foundation/GTMLoggerRingBufferWriterTest.m @@ -19,7 +19,7 @@ #import "GTMSenTestCase.h" #import "GTMLoggerRingBufferWriter.h" #import "GTMLogger.h" - +#import "GTMUnitTestDevLog.h" // -------------------------------------------------- // CountingWriter keeps a count of the number of times it has been @@ -31,16 +31,17 @@ NSMutableArray *loggedContents_; } -- (int)count; +- (NSUInteger)count; - (NSArray *)loggedContents; - (void)reset; @end // CountingWriter @implementation CountingWriter - - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { - if (loggedContents_ == nil) loggedContents_ = [[NSMutableArray alloc] init]; + if (!loggedContents_) { + loggedContents_ = [[NSMutableArray alloc] init]; + } [loggedContents_ addObject:msg]; } // logMessage @@ -54,8 +55,8 @@ loggedContents_ = nil; } // reset -- (int)count { - return (int)[loggedContents_ count]; +- (NSUInteger)count { + return [loggedContents_ count]; } // count - (NSArray *)loggedContents { @@ -68,7 +69,7 @@ @interface GTMLoggerRingBufferWriterTest : GTMTestCase { @private GTMLogger *logger_; - __weak CountingWriter *countingWriter_; + CountingWriter *countingWriter_; } @end // GTMLoggerRingBufferWriterTest @@ -100,12 +101,13 @@ - (void)setUp { - countingWriter_ = [[[CountingWriter alloc] init] autorelease]; + countingWriter_ = [[CountingWriter alloc] init]; logger_ = [[GTMLogger alloc] init]; } // setUp - (void)tearDown { + [countingWriter_ release]; [logger_ release]; } // tearDown @@ -116,21 +118,18 @@ GTMLoggerRingBufferWriter *writer = [GTMLoggerRingBufferWriter ringBufferWriterWithCapacity:32 writer:countingWriter_]; - STAssertEquals([writer capacity], 32, nil); + STAssertEquals([writer capacity], (NSUInteger)32, nil); STAssertTrue([writer writer] == countingWriter_, nil); - STAssertEquals([writer count], 0, nil); - STAssertEquals([writer droppedLogCount], 0, nil); - STAssertEquals([writer totalLogged], 0, nil); + STAssertEquals([writer count], (NSUInteger)0, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)0, nil); + STAssertEquals([writer totalLogged], (NSUInteger)0, nil); // Try with invalid arguments. Should always get nil back. writer = [GTMLoggerRingBufferWriter ringBufferWriterWithCapacity:0 writer:countingWriter_]; STAssertNil(writer, nil); - writer = - [GTMLoggerRingBufferWriter ringBufferWriterWithCapacity:-1 - writer:countingWriter_]; - STAssertNil(writer, nil); + writer = [GTMLoggerRingBufferWriter ringBufferWriterWithCapacity:32 writer:nil]; STAssertNil(writer, nil); @@ -149,28 +148,28 @@ // Shouldn't do anything if there are no contents. [writer dumpContents]; - STAssertEquals([writer count], 0, nil); - STAssertEquals([countingWriter_ count], 0, nil); + STAssertEquals([writer count], (NSUInteger)0, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)0, nil); // Log a single item. Make sure the counts are accurate. [logger_ logDebug:@"oop"]; - STAssertEquals([writer count], 1, nil); - STAssertEquals([writer totalLogged], 1, nil); - STAssertEquals([writer droppedLogCount], 0, nil); - STAssertEquals([countingWriter_ count], 0, nil); + STAssertEquals([writer count], (NSUInteger)1, nil); + STAssertEquals([writer totalLogged], (NSUInteger)1, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)0, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)0, nil); // Log a second item. Also make sure counts are accurate. [logger_ logDebug:@"ack"]; - STAssertEquals([writer count], 2, nil); - STAssertEquals([writer totalLogged], 2, nil); - STAssertEquals([writer droppedLogCount], 0, nil); - STAssertEquals([countingWriter_ count], 0, nil); + STAssertEquals([writer count], (NSUInteger)2, nil); + STAssertEquals([writer totalLogged], (NSUInteger)2, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)0, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)0, nil); // Print them, and make sure the countingWriter sees the right stuff. [writer dumpContents]; - STAssertEquals([countingWriter_ count], 2, nil); - STAssertEquals([writer count], 2, nil); // Should not be zeroed. - STAssertEquals([writer totalLogged], 2, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)2, nil); + STAssertEquals([writer count], (NSUInteger)2, nil); // Should not be zeroed. + STAssertEquals([writer totalLogged], (NSUInteger)2, nil); [self compareWriter:countingWriter_ withExpectedLogging:[NSArray arrayWithObjects:@"oop",@"ack", nil] @@ -180,18 +179,18 @@ // Wipe the slates clean. [writer reset]; [countingWriter_ reset]; - STAssertEquals([writer count], 0, nil); - STAssertEquals([writer totalLogged], 0, nil); + STAssertEquals([writer count], (NSUInteger)0, nil); + STAssertEquals([writer totalLogged], (NSUInteger)0, nil); // An error log level should print the buffer and empty it. [logger_ logDebug:@"oop"]; [logger_ logInfo:@"ack"]; - STAssertEquals([writer droppedLogCount], 0, nil); - STAssertEquals([writer totalLogged], 2, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)0, nil); + STAssertEquals([writer totalLogged], (NSUInteger)2, nil); [logger_ logError:@"blargh"]; - STAssertEquals([countingWriter_ count], 3, nil); - STAssertEquals([writer droppedLogCount], 0, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)3, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)0, nil); [self compareWriter:countingWriter_ withExpectedLogging:[NSArray arrayWithObjects:@"oop", @"ack", @@ -205,13 +204,13 @@ [logger_ logDebug:@"oop"]; [logger_ logInfo:@"ack"]; [logger_ logDebug:@"blargh"]; - STAssertEquals([writer droppedLogCount], 0, nil); - STAssertEquals([writer count], 3, nil); - STAssertEquals([writer totalLogged], 3, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)0, nil); + STAssertEquals([writer count], (NSUInteger)3, nil); + STAssertEquals([writer totalLogged], (NSUInteger)3, nil); [logger_ logAssert:@"ouch"]; - STAssertEquals([countingWriter_ count], 4, nil); - STAssertEquals([writer droppedLogCount], 0, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)4, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)0, nil); [self compareWriter:countingWriter_ withExpectedLogging:[NSArray arrayWithObjects:@"oop", @"ack", @"blargh", @"ouch", nil] @@ -224,11 +223,11 @@ [logger_ logDebug:@"oop"]; [logger_ logDebug:@"blargh"]; [logger_ logDebug:@"flong"]; // Fills buffer - STAssertEquals([writer droppedLogCount], 0, nil); - STAssertEquals([writer count], 4, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)0, nil); + STAssertEquals([writer count], (NSUInteger)4, nil); [logger_ logAssert:@"ouch"]; // should drop "ack" - STAssertEquals([countingWriter_ count], 4, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)4, nil); [self compareWriter:countingWriter_ withExpectedLogging:[NSArray arrayWithObjects:@"oop", @"blargh", @@ -243,11 +242,11 @@ [logger_ logDebug:@"blargh"]; [logger_ logDebug:@"flong"]; // Fills buffer [logger_ logDebug:@"bloogie"]; // should drop "ack" - STAssertEquals([writer droppedLogCount], 1, nil); - STAssertEquals([writer count], 4, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)1, nil); + STAssertEquals([writer count], (NSUInteger)4, nil); [logger_ logAssert:@"ouch"]; // should drop "oop" - STAssertEquals([countingWriter_ count], 4, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)4, nil); [self compareWriter:countingWriter_ withExpectedLogging:[NSArray arrayWithObjects:@"blargh", @@ -265,22 +264,22 @@ [logger_ setWriter:writer]; [logger_ logInfo:@"ack"]; - STAssertEquals([countingWriter_ count], 0, nil); - STAssertEquals([writer count], 1, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)0, nil); + STAssertEquals([writer count], (NSUInteger)1, nil); [writer dumpContents]; - STAssertEquals([countingWriter_ count], 1, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)1, nil); [self compareWriter:countingWriter_ withExpectedLogging:[NSArray arrayWithObjects:@"ack", nil] line:__LINE__]; [logger_ logDebug:@"oop"]; // should drop "ack" - STAssertEquals([writer count], 1, nil); - STAssertEquals([writer droppedLogCount], 1, nil); + STAssertEquals([writer count], (NSUInteger)1, nil); + STAssertEquals([writer droppedLogCount], (NSUInteger)1, nil); [countingWriter_ reset]; [logger_ logError:@"snoogy"]; // should drop "oop" - STAssertEquals([countingWriter_ count], 1, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)1, nil); [self compareWriter:countingWriter_ withExpectedLogging:[NSArray arrayWithObjects:@"snoogy", nil] @@ -292,7 +291,7 @@ // Run 10 threads, all logging through the same logger. -static volatile int gStoppedThreads = 0; // Total number that have stopped. +static volatile NSUInteger gStoppedThreads = 0; // Total number that have stopped. - (void)bangMe:(id)info { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; @@ -320,15 +319,15 @@ static volatile int gStoppedThreads = 0; // Total number that have stopped. - (void)testThreading { - const int kThreadCount = 10; - const int kCapacity = 10; + const NSUInteger kThreadCount = 10; + const NSUInteger kCapacity = 10; GTMLoggerRingBufferWriter *writer = [GTMLoggerRingBufferWriter ringBufferWriterWithCapacity:kCapacity writer:countingWriter_]; [logger_ setWriter:writer]; - for (int i = 0; i < kThreadCount; i++) { + for (NSUInteger i = 0; i < kThreadCount; i++) { [NSThread detachNewThreadSelector:@selector(bangMe:) toTarget:self withObject:logger_]; @@ -345,8 +344,8 @@ static volatile int gStoppedThreads = 0; // Total number that have stopped. // Now make sure we get back what's expected. STAssertEquals([writer count], kThreadCount, nil); - STAssertEquals([countingWriter_ count], 0, nil); // Nothing should be logged - STAssertEquals([writer totalLogged], 420, nil); + STAssertEquals([countingWriter_ count], (NSUInteger)0, nil); // Nothing should be logged + STAssertEquals([writer totalLogged], (NSUInteger)420, nil); [logger_ logError:@"bork"]; STAssertEquals([countingWriter_ count], kCapacity, nil); diff --git a/Foundation/GTMNSAppleEvent+HandlerTest.applescript b/Foundation/GTMNSAppleEvent+HandlerTest.applescript index ec99433..a0822f9 100644 --- a/Foundation/GTMNSAppleEvent+HandlerTest.applescript +++ b/Foundation/GTMNSAppleEvent+HandlerTest.applescript @@ -14,6 +14,21 @@ -- the License. -- +script parentTestScript + property parentTestScriptProperty : 6 + on parentTestScriptFunc() + return "parent" + end parentTestScriptFunc +end script + +script testScript + property parent : parentTestScript + property testScriptProperty : 5 + on testScriptFunc() + return "child" + end testScriptFunc +end script + property foo : 1 on test() @@ -35,5 +50,9 @@ on testAdd of a onto b given otherValue:d return a + b + d end testAdd +on testGetScript() + return testScript +end testGetScript + on open end open diff --git a/Foundation/GTMNSAppleEventDescriptor+Foundation.h b/Foundation/GTMNSAppleEventDescriptor+Foundation.h index 51a724f..fe3e1f0 100644 --- a/Foundation/GTMNSAppleEventDescriptor+Foundation.h +++ b/Foundation/GTMNSAppleEventDescriptor+Foundation.h @@ -1,5 +1,5 @@ // -// NSAppleEventDescriptor+Foundation.h +// GTMNSAppleEventDescriptor+Foundation.h // // Copyright 2008 Google Inc. // @@ -18,15 +18,16 @@ #import <Foundation/Foundation.h> #import "GTMDefines.h" +#import "GTMFourCharCode.h" // A category for dealing with NSAppleEventDescriptors and NSArrays. @interface NSAppleEventDescriptor (GTMAppleEventDescriptorArrayAdditions) // Used to register the types you know how to convert into // NSAppleEventDescriptors. -// See examples in NSAppleEventDescriptor+String, NSAppleEventDescriptor+Number -// etc. +// See examples in GTMNSAppleEventDescriptor+Foundation. // Args: // selector - selector to call for any of the types in |types| +// -(NSAppleEventDesc *)selector_name; // types - an std c array of types of length |count| // count - number of types in |types| + (void)gtm_registerSelector:(SEL)selector @@ -89,3 +90,11 @@ timeOut:(NSTimeInterval)timeout reply:(NSAppleEventDescriptor**)reply; @end + +@interface GTMFourCharCode (GTMAppleEventDescriptorObjectAdditions) + +// if you call gtm_appleEventDescriptor on GTMFourCharCode it will be of +// type typeType. If you need something different (like typeProperty) this +// allows you to define the type you want. +- (NSAppleEventDescriptor*)gtm_appleEventDescriptorOfType:(DescType)type; +@end diff --git a/Foundation/GTMNSAppleEventDescriptor+Foundation.m b/Foundation/GTMNSAppleEventDescriptor+Foundation.m index 2b6acd4..deb375f 100644 --- a/Foundation/GTMNSAppleEventDescriptor+Foundation.m +++ b/Foundation/GTMNSAppleEventDescriptor+Foundation.m @@ -17,7 +17,7 @@ // #import "GTMNSAppleEventDescriptor+Foundation.h" -#import "GTMFourCharCode.h" +#import "GTMDebugSelectorValidation.h" #import <Carbon/Carbon.h> // Needed Solely For keyASUserRecordFields // Map of types to selectors. @@ -29,6 +29,14 @@ static NSMutableDictionary *gTypeMap = nil; forTypes:(DescType*)types count:(NSUInteger)count { if (selector && types && count > 0) { +#if DEBUG + NSAppleEventDescriptor *desc + = [[[NSAppleEventDescriptor alloc] initListDescriptor] autorelease]; + GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(desc, + selector, + @encode(id), + NULL); +#endif @synchronized(self) { if (!gTypeMap) { gTypeMap = [[NSMutableDictionary alloc] init]; @@ -222,6 +230,10 @@ static NSMutableDictionary *gTypeMap = nil; return value; } +- (GTMFourCharCode*)gtm_fourCharCodeValue { + return [GTMFourCharCode fourCharCodeWithFourCharCode:[self typeCodeValue]]; +} + @end @implementation NSObject (GTMAppleEventDescriptorObjectAdditions) @@ -458,6 +470,35 @@ static NSMutableDictionary *gTypeMap = nil; @end +@implementation GTMFourCharCode (GTMAppleEventDescriptorObjectAdditions) + ++ (void)load { + DescType types[] = { + typeType, + typeKeyword, + typeApplSignature, + typeEnumerated, + }; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_fourCharCodeValue) + forTypes:types + count:sizeof(types)/sizeof(DescType)]; + [pool release]; +} + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + return [self gtm_appleEventDescriptorOfType:typeType]; +} + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptorOfType:(DescType)type { + FourCharCode code = [self fourCharCode]; + return [NSAppleEventDescriptor descriptorWithDescriptorType:type + bytes:&code + length:sizeof(code)]; +} +@end + @implementation NSAppleEventDescriptor (GTMAppleEventDescriptorAdditions) - (BOOL)gtm_sendEventWithMode:(AESendMode)mode diff --git a/Foundation/GTMNSAppleEventDescriptor+FoundationTest.m b/Foundation/GTMNSAppleEventDescriptor+FoundationTest.m index c35c352..833fd5a 100644 --- a/Foundation/GTMNSAppleEventDescriptor+FoundationTest.m +++ b/Foundation/GTMNSAppleEventDescriptor+FoundationTest.m @@ -52,16 +52,16 @@ DescType type; [NSAppleEventDescriptor gtm_registerSelector:nil forTypes:&type count:1]; - [NSAppleEventDescriptor gtm_registerSelector:@selector(retain) + [NSAppleEventDescriptor gtm_registerSelector:@selector(initListDescriptor) forTypes:nil count:1]; - [NSAppleEventDescriptor gtm_registerSelector:@selector(retain) + [NSAppleEventDescriptor gtm_registerSelector:@selector(initListDescriptor) forTypes:&type count:0]; // Test the duplicate case - [NSAppleEventDescriptor gtm_registerSelector:@selector(retain) + [NSAppleEventDescriptor gtm_registerSelector:@selector(initListDescriptor) forTypes:&type count:1]; - [GTMUnitTestDevLog expectPattern:@"retain being replaced with retain exists " - "for type: [0-9]+"]; - [NSAppleEventDescriptor gtm_registerSelector:@selector(retain) + [GTMUnitTestDevLog expectPattern:@"initListDescriptor being replaced with " + "initListDescriptor exists for type: [0-9]+"]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(initListDescriptor) forTypes:&type count:1]; } @@ -558,6 +558,23 @@ } } +- (void)testDescriptorWithGTMFourCharCode { + GTMFourCharCode *fcc = [GTMFourCharCode fourCharCodeWithFourCharCode:'APPL']; + STAssertNotNil(fcc, nil); + NSAppleEventDescriptor *desc = [fcc gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + GTMFourCharCode *fcc2 = [desc gtm_objectValue]; + STAssertNotNil(fcc2, nil); + STAssertEqualObjects(fcc, fcc2, nil); + STAssertEquals([desc descriptorType], (DescType)typeType, nil); + desc = [fcc gtm_appleEventDescriptorOfType:typeKeyword]; + STAssertNotNil(desc, nil); + fcc2 = [desc gtm_objectValue]; + STAssertNotNil(fcc2, nil); + STAssertEqualObjects(fcc, fcc2, nil); + STAssertEquals([desc descriptorType], (DescType)typeKeyword, nil); +} + - (void)handleEvent:(NSAppleEventDescriptor*)event withReply:(NSAppleEventDescriptor*)reply { gotEvent_ = YES; diff --git a/Foundation/GTMNSAppleEventDescriptor+Handler.m b/Foundation/GTMNSAppleEventDescriptor+Handler.m index b5b2974..da0197b 100644 --- a/Foundation/GTMNSAppleEventDescriptor+Handler.m +++ b/Foundation/GTMNSAppleEventDescriptor+Handler.m @@ -1,5 +1,5 @@ // -// NSAppleEventDescriptor+Handler.m +// GTMNSAppleEventDescriptor+Handler.m // // Copyright 2008 Google Inc. // diff --git a/Foundation/GTMNSAppleEventDescriptor+HandlerTest.m b/Foundation/GTMNSAppleEventDescriptor+HandlerTest.m index 769e014..a137d8a 100644 --- a/Foundation/GTMNSAppleEventDescriptor+HandlerTest.m +++ b/Foundation/GTMNSAppleEventDescriptor+HandlerTest.m @@ -1,5 +1,5 @@ // -// NSAppleEventDescriptor+HandlerTest.m +// GTNNSAppleEventDescriptor+HandlerTest.m // // Copyright 2008 Google Inc. // diff --git a/Foundation/GTMNSAppleScript+Handler.h b/Foundation/GTMNSAppleScript+Handler.h index ca3d6d2..2c38f36 100644 --- a/Foundation/GTMNSAppleScript+Handler.h +++ b/Foundation/GTMNSAppleScript+Handler.h @@ -19,9 +19,23 @@ #import <Foundation/Foundation.h> #import "GTMDefines.h" +// :::WARNING::: NSAppleScript and Garbage Collect (GC) +// +// As of 10.5.4 (and below) Apple has bugs in NSAppleScript when running with +// GC; ie-things crash that have nothing to do w/ this or your code. See +// http://rails.wincent.com/issues/640 for a good amount of detail about the +// problems and simple cases that show it. + // A category for calling handlers in NSAppleScript + +enum { + // Data type is OSAID. These will generally be representing + // scripts. + typeGTMOSAID = 'GTMO' +}; + @interface NSAppleScript(GTMAppleScriptHandlerAdditions) -// This method allows us to call a specific handler in an AppleScript. +// Allows us to call a specific handler in an AppleScript. // parameters are passed in left-right order 0-n. // // Args: @@ -36,24 +50,73 @@ error:(NSDictionary**)error; +// Allows us to call a specific labeled handler in an AppleScript. +// Parameters for a labeled handler can be in any order, as long as the +// order of the params array corresponds to the order of the labels array +// such that labels are associated with their correct parameter values. +// +// Args: +// handler - name of the handler to call in the Applescript +// labels - the labels to associate with the parameters +// params - the parameters to pass to the handler +// count - number of labels/parameters +// error - in non-nil returns any error that may have occurred. +// +// Returns: +// The result of the handler being called. nil on failure. - (NSAppleEventDescriptor*)gtm_executeLabeledHandler:(NSString*)handler labels:(AEKeyword*)labels parameters:(id*)params count:(NSUInteger)count error:(NSDictionary **)error; + +// Same as executeAppleEvent:error: except that it handles return values of +// script correctly. Return values containing scripts will have the +// typeGTMOSAID. Calling gtm_objectValue on a NSAppleEventDescriptor of +// typeGTMOSAID will resolve correctly to a script value. We don't use +// typeScript because that actually copies the script instead of returning the +// actual value. Therefore if you called executeAppleEvent:error: (instead of +// the GTM version) to execute an event that returns a script, you will +// get a completely new Applescript, instead of the actual script you wanted. If +// you are working with script information, use gtm_executeAppleEvent:error +// instead of executeAppleEvent:error: to avoid the problem. +- (NSAppleEventDescriptor *)gtm_executeAppleEvent:(NSAppleEventDescriptor *)event + error:(NSDictionary **)error; + +// The set of all handlers that are defined in this script and its parents. +// Remember that handlers that are defined in an sdef will have their +// eventclass/eventid as their handler instead of the name seen in the script. +// So: +// on open(a) +// blah +// end open +// won't be "open" it will be "aevtodoc". - (NSSet*)gtm_handlers; + +// The set of all properties that are defined in this script and its parents. +// Note that properties can be strings or GTMNSFourCharCodes, so expect both +// coming back in the set. - (NSSet*)gtm_properties; -- (BOOL)gtm_setValue:(id)value forProperty:(NSString*)property; -- (id)gtm_valueForProperty:(NSString*)property; -@end +// Return a value for a property. Will look up the inheritence tree. +// Property must be an NSString or a GTMFourCharCode. +- (id)gtm_valueForProperty:(id)property; -@interface NSAppleEventDescriptor(GTMAppleEventDescriptorScriptAdditions) +// Return a value for a property by type (eg pASParent). Will look up the +// inheritence tree +- (id)gtm_valueForPropertyEnum:(DescType)property; -// Return an NSAppleScript for a desc of typeScript -// Returns nil on failure. -- (NSAppleScript*)gtm_scriptValue; +// Set a script property value. Returns YES/NO on success/failure. +// Property must be of kind NSString or GTMFourCharCode. +// If addingDefinition is YES, it will add a definition to the script +// if the value doesn't exist in the script or one of it's parents. +- (BOOL)gtm_setValue:(id)value + forProperty:(id)property + addingDefinition:(BOOL)adding; -// Return a NSString with [eventClass][eventID] for typeEvent 'evnt' -- (NSString*)gtm_eventValue; +// Set a value for a property by type (eg pASParent). See note above +// for gtm_setValue:forProperty. +- (BOOL)gtm_setValue:(id)value + forPropertyEnum:(DescType)property + addingDefinition:(BOOL)adding; @end diff --git a/Foundation/GTMNSAppleScript+Handler.m b/Foundation/GTMNSAppleScript+Handler.m index d3eeffd..ec6f5d0 100644 --- a/Foundation/GTMNSAppleScript+Handler.m +++ b/Foundation/GTMNSAppleScript+Handler.m @@ -1,5 +1,5 @@ // -// NSAppleScript+Handler.m +// GTMNSAppleScript+Handler.m // // Copyright 2008 Google Inc. // @@ -28,12 +28,47 @@ + (ComponentInstance)_defaultScriptingComponent; - (OSAID) _compiledScriptID; - (id)_initWithData:(NSData*)data error:(NSDictionary**)error; +- (id)_initWithScriptIDNoCopy:(OSAID)osaID; ++ (id)_infoForOSAError:(OSAError)error; @end @interface NSMethodSignature (NSPrivate) + (id)signatureWithObjCTypes:(const char *)fp8; @end +// Our own private interfaces. +@interface NSAppleScript (GTMAppleScriptHandlerAdditionsPrivate) + +// Return an descriptor for a property. Properties are only supposed to be +// of type NSString or GTMFourCharCode. GTMFourCharCode's need special handling +// as they must be turned into NSAppleEventDescriptors of typeProperty. +- (NSAppleEventDescriptor*)gtm_descriptorForPropertyValue:(id)property; + +// Return an NSAppleEventDescriptor for a given property. +// |property| must be kind of class GTMFourCharCode +- (NSAppleEventDescriptor*)gtm_valueDescriptorForProperty:(id)property; + +// Utility routine for extracting multiple values in scripts and their +// parents. +- (NSSet*)gtm_allValuesUsingSelector:(SEL)selector; + +// Utility routine for extracting the handlers for a specific script without +// referring to parent scripts. +- (NSSet*)gtm_scriptHandlers; + +// Utility routine for extracting the properties for a specific script without +// referring to parent scripts. +- (NSSet*)gtm_scriptProperties; + +// Handles creating an NSAppleEventDescriptor from an OSAID +- (NSAppleEventDescriptor*)descForScriptID:(OSAID)scriptID + component:(ComponentInstance)component; + +// Utility methods for converting between real and generic OSAIDs. +- (OSAID)gtm_genericID:(OSAID)osaID forComponent:(ComponentInstance)component; +- (OSAID)gtm_realIDAndComponent:(ComponentInstance*)component; +@end + @implementation NSAppleScript(GTMAppleScriptHandlerAdditions) GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_descriptorWithPositionalHandler:parametersArray:); // COV_NF_LINE GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_descriptorWithLabeledHandler:labels:parameters:count:); // COV_NF_LINE @@ -56,25 +91,41 @@ GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_eventValue) forTypes:types2 count:sizeof(types2)/sizeof(DescType)]; + + DescType types3[] = { + typeGTMOSAID + }; + + [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_osaIDValue) + forTypes:types3 + count:sizeof(types3)/sizeof(DescType)]; [pool release]; } -- (OSAID)gtm_realIDAndComponent:(ComponentInstance*)component { +- (NSAppleEventDescriptor *)gtm_executeAppleEvent:(NSAppleEventDescriptor *)event + error:(NSDictionary **)error { if (![self isCompiled]) { - NSDictionary *error; - if (![self compileAndReturnError:&error]) { - _GTMDevLog(@"Unable to compile script: %@ %@", self, error); - return kOSANullScript; + if (![self compileAndReturnError:error]) { + return nil; } } - OSAID genericID = [self _compiledScriptID]; - ComponentInstance genericComponent = [NSAppleScript _defaultScriptingComponent]; - OSAError err = OSAGenericToRealID(genericComponent, &genericID, component); - if (err) { - _GTMDevLog(@"Unable to get real id script: %@ %@", self, err); // COV_NF_LINE - genericID = kOSANullScript; // COV_NF_LINE + NSAppleEventDescriptor *desc = nil; + ComponentInstance component; + OSAID scriptID = [self gtm_realIDAndComponent:&component]; + OSAID valueID; + OSAError err = OSAExecuteEvent(component, [event aeDesc], scriptID, + kOSAModeNull, &valueID); + if (err == noErr) { + // descForScriptID:component: is what sets this apart from the + // standard executeAppelEvent:error: in that it handles + // taking script results and turning them into AEDescs of typeGTMOSAID + // instead of typeScript. + desc = [self descForScriptID:valueID component:component]; } - return genericID; + if (err && error) { + *error = [NSAppleScript _infoForOSAError:err]; + } + return desc; } - (NSAppleEventDescriptor*)gtm_executePositionalHandler:(NSString*)handler @@ -83,7 +134,7 @@ GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); NSAppleEventDescriptor *event = [NSAppleEventDescriptor gtm_descriptorWithPositionalHandler:handler parametersArray:params]; - return [self executeAppleEvent:event error:error]; + return [self gtm_executeAppleEvent:event error:error]; } - (NSAppleEventDescriptor*)gtm_executeLabeledHandler:(NSString*)handler @@ -96,60 +147,50 @@ GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); labels:labels parameters:params count:count]; - return [self executeAppleEvent:event error:error]; + return [self gtm_executeAppleEvent:event error:error]; } - (NSSet*)gtm_handlers { - AEDescList names = { typeNull, NULL }; - NSArray *array = nil; - ComponentInstance component; - OSAID osaID = [self gtm_realIDAndComponent:&component]; - OSAError err = OSAGetHandlerNames(component, kOSAModeNull, osaID, &names); - if (!err) { - NSAppleEventDescriptor *desc - = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&names] autorelease]; - array = [desc gtm_objectValue]; - } - if (err) { - _GTMDevLog(@"Error getting handlers: %d", err); - } - return [NSSet setWithArray:array]; + return [self gtm_allValuesUsingSelector:@selector(gtm_scriptHandlers)]; } - + - (NSSet*)gtm_properties { - AEDescList names = { typeNull, NULL }; - NSArray *array = nil; - ComponentInstance component; - OSAID osaID = [self gtm_realIDAndComponent:&component]; - OSAError err = OSAGetPropertyNames(component, kOSAModeNull, osaID, &names); - if (!err) { - NSAppleEventDescriptor *desc - = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&names] autorelease]; - array = [desc gtm_objectValue]; - } - if (err) { - _GTMDevLog(@"Error getting properties: %d", err); - } - return [NSSet setWithArray:array]; + return [self gtm_allValuesUsingSelector:@selector(gtm_scriptProperties)]; } -- (BOOL)gtm_setValue:(id)value forProperty:(NSString*)property { +// Set a value for a property by type (eg pASTopLevelScript) +- (BOOL)gtm_setValue:(id)value + forPropertyEnum:(DescType)property + addingDefinition:(BOOL)adding { + GTMFourCharCode *fcc + = [GTMFourCharCode fourCharCodeWithFourCharCode:property]; + return [self gtm_setValue:value forProperty:fcc addingDefinition:adding]; +} + +- (BOOL)gtm_setValue:(id)value + forProperty:(id)property + addingDefinition:(BOOL)adding{ + OSAError error = paramErr; BOOL wasGood = NO; + NSAppleEventDescriptor *propertyName + = [self gtm_descriptorForPropertyValue:property]; NSAppleEventDescriptor *desc = [value gtm_appleEventDescriptor]; - NSAppleEventDescriptor *propertyName = [property gtm_appleEventDescriptor]; - OSAError error = paramErr; - if (desc && propertyName) { + if (propertyName && desc) { + NSAppleScript *script = self; OSAID valueID = kOSANullScript; ComponentInstance component; - OSAID scriptID = [self gtm_realIDAndComponent:&component]; + OSAID scriptID = [script gtm_realIDAndComponent:&component]; error = OSACoerceFromDesc(component, [desc aeDesc], kOSAModeNull, &valueID); - if (!error) { - error = OSASetProperty(component, kOSAModeNull, - scriptID, [propertyName aeDesc], valueID); - if (!error) { + if (error == noErr) { + error = OSASetProperty(component, + adding ? kOSAModeNull : kOSAModeDontDefine, + scriptID, + [propertyName aeDesc], + valueID); + if (error == noErr) { wasGood = YES; } } @@ -161,38 +202,13 @@ GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); return wasGood; } -- (id)gtm_valueForProperty:(NSString*)property { - id value = nil; - NSAppleEventDescriptor *propertyName = [property gtm_appleEventDescriptor]; - OSAError error = paramErr; - if (propertyName) { - ComponentInstance component; - OSAID scriptID = [self gtm_realIDAndComponent:&component]; - OSAID valueID = kOSANullScript; - error = OSAGetProperty(component, - kOSAModeNull, - scriptID, - [propertyName aeDesc], - &valueID); - if (!error) { - AEDesc aeDesc; - error = OSACoerceToDesc(component, - valueID, - typeWildCard, - kOSAModeNull, - &aeDesc); - if (!error) { - NSAppleEventDescriptor *desc - = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&aeDesc] autorelease]; - value = [desc gtm_objectValue]; - } - } - } - if (error) { - _GTMDevLog(@"Unable to get valueForProperty:%@ from %@ (%d)", - property, self, error); - } - return value; +- (id)gtm_valueForProperty:(id)property { + return [[self gtm_valueDescriptorForProperty:property] gtm_objectValue]; +} + +- (id)gtm_valueForPropertyEnum:(DescType)property { + GTMFourCharCode *fcc = [GTMFourCharCode fourCharCodeWithFourCharCode:property]; + return [self gtm_valueForProperty:fcc]; } - (NSAppleEventDescriptor*)gtm_appleEventDescriptor { @@ -200,15 +216,16 @@ GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); OSAID osaID = [self gtm_realIDAndComponent:&component]; AEDesc result = { typeNull, NULL }; NSAppleEventDescriptor *desc = nil; - OSAError err = OSACoerceToDesc(component, - osaID, - typeScript, - kOSAModeNull, - &result); - if (!err) { - desc = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&result] autorelease]; + OSAError error = OSACoerceToDesc(component, + osaID, + typeScript, + kOSAModeNull, + &result); + if (error == noErr) { + desc = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&result] + autorelease]; } else { - _GTMDevLog(@"Unable to coerce script %d", err); // COV_NF_LINE + _GTMDevLog(@"Unable to coerce script %d", error); // COV_NF_LINE } return desc; } @@ -255,7 +272,195 @@ GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); } @end -@implementation NSAppleEventDescriptor(GMAppleEventDescriptorScriptAdditions) +@implementation NSAppleScript (GTMAppleScriptHandlerAdditionsPrivate) + +- (NSAppleEventDescriptor*)gtm_descriptorForPropertyValue:(id)property { + NSAppleEventDescriptor *propDesc = nil; + if ([property isKindOfClass:[GTMFourCharCode class]]) { + propDesc = [property gtm_appleEventDescriptorOfType:typeProperty]; + } else if ([property isKindOfClass:[NSString class]]) { + propDesc = [property gtm_appleEventDescriptor]; + } + return propDesc; +} + +- (NSAppleEventDescriptor*)gtm_valueDescriptorForProperty:(id)property { + OSAError error = paramErr; + NSAppleEventDescriptor *desc = nil; + NSAppleEventDescriptor *propertyName + = [self gtm_descriptorForPropertyValue:property]; + if (propertyName) { + ComponentInstance component; + OSAID scriptID = [self gtm_realIDAndComponent:&component]; + OSAID valueID = kOSANullScript; + error = OSAGetProperty(component, + kOSAModeNull, + scriptID, + [propertyName aeDesc], + &valueID); + if (error == noErr) { + desc = [self descForScriptID:valueID component:component]; + } + } + if (error) { + _GTMDevLog(@"Unable to get valueForProperty:%@ from %@ (%d)", + property, self, error); + } + return desc; +} + +- (NSSet*)gtm_allValuesUsingSelector:(SEL)selector { + NSMutableSet *resultSet = [NSMutableSet set]; + NSAppleEventDescriptor *scriptDesc = [self gtm_appleEventDescriptor]; + NSMutableSet *scriptDescsWeveSeen = [NSMutableSet set]; + GTMFourCharCode *fcc = [GTMFourCharCode fourCharCodeWithFourCharCode:pASParent]; + Class appleScriptClass = [NSAppleScript class]; + while (scriptDesc) { + NSAppleScript *script = [scriptDesc gtm_objectValue]; + if ([script isKindOfClass:appleScriptClass]) { + NSData *data = [scriptDesc data]; + if (!data || [scriptDescsWeveSeen containsObject:data]) { + break; + } else { + [scriptDescsWeveSeen addObject:data]; + } + NSSet *newSet = [script performSelector:selector]; + [resultSet unionSet:newSet]; + scriptDesc = [script gtm_valueDescriptorForProperty:fcc]; + } else { + break; + } + } + return resultSet; +} + +- (NSSet*)gtm_scriptHandlers { + AEDescList names = { typeNull, NULL }; + NSArray *array = nil; + ComponentInstance component; + OSAID osaID = [self gtm_realIDAndComponent:&component]; + OSAError error = OSAGetHandlerNames(component, kOSAModeNull, osaID, &names); + if (error == noErr) { + NSAppleEventDescriptor *desc + = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&names] + autorelease]; + array = [desc gtm_objectValue]; + } + if (error != noErr) { + _GTMDevLog(@"Error getting handlers: %d", error); // COV_NF_LINE + } + return [NSSet setWithArray:array]; +} + +- (NSSet*)gtm_scriptProperties { + AEDescList names = { typeNull, NULL }; + NSArray *array = nil; + ComponentInstance component; + OSAID osaID = [self gtm_realIDAndComponent:&component]; + OSAError error = OSAGetPropertyNames(component, kOSAModeNull, osaID, &names); + if (error == noErr) { + NSAppleEventDescriptor *desc + = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&names] + autorelease]; + array = [desc gtm_objectValue]; + } + if (error != noErr) { + _GTMDevLog(@"Error getting properties: %d", error); // COV_NF_LINE + } + return [NSSet setWithArray:array]; +} + +- (OSAID)gtm_genericID:(OSAID)osaID forComponent:(ComponentInstance)component { + ComponentInstance genericComponent = [NSAppleScript _defaultScriptingComponent]; + OSAID exactID = osaID; + OSAError error = OSARealToGenericID(genericComponent, &exactID, component); + if (error != noErr) { + _GTMDevLog(@"Unable to get real id script: %@ %d", self, error); // COV_NF_LINE + exactID = kOSANullScript; // COV_NF_LINE + } + return exactID; +} + +- (NSAppleEventDescriptor*)descForScriptID:(OSAID)osaID + component:(ComponentInstance)component { + NSAppleEventDescriptor *desc = nil; + // If we have a script, return a typeGTMOSAID, otherwise convert it to + // it's default AEDesc using OSACoerceToDesc with typeWildCard. + long value = 0; + OSAError err = noErr; + if (osaID == 0) { + desc = [NSAppleEventDescriptor nullDescriptor]; + } else { + err = OSAGetScriptInfo(component, + osaID, + kOSAScriptBestType, + &value); + if (err == noErr) { + if (value == typeScript) { + osaID = [self gtm_genericID:osaID forComponent:component]; + desc = [NSAppleEventDescriptor descriptorWithDescriptorType:typeGTMOSAID + bytes:&osaID + length:sizeof(osaID)]; + } else { + AEDesc aeDesc; + err = OSACoerceToDesc(component, + osaID, + typeWildCard, + kOSAModeNull, + &aeDesc); + if (err == noErr) { + desc = [[[NSAppleEventDescriptor alloc] + initWithAEDescNoCopy:&aeDesc] autorelease]; + } + } + } + } + if (err != noErr) { + _GTMDevLog(@"Unable to create desc for id:%d (%d)", osaID, err); // COV_NF_LINE + } + return desc; +} + +- (OSAID)gtm_realIDAndComponent:(ComponentInstance*)component { + if (![self isCompiled]) { + NSDictionary *error; + if (![self compileAndReturnError:&error]) { + _GTMDevLog(@"Unable to compile script: %@ %@", self, error); + return kOSANullScript; + } + } + OSAID genericID = [self _compiledScriptID]; + ComponentInstance genericComponent = [NSAppleScript _defaultScriptingComponent]; + OSAError error = OSAGenericToRealID(genericComponent, &genericID, component); + if (error != noErr) { + _GTMDevLog(@"Unable to get real id script: %@ %d", self, error); // COV_NF_LINE + genericID = kOSANullScript; // COV_NF_LINE + } + return genericID; +} + +@end + +// Private methods for dealing with Scripts/Events and NSAppleEventDescriptors +@interface NSAppleEventDescriptor (GTMAppleEventDescriptorScriptAdditions) + +// Return an NSAppleScript for a desc of typeScript. This will create a new +// Applescript that is a copy of the script that you want. +// Returns nil on failure. +- (NSAppleScript*)gtm_scriptValue; + +// Return an NSAppleScript for a desc of typeGTMOSAID. This will not copy the +// script, but will create an NSAppleScript wrapping the script represented +// by the OSAID. +// Returns nil on failure. +- (NSAppleScript*)gtm_osaIDValue; + +// Return a NSString with [eventClass][eventID] for typeEvent 'evnt' +- (NSString*)gtm_eventValue; +@end + + +@implementation NSAppleEventDescriptor (GMAppleEventDescriptorScriptAdditions) - (NSAppleScript*)gtm_scriptValue { NSDictionary *error; @@ -267,6 +472,12 @@ GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); return script; } +- (NSAppleScript*)gtm_osaIDValue { + _GTMDevAssert([[self data] length] == sizeof(OSAID), nil); + OSAID osaID = *(const OSAID*)[[self data] bytes]; + return [[[NSAppleScript alloc] _initWithScriptIDNoCopy:osaID] autorelease]; +} + - (NSString*)gtm_eventValue { struct AEEventRecordStruct { AEEventClass eventClass; diff --git a/Foundation/GTMNSAppleScript+HandlerTest.m b/Foundation/GTMNSAppleScript+HandlerTest.m index 5a9d52b..d4bb9bc 100644 --- a/Foundation/GTMNSAppleScript+HandlerTest.m +++ b/Foundation/GTMNSAppleScript+HandlerTest.m @@ -1,5 +1,5 @@ // -// NSAppleScript+HandlerTest.m +// GTMNSAppleScript+HandlerTest.m // // Copyright 2008 Google Inc. // @@ -21,6 +21,9 @@ #import "GTMNSAppleScript+Handler.h" #import "GTMNSAppleEventDescriptor+Foundation.h" #import "GTMUnitTestDevLog.h" +#import "GTMGarbageCollection.h" +#import "GTMSystemVersion.h" +#import "GTMFourCharCode.h" @interface GTMNSAppleScript_HandlerTest : GTMTestCase { NSAppleScript *script_; @@ -28,16 +31,34 @@ @end @implementation GTMNSAppleScript_HandlerTest +- (void)invokeTest { + // NOTE: These tests are disabled in GC is on. See the comment/warning in the + // GTMNSAppleScript+Handler.h for more details, but we disable them to avoid + // the tests failing (crashing) when it's Apple's bug. Please bump the system + // check as appropriate when new systems are tested. Currently broken on + // 10.5.4 and below. Radar 6126682. + long major, minor, bugfix; + [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugfix]; + if (!(GTMIsGarbageCollectionEnabled() + && major <= 10 && minor <= 5 && bugfix <= 4)) { + [super invokeTest]; + } else { + NSLog(@"--- %@ NOT run because of GC incompatibilites ---", [self name]); + } +} + - (void)setUp { - NSBundle *bundle = [NSBundle bundleForClass:[GTMNSAppleScript_HandlerTest class]]; + NSBundle *bundle + = [NSBundle bundleForClass:[GTMNSAppleScript_HandlerTest class]]; STAssertNotNil(bundle, nil); NSString *path = [bundle pathForResource:@"GTMNSAppleEvent+HandlerTest" ofType:@"scpt" inDirectory:@"Scripts"]; STAssertNotNil(path, [bundle description]); NSDictionary *error = nil; - script_ = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] - error:&error]; + script_ + = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] + error:&error]; STAssertNotNil(script_, [error description]); STAssertNil(error, @"Error should be nil. Error = %@", [error description]); } @@ -47,6 +68,30 @@ script_ = nil; } +- (void)testExecuteAppleEvent { + NSString *source = @"on test()\nreturn 1\nend test"; + NSAppleScript *script + = [[[NSAppleScript alloc] initWithSource:source] autorelease]; + STAssertNotNil(script, nil); + NSDictionary *error = nil; + NSAppleEventDescriptor *desc = [script gtm_executePositionalHandler:@"test" + parameters:nil + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc gtm_objectValue], [NSNumber numberWithInt:1], nil); + + // bogus script + source = @"adf872345ba asdf asdf gr"; + script = [[[NSAppleScript alloc] initWithSource:source] autorelease]; + STAssertNotNil(script, nil); + desc = [script gtm_executePositionalHandler:@"test" + parameters:nil + error:&error]; + STAssertNil(desc, nil); + STAssertNotNil(error, @"Error should not be nil"); +} + - (void)testHandlerNoParamsNoReturn { NSDictionary *error = nil; NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"test" @@ -73,9 +118,10 @@ - (void)testHandlerNoParamsWithReturn { NSDictionary *error = nil; - NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"testReturnOne" - parameters:nil - error:&error]; + NSAppleEventDescriptor *desc + = [script_ gtm_executePositionalHandler:@"testReturnOne" + parameters:nil + error:&error]; STAssertNotNil(desc, [error description]); STAssertNil(error, @"Error should be nil. Error = %@", [error description]); STAssertEquals([desc descriptorType], (DescType)typeSInt32, nil); @@ -101,9 +147,10 @@ - (void)testHandlerOneParamWithReturn { NSDictionary *error = nil; // Note case change in executeHandler call - NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"testreturnParam" - parameters:nil - error:&error]; + NSAppleEventDescriptor *desc + = [script_ gtm_executePositionalHandler:@"testreturnParam" + parameters:nil + error:&error]; STAssertNil(desc, @"Desc should by nil %@", desc); STAssertNotNil(error, nil); error = nil; @@ -128,9 +175,10 @@ NSDictionary *error = nil; // Note case change in executeHandler call // Test case and empty params - NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"testADDPArams" - parameters:nil - error:&error]; + NSAppleEventDescriptor *desc + = [script_ gtm_executePositionalHandler:@"testADDPArams" + parameters:nil + error:&error]; STAssertNil(desc, @"Desc should by nil %@", desc); STAssertNotNil(error, nil); @@ -193,10 +241,11 @@ params[2] = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:4] forKey:@"othervalue"]; - NSAppleEventDescriptor *desc = [script_ gtm_executeLabeledHandler:@"testAdd" - labels:labels - parameters:params - count:sizeof(params) / sizeof(id) + NSAppleEventDescriptor *desc + = [script_ gtm_executeLabeledHandler:@"testAdd" + labels:labels + parameters:params + count:sizeof(params) / sizeof(id) error:&error]; STAssertNotNil(desc, [error description]); STAssertNil(error, @"Error should be nil. Error = %@", [error description]); @@ -235,39 +284,136 @@ @"testreturnparam", @"testaddparams", @"testadd", + @"testgetscript", nil]; STAssertEqualObjects(handlers, expected, @"Unexpected handlers?"); } +- (void)testInheritedHandlers { + NSDictionary *error = nil; + NSAppleEventDescriptor *desc + = [script_ gtm_executePositionalHandler:@"testGetScript" + parameters:nil + error:&error]; + STAssertNil(error, nil); + STAssertNotNil(desc, nil); + NSAppleScript *script = [desc gtm_objectValue]; + STAssertTrue([script isKindOfClass:[NSAppleScript class]], nil); + error = nil; + desc = [script gtm_executePositionalHandler:@"parentTestScriptFunc" + parameters:nil error:&error]; + STAssertNil(error, nil); + STAssertNotNil(desc, nil); + NSString *value = [desc gtm_objectValue]; + STAssertEqualObjects(value, @"parent", nil); +} + - (void)testProperties { - NSSet *properties = [script_ gtm_properties]; - NSSet *expected = [NSSet setWithObjects: - @"foo", - @"asdscriptuniqueidentifier", - nil]; + NSDictionary *error = nil; + NSAppleEventDescriptor *desc + = [script_ gtm_executePositionalHandler:@"testGetScript" + parameters:nil + error:&error]; + STAssertNil(error, nil); + STAssertNotNil(desc, nil); + NSAppleScript *script = [desc gtm_objectValue]; + STAssertTrue([script isKindOfClass:[NSAppleScript class]], nil); + + NSSet *properties = [script gtm_properties]; + NSSet *expected + = [NSSet setWithObjects: + @"testscriptproperty", + @"parenttestscriptproperty", + @"foo", + @"testscript", + @"parenttestscript", + @"asdscriptuniqueidentifier", + [GTMFourCharCode fourCharCodeWithFourCharCode:pVersion], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASPrintDepth], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASTopLevelScript], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASResult], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASMinutes], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASDays], + // No constant for linefeed in the 10.5 sdk + // Radar 6132775 Need a constant for the Applescript Property 'lnfd' + [GTMFourCharCode fourCharCodeWithFourCharCode:'lnfd'], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASPi], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASReturn], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASSpace], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASPrintLength], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASQuote], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASWeeks], + [GTMFourCharCode fourCharCodeWithFourCharCode:pTextItemDelimiters], + // Applescript properties should be pASSeconds, but + // on 10.5.4 it is actually using cSeconds. + // Radar 6132696 Applescript root level property is cSeconds + // instead of pASSeconds + [GTMFourCharCode fourCharCodeWithFourCharCode:cSeconds], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASHours], + [GTMFourCharCode fourCharCodeWithFourCharCode:pASTab], + nil]; STAssertEqualObjects(properties, expected, @"Unexpected properties?"); - id value = [script_ gtm_valueForProperty:@"foo"]; - STAssertEqualObjects(value, [NSNumber numberWithInt:1], @"bad property?"); - BOOL goodSet = [script_ gtm_setValue:@"bar" forProperty:@"foo"]; + id value = [script gtm_valueForProperty:@"testScriptProperty"]; + STAssertEqualObjects(value, [NSNumber numberWithInt:5], @"bad property?"); + BOOL goodSet = [script gtm_setValue:@"bar" + forProperty:@"foo" + addingDefinition:NO]; STAssertTrue(goodSet, @"Couldn't set property"); + + // Test local set + value = [script gtm_valueForProperty:@"foo"]; + STAssertEqualObjects(value, @"bar", @"bad property?"); + + // Test inherited set value = [script_ gtm_valueForProperty:@"foo"]; STAssertEqualObjects(value, @"bar", @"bad property?"); - + [GTMUnitTestDevLog expectPattern:@"Unable to setValue:bar forProperty:" "\\(null\\) from <NSAppleScript: 0x[0-9a-f]+> \\(-50\\)"]; - goodSet = [script_ gtm_setValue:@"bar" forProperty:nil]; + goodSet = [script gtm_setValue:@"bar" + forProperty:nil + addingDefinition:NO]; + STAssertFalse(goodSet, @"Set property?"); + + [GTMUnitTestDevLog expectPattern:@"Unable to setValue:bar forProperty:3" + " from <NSAppleScript: 0x[0-9a-f]+> \\(-50\\)"]; + goodSet = [script gtm_setValue:@"bar" + forProperty:[NSNumber numberWithInt:3] + addingDefinition:YES]; STAssertFalse(goodSet, @"Set property?"); + + [GTMUnitTestDevLog expectPattern:@"Unable to get valueForProperty:gargle " "from <NSAppleScript: 0x[0-9a-f]+> \\(-1753\\)"]; - value = [script_ gtm_valueForProperty:@"gargle"]; + value = [script gtm_valueForProperty:@"gargle"]; STAssertNil(value, @"Property named gargle?"); + + goodSet = [script_ gtm_setValue:@"wow" + forProperty:@"addedProperty" + addingDefinition:YES]; + STAssertTrue(goodSet, @"Unable to addProperty"); + + value = [script gtm_valueForProperty:@"addedProperty"]; + STAssertNotNil(value, nil); + STAssertEqualObjects(value, @"wow", nil); + + // http://www.straightdope.com/classics/a3_341.html + NSNumber *newPI = [NSNumber numberWithInt:3]; + goodSet = [script gtm_setValue:newPI + forPropertyEnum:pASPi + addingDefinition:NO]; + STAssertTrue(goodSet, @"Unable to set property"); + value = [script_ gtm_valueForPropertyEnum:pASPi]; + STAssertNotNil(value, nil); + STAssertEqualObjects(value, newPI, @"bad property"); } - (void)testFailures { NSDictionary *error = nil; - NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"noSuchTest" - parameters:nil - error:&error]; + NSAppleEventDescriptor *desc + = [script_ gtm_executePositionalHandler:@"noSuchTest" + parameters:nil + error:&error]; STAssertNil(desc, nil); STAssertNotNil(error, nil); @@ -294,15 +440,18 @@ STAssertNil(desc, nil); // Test with a bad script - NSAppleScript *script = [[[NSAppleScript alloc] initWithSource:@"david hasselhoff"] autorelease]; + NSAppleScript *script + = [[[NSAppleScript alloc] initWithSource:@"david hasselhoff"] autorelease]; [GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"]; - [GTMUnitTestDevLog expectPattern:@"Error getting handlers: -[0-9]+"]; + [GTMUnitTestDevLog expectPattern:@"Unable to coerce script -2147450879"]; NSSet *handlers = [script gtm_handlers]; STAssertEquals([handlers count], (NSUInteger)0, @"Should have no handlers"); [GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"]; - [GTMUnitTestDevLog expectPattern:@"Error getting properties: -[0-9]+"]; + [GTMUnitTestDevLog expectPattern:@"Unable to coerce script -2147450879"]; NSSet *properties = [script gtm_properties]; - STAssertEquals([properties count], (NSUInteger)0, @"Should have no properties"); + STAssertEquals([properties count], + (NSUInteger)0, + @"Should have no properties"); } - (void)testScriptDescriptors { @@ -325,7 +474,8 @@ [foo test]; NSNumber *val = [foo testReturnParam:[NSNumber numberWithInt:2]]; STAssertEquals([val intValue], 2, @"should be 2"); - val = [foo testAddParams:[NSNumber numberWithInt:2] :[NSNumber numberWithInt:3]]; + val = [foo testAddParams:[NSNumber numberWithInt:2] + :[NSNumber numberWithInt:3]]; STAssertEquals([val intValue], 5, @"should be 5"); } @end diff --git a/Foundation/GTMNSString+FindFolder.m b/Foundation/GTMNSString+FindFolder.m index 7d2f3b5..3a44174 100644 --- a/Foundation/GTMNSString+FindFolder.m +++ b/Foundation/GTMNSString+FindFolder.m @@ -31,12 +31,11 @@ OSErr err = FSFindFolder(theDomain, theFolderType, doCreate, &folderRef); if (err == noErr) { - CFURLRef folderURL = CFURLCreateFromFSRef(kCFAllocatorSystemDefault, &folderRef); + CFURLRef folderURL = CFURLCreateFromFSRef(kCFAllocatorSystemDefault, + &folderRef); if (folderURL) { - - folderPath = GTMNSMakeCollectable(CFURLCopyFileSystemPath(folderURL, kCFURLPOSIXPathStyle)); - [folderPath autorelease]; - + folderPath = GTMCFAutorelease(CFURLCopyFileSystemPath(folderURL, + kCFURLPOSIXPathStyle)); CFRelease(folderURL); } } diff --git a/Foundation/GTMNSString+URLArguments.m b/Foundation/GTMNSString+URLArguments.m index 564b943..46d2c99 100644 --- a/Foundation/GTMNSString+URLArguments.m +++ b/Foundation/GTMNSString+URLArguments.m @@ -30,7 +30,7 @@ NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8); - return [GTMNSMakeCollectable(escaped) autorelease]; + return GTMCFAutorelease(escaped); } - (NSString*)gtm_stringByUnescapingFromURLArgument { diff --git a/Foundation/GTMNSString+XMLTest.m b/Foundation/GTMNSString+XMLTest.m index dc157dc..4788690 100644 --- a/Foundation/GTMNSString+XMLTest.m +++ b/Foundation/GTMNSString+XMLTest.m @@ -1,5 +1,5 @@ // -// NSString+XMLTest.m +// GTMNSString+XMLTest.m // // Copyright 2007-2008 Google Inc. // diff --git a/Foundation/GTMObjC2Runtime.h b/Foundation/GTMObjC2Runtime.h index ab34cfb..f94e680 100644 --- a/Foundation/GTMObjC2Runtime.h +++ b/Foundation/GTMObjC2Runtime.h @@ -50,17 +50,25 @@ #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 #import "objc/Protocol.h" -Class object_getClass(id obj); -const char *class_getName(Class cls); -BOOL class_conformsToProtocol(Class cls, Protocol *protocol); -Class class_getSuperclass(Class cls); -Method *class_copyMethodList(Class cls, unsigned int *outCount); -SEL method_getName(Method m); -void method_exchangeImplementations(Method m1, Method m2); -IMP method_getImplementation(Method method); -IMP method_setImplementation(Method method, IMP imp); -struct objc_method_description protocol_getMethodDescription(Protocol *p, - SEL aSel, - BOOL isRequiredMethod, - BOOL isInstanceMethod); +#ifdef __cplusplus +extern "C" { +#endif + +OBJC_EXPORT Class object_getClass(id obj); +OBJC_EXPORT const char *class_getName(Class cls); +OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol); +OBJC_EXPORT Class class_getSuperclass(Class cls); +OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount); +OBJC_EXPORT SEL method_getName(Method m); +OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2); +OBJC_EXPORT IMP method_getImplementation(Method method); +OBJC_EXPORT IMP method_setImplementation(Method method, IMP imp); +OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod); +#ifdef __cplusplus +} +#endif + #endif // OBJC2_UNAVAILABLE diff --git a/Foundation/GTMProgressMonitorInputStream.m b/Foundation/GTMProgressMonitorInputStream.m index 2336268..94212dd 100644 --- a/Foundation/GTMProgressMonitorInputStream.m +++ b/Foundation/GTMProgressMonitorInputStream.m @@ -59,13 +59,40 @@ inputStream_ = [input retain]; dataSize_ = length; + + if (!inputStream_) { + [self release]; + self = nil; + } } return self; } +#pragma mark - + - (id)init { - _GTMDevAssert(NO, @"should call initWithStream:length:"); - return nil; + return [self initWithStream:nil length:0]; +} + +- (id)initWithData:(NSData *)data { + unsigned long long dataLength = [data length]; + NSInputStream *inputStream = nil; + if (data) { + inputStream = [NSInputStream inputStreamWithData:data]; + } + return [self initWithStream:inputStream length:dataLength]; +} + +- (id)initWithFileAtPath:(NSString *)path { + NSDictionary *fileAttrs = + [[NSFileManager defaultManager] fileAttributesAtPath:path + traverseLink:YES]; + unsigned long long fileSize = [fileAttrs fileSize]; + NSInputStream *inputStream = nil; + if (fileSize) { + inputStream = [NSInputStream inputStreamWithFileAtPath:path]; + } + return [self initWithStream:inputStream length:fileSize]; } - (void)dealloc { @@ -75,7 +102,6 @@ #pragma mark - - - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { NSInteger numRead = [inputStream_ read:buffer maxLength:len]; @@ -103,6 +129,9 @@ } - (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len { + // TODO: doesn't this advance the stream so we should warn the progress + // callback? NSInputStream w/ a file and NSData both seem to return NO for + // this so I'm not sure how to test it. return [inputStream_ getBuffer:buffer length:len]; } @@ -135,6 +164,7 @@ - (id)propertyForKey:(NSString *)key { return [inputStream_ propertyForKey:key]; } + - (BOOL)setProperty:(id)property forKey:(NSString *)key { return [inputStream_ setProperty:property forKey:key]; } diff --git a/Foundation/GTMProgressMonitorInputStreamTest.m b/Foundation/GTMProgressMonitorInputStreamTest.m new file mode 100644 index 0000000..1d0d1aa --- /dev/null +++ b/Foundation/GTMProgressMonitorInputStreamTest.m @@ -0,0 +1,257 @@ +// +// GTMProgressMonitorInputStreamTest.m +// +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMSenTestCase.h" +#import "GTMProgressMonitorInputStream.h" +#import "GTMUnitTestDevLog.h" + + +@interface GTMProgressMonitorInputStreamTest : GTMTestCase +@end + +@interface TestStreamMonitor : NSObject { + @private + NSMutableArray *reportedDeliverySizesArray_; + NSMutableSet *reportedTotalSizesSet_; +} +- (NSArray *)reportedSizes; +- (NSSet *)reportedTotals; +- (void)inputStream:(GTMProgressMonitorInputStream *)stream + hasDeliveredBytes:(unsigned long long)numRead + ofTotalBytes:(unsigned long long)total; +@end + +@implementation GTMProgressMonitorInputStreamTest + +static const unsigned long long kSourceDataByteCount = (10000*10); + +- (void)testInit { + + // bad inputs + + // init + STAssertNil([[GTMProgressMonitorInputStream alloc] init], nil); + STAssertNil([[GTMProgressMonitorInputStream alloc] initWithStream:nil length:0], nil); + STAssertNil([[GTMProgressMonitorInputStream alloc] initWithData:nil], nil); + STAssertNil([[GTMProgressMonitorInputStream alloc] initWithFileAtPath:nil], nil); + + // class helpers + STAssertNil([GTMProgressMonitorInputStream inputStreamWithStream:nil length:0], nil); + STAssertNil([GTMProgressMonitorInputStream inputStreamWithData:nil], nil); + STAssertNil([GTMProgressMonitorInputStream inputStreamWithFileAtPath:nil], nil); + + // some data for next round + NSData *data = [@"some data" dataUsingEncoding:NSUTF8StringEncoding]; + STAssertNotNil(data, nil); + GTMProgressMonitorInputStream *monStream; + + // good inputs + + NSInputStream *inputStream = [NSInputStream inputStreamWithData:data]; + STAssertNotNil(inputStream, nil); + monStream = + [GTMProgressMonitorInputStream inputStreamWithStream:inputStream + length:[data length]]; + STAssertNotNil(monStream, nil); + + monStream = [GTMProgressMonitorInputStream inputStreamWithData:data]; + STAssertNotNil(monStream, nil); + + monStream = + [GTMProgressMonitorInputStream inputStreamWithFileAtPath:@"/etc/services"]; + STAssertNotNil(monStream, nil); + +} + +- (void)testMonitorAccessors { + + NSData *data = [@"some data" dataUsingEncoding:NSUTF8StringEncoding]; + STAssertNotNil(data, nil); + GTMProgressMonitorInputStream *monStream = + [GTMProgressMonitorInputStream inputStreamWithData:data]; + STAssertNotNil(monStream, nil); + + TestStreamMonitor *monitor = [[[TestStreamMonitor alloc] init] autorelease]; + STAssertNotNil(monitor, nil); + + SEL monSel = @selector(inputStream:hasDeliveredBytes:ofTotalBytes:); + [monStream setMonitorDelegate:monitor selector:monSel]; + STAssertEquals([monStream monitorDelegate], monitor, nil); + STAssertEquals([monStream monitorSelector], monSel, nil); + + [monStream setMonitorSource:data]; + STAssertEquals([monStream monitorSource], data, nil); +} + +- (void)testInputStreamAccessors { + + GTMProgressMonitorInputStream *monStream = + [GTMProgressMonitorInputStream inputStreamWithFileAtPath:@"/etc/services"]; + STAssertNotNil(monStream, nil); + + // delegate + + [monStream setDelegate:self]; + STAssertEquals([monStream delegate], self, nil); + [monStream setDelegate:nil]; + STAssertNil([monStream delegate], nil); + + // error (we get unknown error before we open things) + + NSError *err = [monStream streamError]; + STAssertEqualObjects([err domain], @"NSUnknownErrorDomain", nil); + + // status and properties + + // pre open + STAssertEquals([monStream streamStatus], + (NSStreamStatus)NSStreamStatusNotOpen, nil); + [monStream open]; + // post open + STAssertEquals([monStream streamStatus], + (NSStreamStatus)NSStreamStatusOpen, nil); + STAssertEqualObjects([monStream propertyForKey:NSStreamFileCurrentOffsetKey], + [NSNumber numberWithInt:0], nil); + // read some + uint8_t buf[8]; + long bytesRead = [monStream read:buf maxLength:sizeof(buf)]; + STAssertGreaterThanOrEqual(bytesRead, (long)sizeof(buf), nil); + // post read + STAssertEqualObjects([monStream propertyForKey:NSStreamFileCurrentOffsetKey], + [NSNumber numberWithLong:bytesRead], nil); + [monStream close]; + // post close + STAssertEquals([monStream streamStatus], + (NSStreamStatus)NSStreamStatusClosed, nil); + +} + +- (void)testProgressMessagesViaRead { + + // make a big data buffer (sourceData) + NSMutableData *sourceData = + [NSMutableData dataWithCapacity:kSourceDataByteCount]; + for (int idx = 0; idx < 10000; idx++) { + [sourceData appendBytes:"0123456789" length:10]; + } + STAssertEquals([sourceData length], (NSUInteger)kSourceDataByteCount, nil); + + // make a buffer to hold the data as read from the stream, and an array + // to hold the size of each read + NSMutableData *resultData = [NSMutableData data]; + NSMutableArray *deliverySizesArray = [NSMutableArray array]; + + TestStreamMonitor *monitor = [[[TestStreamMonitor alloc] init] autorelease]; + STAssertNotNil(monitor, nil); + + // create the stream; set self as the monitor + GTMProgressMonitorInputStream* monStream = + [GTMProgressMonitorInputStream inputStreamWithData:sourceData]; + [monStream setMonitorDelegate:monitor + selector:@selector(inputStream:hasDeliveredBytes:ofTotalBytes:)]; + [monStream open]; + + // we'll read random-sized chunks of data from our stream, adding the chunk + // size to deliverySizesArray and the data itself to resultData + srandomdev(); + + NSUInteger bytesReadSoFar = 0; + uint8_t readBuffer[2048]; + while (1) { + NSStreamStatus status = [monStream streamStatus]; + if (bytesReadSoFar < kSourceDataByteCount) { + STAssertTrue([monStream hasBytesAvailable], nil); + STAssertEquals(status, (NSStreamStatus)NSStreamStatusOpen, nil); + } else { + STAssertFalse([monStream hasBytesAvailable], nil); + STAssertEquals(status, (NSStreamStatus)NSStreamStatusAtEnd, nil); + } + + // read a random block size between 1 and 2048 bytes + NSUInteger bytesToRead = (random() % sizeof(readBuffer)) + 1; + NSInteger bytesRead = [monStream read:readBuffer maxLength:bytesToRead]; + + // done? + if (bytesRead <= 0) { + break; + } + + // save the data we just read, and the size of the read + [resultData appendBytes:readBuffer length:bytesRead]; + bytesReadSoFar += bytesRead; + NSNumber *bytesReadSoFarNumber = + [NSNumber numberWithUnsignedLongLong:(unsigned long long)bytesReadSoFar]; + [deliverySizesArray addObject:bytesReadSoFarNumber]; + } + + [monStream close]; + + // compare deliverySizesArray to the array built by our callback, and + // resultData to the sourceData + STAssertEqualObjects(deliverySizesArray, [monitor reportedSizes], + @"unexpected size deliveries"); + NSNumber *sourceNumber = + [NSNumber numberWithUnsignedLongLong:kSourceDataByteCount]; + STAssertEqualObjects([NSSet setWithObject:sourceNumber], + [monitor reportedTotals], + @"unexpected total sizes"); + + // STAssertEqualObjects on the actual NSDatas is hanging when they are unequal + // here so I'll just assert True + STAssertTrue([sourceData isEqualToData:resultData], + @"unexpected data read"); +} + +@end + +@implementation TestStreamMonitor + +- (id)init { + self = [super init]; + if (self) { + reportedDeliverySizesArray_ = [[NSMutableArray alloc] init]; + reportedTotalSizesSet_ = [[NSMutableSet alloc] init]; + } + return self; +} + +- (void) dealloc { + [reportedDeliverySizesArray_ release]; + [reportedTotalSizesSet_ release]; + [super dealloc]; +} + +- (NSArray *)reportedSizes { + return reportedDeliverySizesArray_; +} + +- (NSSet *)reportedTotals { + return reportedTotalSizesSet_; +} + +- (void)inputStream:(GTMProgressMonitorInputStream *)stream + hasDeliveredBytes:(unsigned long long)numRead + ofTotalBytes:(unsigned long long)total { + // add the number read so far to the array + [reportedDeliverySizesArray_ addObject: + [NSNumber numberWithUnsignedLongLong:numRead]]; + [reportedTotalSizesSet_ addObject: + [NSNumber numberWithUnsignedLongLong:total]]; +} + +@end diff --git a/Foundation/GTMRegex.m b/Foundation/GTMRegex.m index fb6e3a0..c142c62 100644 --- a/Foundation/GTMRegex.m +++ b/Foundation/GTMRegex.m @@ -276,7 +276,7 @@ static NSString *const kReplacementPattern = } result = buildResult; - } // COV_NF_LINE - radar 5851992 not all brackets reachable w/ obj-c exceptions and coverage + } // COV_NF_LINE - radar 5851992 only reachable w/ an uncaught exception which isn't testable @finally { free(regMatches); } @@ -649,8 +649,7 @@ static NSString *const kReplacementPattern = isMatch:isMatch] autorelease]; nextMatches = nil; } - } // COV_NF_START - no real way to force this in a test - @catch (id e) { + } @catch (id e) { // COV_NF_START - no real way to force this in a test _GTMDevLog(@"Exceptions while trying to advance enumeration (%@)", e); // if we still have something in our temp, free it if (nextMatches) diff --git a/Foundation/GTMScriptRunnerTest.m b/Foundation/GTMScriptRunnerTest.m index 45378bc..9545045 100644 --- a/Foundation/GTMScriptRunnerTest.m +++ b/Foundation/GTMScriptRunnerTest.m @@ -207,7 +207,13 @@ STAssertEquals([output intValue], numVars, @"should be back down to %d vars", numVars); - NSDictionary *currVars = [[NSProcessInfo processInfo] environment]; + NSMutableDictionary *currVars + = [[[[NSProcessInfo processInfo] environment] mutableCopy] autorelease]; + + // When debugging a release build _ was not in the processInfo environment + // causing the assert below to fail. Not sure why, but it appeared + // to be harmless, and easy to account for. + [currVars setObject:@"/usr/bin/env" forKey:@"_"]; [sr setEnvironment:currVars]; output = [sr run:@"/usr/bin/env | wc -l"]; diff --git a/Foundation/GTMStackTrace.c b/Foundation/GTMStackTrace.c deleted file mode 100644 index 68e5c7d..0000000 --- a/Foundation/GTMStackTrace.c +++ /dev/null @@ -1,92 +0,0 @@ -// -// GTMStackTrace.m -// -// Copyright 2007-2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - -#include <stdlib.h> -#include <dlfcn.h> -#include <mach-o/nlist.h> -#include "GTMStackTrace.h" - -// Structure representing a small portion of a stack, starting from the saved -// frame pointer, and continuing through the saved program counter. -struct StackFrame { - void *saved_fp; -#if defined (__ppc__) || defined(__ppc64__) - void *padding; -#endif - void *saved_pc; -}; - -// __builtin_frame_address(0) is a gcc builtin that returns a pointer to the -// current frame pointer. We then use the frame pointer to walk the stack -// picking off program counters and other saved frame pointers. This works -// great on i386, but PPC requires a little more work because the PC (or link -// register) isn't always stored on the stack. -// -int GTMGetStackProgramCounters(void *outPcs[], int size) { - if (!outPcs || (size < 1)) return 0; - - struct StackFrame *fp; -#if defined (__ppc__) || defined(__ppc64__) - outPcs[0] = __builtin_return_address(0); - fp = (struct StackFrame *)__builtin_frame_address(1); -#elif defined (__i386__) || defined(__x86_64__) - fp = (struct StackFrame *)__builtin_frame_address(0); -#else -#error architecture not supported -#endif - - int level = 0; - while (level < size) { - if (fp == NULL) { - level--; - break; - } - outPcs[level] = fp->saved_pc; - level++; - fp = (struct StackFrame *)fp->saved_fp; - } - - return level; -} - -CFStringRef GTMStackTraceCreate(void) { - // The maximum number of stack frames that we will walk. We limit this so - // that super-duper recursive functions (or bugs) don't send us for an - // infinite loop. - static const int kMaxStackTraceDepth = 100; - void *pcs[kMaxStackTraceDepth]; - int depth = kMaxStackTraceDepth; - depth = GTMGetStackProgramCounters(pcs, depth); - - CFMutableStringRef trace = CFStringCreateMutable(kCFAllocatorDefault, 0); - - for (int i = 0; i < depth; i++) { - Dl_info info = { NULL, NULL, NULL, NULL }; - dladdr(pcs[i], &info); - const char *symbol = info.dli_sname; - const char *fname = info.dli_fname; - - CFStringAppendFormat(trace, NULL, - CFSTR("#%-2d 0x%08lx %s () [%s]\n"), - i, pcs[i], - (symbol ? symbol : "??"), - (fname ? fname : "??")); - } - - return trace; -} diff --git a/Foundation/GTMStackTrace.h b/Foundation/GTMStackTrace.h index cfb9c95..a77ca93 100644 --- a/Foundation/GTMStackTrace.h +++ b/Foundation/GTMStackTrace.h @@ -22,20 +22,20 @@ extern "C" { #endif -/// Returns a string containing a nicely formatted stack trace. -// The caller owns the returned CFStringRef and is responsible for releasing it. -// -// ***************************************************************************** -// The symbolic names returned for Objective-C methods will be INCORRECT. This -// is because dladdr() doesn't properly resolve Objective-C names. The symbol's -// address will be CORRECT, so will be able to use atos or gdb to get a properly -// resolved Objective-C name. -- 5/15/2007 -// TODO: write dladdr() replacement that works with Objective-C symbols. -// ***************************************************************************** +struct GTMAddressDescriptor { + const void *address; // address + const char *symbol; // nearest symbol to address + const char *class_name; // if it is an obj-c method, the method's class + BOOL is_class_method; // if it is an obj-c method, type of method + const char *filename; // file that the method came from. +}; + +// Returns a string containing a nicely formatted stack trace. // -// This function gets the stack trace for the current thread, and is safe to -// use in production multi-threaded code. Typically this function will be used -// along with some loggins, as in the following: +// This function gets the stack trace for the current thread. It will +// be from the caller of GTMStackTrace upwards to the top the calling stack. +// Typically this function will be used along with some logging, +// as in the following: // // MyAppLogger(@"Should never get here:\n%@", GTMStackTrace()); // @@ -49,34 +49,43 @@ extern "C" { // #5 0x00002692 tart () [/Users/me/./StackLog] // #6 0x000025b9 tart () [/Users/me/./StackLog] // -// If you're using this with Objective-C, you may want to use the GTMStackTrace() -// variant that autoreleases the returned string. -// -CFStringRef GTMStackTraceCreate(void); - -/// Wrapper that autoreleases the returned CFStringRef. -// This is simply for the convenience of those using Objective-C. -// -#if __OBJC__ -#include "GTMGarbageCollection.h" -#define GTMStackTrace() [GTMNSMakeCollectable(GTMStackTraceCreate()) autorelease] -#endif -/// Returns an array of program counters from the current thread's stack. +NSString *GTMStackTrace(void); + +// Returns an array of program counters from the current thread's stack. // *** You should probably use GTMStackTrace() instead of this function *** // However, if you actually want all the PCs in "void *" form, then this -// funtion is more convenient. +// funtion is more convenient. This will include PCs of GTMStaceTrace and +// its inner utility functions that you may want to strip out. // // Args: // outPcs - an array of "void *" pointers to the program counters found on the // current thread's stack. -// size - the size of outPcs +// count - the number of entries in the outPcs array // // Returns: // The number of program counters actually added to outPcs. // -int GTMGetStackProgramCounters(void *outPcs[], int size); +int GTMGetStackProgramCounters(void *outPcs[], int count); +// Returns an array of GTMAddressDescriptors from the current thread's stack. +// *** You should probably use GTMStackTrace() instead of this function *** +// However, if you actually want all the PCs with symbols, this is the way +// to get them. There is no memory allocations done, so no clean up is required +// except for the caller to free outDescs if they allocated it themselves. +// This will include PCs of GTMStaceTrace and its inner utility functions that +// you may want to strip out. +// +// Args: +// outDescs - an array of "struct GTMAddressDescriptor" pointers corresponding +// to the program counters found on the current thread's stack. +// count - the number of entries in the outDescs array +// +// Returns: +// The number of program counters actually added to outPcs. +// +int GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], + int count); #ifdef __cplusplus } #endif diff --git a/Foundation/GTMStackTrace.m b/Foundation/GTMStackTrace.m new file mode 100644 index 0000000..5dc1c0b --- /dev/null +++ b/Foundation/GTMStackTrace.m @@ -0,0 +1,228 @@ +// +// GTMStackTrace.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#include <stdlib.h> +#include <dlfcn.h> +#include <mach-o/nlist.h> +#include "GTMStackTrace.h" +#include "GTMObjC2Runtime.h" + +// Structure representing a small portion of a stack, starting from the saved +// frame pointer, and continuing through the saved program counter. +struct GTMStackFrame { + void *saved_fp; +#if defined (__ppc__) || defined(__ppc64__) + void *padding; +#endif + void *saved_pc; +}; + +struct GTMClassDescription { + const char *class_name; + Method *class_methods; + unsigned int class_method_count; + Method *instance_methods; + unsigned int instance_method_count; +}; + +#pragma mark Private utility functions + +static struct GTMClassDescription *GTMClassDescriptions(int *total_count) { + int class_count = objc_getClassList(nil, 0); + struct GTMClassDescription *class_descs + = calloc(class_count, sizeof(struct GTMClassDescription)); + if (class_descs) { + Class *classes = calloc(class_count, sizeof(Class)); + if (classes) { + objc_getClassList(classes, class_count); + for (int i = 0; i < class_count; ++i) { + class_descs[i].class_methods + = class_copyMethodList(object_getClass(classes[i]), + &class_descs[i].class_method_count); + class_descs[i].instance_methods + = class_copyMethodList(classes[i], + &class_descs[i].instance_method_count); + class_descs[i].class_name = class_getName(classes[i]); + } + free(classes); + } else { + free(class_descs); + class_count = 0; + } + } + if (total_count) { + *total_count = class_count; + } + return class_descs; +} + +static void GTMFreeClassDescriptions(struct GTMClassDescription *class_descs, + int count) { + if (!class_descs || count < 0) return; + for (int i = 0; i < count; ++i) { + if (class_descs[i].instance_methods) { + free(class_descs[i].instance_methods); + } + if (class_descs[i].class_methods) { + free(class_descs[i].class_methods); + } + } + free(class_descs); +} + +#pragma mark Public functions + +// __builtin_frame_address(0) is a gcc builtin that returns a pointer to the +// current frame pointer. We then use the frame pointer to walk the stack +// picking off program counters and other saved frame pointers. This works +// great on i386, but PPC requires a little more work because the PC (or link +// register) isn't always stored on the stack. +// +int GTMGetStackProgramCounters(void *outPcs[], int count) { + if (!outPcs || (count < 1)) return 0; + + struct GTMStackFrame *fp; +#if defined (__ppc__) || defined(__ppc64__) + outPcs[0] = __builtin_return_address(0); + fp = (struct GTMStackFrame *)__builtin_frame_address(1); +#elif defined (__i386__) || defined(__x86_64__) + fp = (struct GTMStackFrame *)__builtin_frame_address(0); +#else +#error architecture not supported +#endif + + int level = 0; + while (level < count) { + if (fp == NULL) { + level--; + break; + } + outPcs[level] = fp->saved_pc; + level++; + fp = (struct GTMStackFrame *)fp->saved_fp; + } + + return level; +} + +int GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], + int count) { + if (count < 1) return 0; + + void **pcs = calloc(count, sizeof(void*)); + if (!pcs) return 0; + + int newSize = GTMGetStackProgramCounters(pcs, count); + int class_desc_count; + + // Get our obj-c class descriptions. This is expensive, so we do it once + // at the top. We go through this because dladdr doesn't work with + // obj methods. + struct GTMClassDescription *class_descs + = GTMClassDescriptions(&class_desc_count); + + // Iterate through the stack. + for (int i = 0; i < newSize; ++i) { + const char *class_name = NULL; + Boolean is_class_method = FALSE; + size_t smallest_diff = SIZE_MAX; + struct GTMAddressDescriptor *currDesc = &outDescs[i]; + currDesc->address = pcs[i]; + Method best_method = NULL; + // Iterate through all the classes we know of. + for (int j = 0; j < class_desc_count; ++j) { + // First check the class methods. + for (unsigned int k = 0; k < class_descs[j].class_method_count; ++k) { + IMP imp = method_getImplementation(class_descs[j].class_methods[k]); + if (imp <= (IMP)currDesc->address) { + size_t diff = (size_t)currDesc->address - (size_t)imp; + if (diff < smallest_diff) { + best_method = class_descs[j].class_methods[k]; + class_name = class_descs[j].class_name; + is_class_method = TRUE; + smallest_diff = diff; + } + } + } + // Then check the instance methods. + for (unsigned int k = 0; k < class_descs[j].instance_method_count; ++k) { + IMP imp = method_getImplementation(class_descs[j].instance_methods[k]); + if (imp <= (IMP)currDesc->address) { + size_t diff = (size_t)currDesc->address - (size_t)imp; + if (diff < smallest_diff) { + best_method = class_descs[j].instance_methods[k]; + class_name = class_descs[j].class_name; + is_class_method = TRUE; + smallest_diff = diff; + } + } + } + } + + // If we have one, store it off. + if (best_method) { + currDesc->symbol = sel_getName(method_getName(best_method)); + currDesc->is_class_method = is_class_method; + currDesc->class_name = class_name; + } + Dl_info info = { NULL, NULL, NULL, NULL }; + + // Check to see if the one returned by dladdr is better. + dladdr(currDesc->address, &info); + if ((size_t)currDesc->address - (size_t)info.dli_saddr < smallest_diff) { + currDesc->symbol = info.dli_sname; + currDesc->is_class_method = FALSE; + currDesc->class_name = NULL; + } + currDesc->filename = info.dli_fname; + } + GTMFreeClassDescriptions(class_descs, class_desc_count); + return newSize; +} + +NSString *GTMStackTrace(void) { + // The maximum number of stack frames that we will walk. We limit this so + // that super-duper recursive functions (or bugs) don't send us for an + // infinite loop. + const int kMaxStackTraceDepth = 100; + struct GTMAddressDescriptor descs[kMaxStackTraceDepth]; + int depth = kMaxStackTraceDepth; + depth = GTMGetStackAddressDescriptors(descs, depth); + + NSMutableString *trace = [NSMutableString string]; + + // Start at the second item so that GTMStackTrace and it's utility calls (of + // which there is currently 1) is not included in the output. + const int kTracesToStrip = 2; + for (int i = kTracesToStrip; i < depth; i++) { + if (descs[i].class_name) { + [trace appendFormat:@"#%-2d 0x%08lx %s[%s %s] (%s)\n", + i - kTracesToStrip, descs[i].address, + (descs[i].is_class_method ? "+" : "-"), + descs[i].class_name, + (descs[i].symbol ? descs[i].symbol : "??"), + (descs[i].filename ? descs[i].filename : "??")]; + } else { + [trace appendFormat:@"#%-2d 0x%08lx %s() (%s)\n", + i - kTracesToStrip, descs[i].address, + (descs[i].symbol ? descs[i].symbol : "??"), + (descs[i].filename ? descs[i].filename : "??")]; + } + } + return trace; +} diff --git a/Foundation/GTMStackTraceTest.m b/Foundation/GTMStackTraceTest.m index edcb851..dd92fe9 100644 --- a/Foundation/GTMStackTraceTest.m +++ b/Foundation/GTMStackTraceTest.m @@ -35,9 +35,13 @@ @"stack trace must have < 25 lines"); NSString *firstFrame = [stacklines objectAtIndex:0]; - NSRange range = [firstFrame rangeOfString:@"GTMStackTrace"]; + NSRange range = [firstFrame rangeOfString:@"testStackTraceBasic"]; STAssertNotEquals(range.location, (NSUInteger)NSNotFound, - @"First frame should contain GTMStackTrace, stack trace: %@", + @"First frame should contain testStackTraceBasic," + " stack trace: %@", stacktrace); + range = [firstFrame rangeOfString:@"#0"]; + STAssertNotEquals(range.location, (NSUInteger)NSNotFound, + @"First frame should contain #0, stack trace: %@", stacktrace); } |