diff options
author | thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-11-04 20:10:52 +0000 |
---|---|---|
committer | thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-11-04 20:10:52 +0000 |
commit | 8ddb49cefd01b220ad5e1d2f0060b2a0ad54efdb (patch) | |
tree | b1cb7590d31d8086eb3929028303e1d16324e115 /Foundation | |
parent | 750b28c89618586a0450cacb86e28cd709374c9d (diff) |
- Added has ability to check if a script has an open handler to GTMNSAppleScript+Handler.
- GTMStackTrace support for building a trace from the call stack in an NSException (for 10.5+ and iPhone).
- Added GTMUIFont+LineHeight.
- Cleaned up some OS version checks to use constants instead of numbers directly.
Diffstat (limited to 'Foundation')
-rw-r--r-- | Foundation/GTMGarbageCollection.h | 2 | ||||
-rw-r--r-- | Foundation/GTMLogger.h | 2 | ||||
-rw-r--r-- | Foundation/GTMLoggerTest.m | 4 | ||||
-rw-r--r-- | Foundation/GTMNSAppleEvent+HandlerTest.applescript | 6 | ||||
-rw-r--r-- | Foundation/GTMNSAppleScript+Handler.h | 4 | ||||
-rw-r--r-- | Foundation/GTMNSAppleScript+Handler.m | 15 | ||||
-rw-r--r-- | Foundation/GTMNSAppleScript+HandlerTest.m | 10 | ||||
-rw-r--r-- | Foundation/GTMNSFileManager+Path.h | 4 | ||||
-rw-r--r-- | Foundation/GTMNSFileManager+Path.m | 4 | ||||
-rw-r--r-- | Foundation/GTMNSFileManager+PathTest.m | 6 | ||||
-rw-r--r-- | Foundation/GTMNSString+Replace.h | 4 | ||||
-rw-r--r-- | Foundation/GTMNSString+Replace.m | 4 | ||||
-rw-r--r-- | Foundation/GTMNSString+ReplaceTest.m | 4 | ||||
-rw-r--r-- | Foundation/GTMObjC2Runtime.h | 2 | ||||
-rw-r--r-- | Foundation/GTMObjC2Runtime.m | 2 | ||||
-rw-r--r-- | Foundation/GTMObjC2RuntimeTest.m | 2 | ||||
-rw-r--r-- | Foundation/GTMPathTest.m | 2 | ||||
-rw-r--r-- | Foundation/GTMStackTrace.h | 15 | ||||
-rw-r--r-- | Foundation/GTMStackTrace.m | 178 | ||||
-rw-r--r-- | Foundation/GTMStackTraceTest.m | 41 |
20 files changed, 228 insertions, 83 deletions
diff --git a/Foundation/GTMGarbageCollection.h b/Foundation/GTMGarbageCollection.h index b29a13b..58c8140 100644 --- a/Foundation/GTMGarbageCollection.h +++ b/Foundation/GTMGarbageCollection.h @@ -28,7 +28,7 @@ // http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/Articles/gcCoreFoundation.html#//apple_ref/doc/uid/TP40006687-SW1 // for details. -#if (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050) && !GTM_IPHONE_SDK +#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) && !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 diff --git a/Foundation/GTMLogger.h b/Foundation/GTMLogger.h index 8fdb0fa..1626b1b 100644 --- a/Foundation/GTMLogger.h +++ b/Foundation/GTMLogger.h @@ -54,7 +54,7 @@ // Predeclaration of used protocols that are declared later in this file. @protocol GTMLogWriter, GTMLogFormatter, GTMLogFilter; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 #define CHECK_FORMAT_NSSTRING(a, b) __attribute__((format(__NSString__, a, b))) #else #define CHECK_FORMAT_NSSTRING(a, b) diff --git a/Foundation/GTMLoggerTest.m b/Foundation/GTMLoggerTest.m index 043c73d..86ced5e 100644 --- a/Foundation/GTMLoggerTest.m +++ b/Foundation/GTMLoggerTest.m @@ -94,7 +94,7 @@ @"GTMLoggerUnitTest.log"] retain]; STAssertNotNil(path_, nil); // Make sure we're cleaned up from the last run -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 [[NSFileManager defaultManager] removeFileAtPath:path_ handler:nil]; #else [[NSFileManager defaultManager] removeItemAtPath:path_ error:NULL]; @@ -103,7 +103,7 @@ - (void)tearDown { STAssertNotNil(path_, nil); -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 [[NSFileManager defaultManager] removeFileAtPath:path_ handler:nil]; #else [[NSFileManager defaultManager] removeItemAtPath:path_ error:NULL]; diff --git a/Foundation/GTMNSAppleEvent+HandlerTest.applescript b/Foundation/GTMNSAppleEvent+HandlerTest.applescript index a0822f9..377d733 100644 --- a/Foundation/GTMNSAppleEvent+HandlerTest.applescript +++ b/Foundation/GTMNSAppleEvent+HandlerTest.applescript @@ -27,6 +27,8 @@ script testScript on testScriptFunc() return "child" end testScriptFunc + on open foo + end open end script property foo : 1 @@ -54,5 +56,5 @@ on testGetScript() return testScript end testGetScript -on open -end open +on print +end print diff --git a/Foundation/GTMNSAppleScript+Handler.h b/Foundation/GTMNSAppleScript+Handler.h index e63eaee..6e3af50 100644 --- a/Foundation/GTMNSAppleScript+Handler.h +++ b/Foundation/GTMNSAppleScript+Handler.h @@ -119,4 +119,8 @@ enum { - (BOOL)gtm_setValue:(id)value forPropertyEnum:(DescType)property addingDefinition:(BOOL)adding; + +// Return YES if the script has an open documents (odoc) handler +// Does not require script compilation, so it's a fast check. +- (BOOL)gtm_hasOpenDocumentsHandler; @end diff --git a/Foundation/GTMNSAppleScript+Handler.m b/Foundation/GTMNSAppleScript+Handler.m index 592aca6..be4a2dc 100644 --- a/Foundation/GTMNSAppleScript+Handler.m +++ b/Foundation/GTMNSAppleScript+Handler.m @@ -232,6 +232,21 @@ GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); return desc; } +- (BOOL)gtm_hasOpenDocumentsHandler { + ComponentInstance component; + OSAID osaID = [self gtm_realIDAndComponent:&component]; + long value = 0; + OSAError error = OSAGetScriptInfo(component, + osaID, + kASHasOpenHandler, + &value); + if (error) { + _GTMDevLog(@"Unable to get script info about open handler %d", error); + value = 0; + } + return value != 0; +} + - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; if (!signature) { diff --git a/Foundation/GTMNSAppleScript+HandlerTest.m b/Foundation/GTMNSAppleScript+HandlerTest.m index 102332e..02f62e0 100644 --- a/Foundation/GTMNSAppleScript+HandlerTest.m +++ b/Foundation/GTMNSAppleScript+HandlerTest.m @@ -278,7 +278,7 @@ - (void)testHandlers { NSSet *handlers = [script_ gtm_handlers]; NSSet *expected = [NSSet setWithObjects: - @"aevtodoc", + @"aevtpdoc", @"test", @"testreturnone", @"testreturnparam", @@ -475,6 +475,14 @@ STAssertNotNil(handlers, @"Couldn't get handlers"); } +- (void)testOpenHandler { + STAssertFalse([script_ gtm_hasOpenDocumentsHandler], nil); + id script = [script_ gtm_valueForProperty:@"testscript"]; + STAssertNotNil(script, nil); + STAssertTrue([script gtm_hasOpenDocumentsHandler], nil); +} + + @protocol ScriptInterface - (id)test; - (id)testReturnParam:(id)param; diff --git a/Foundation/GTMNSFileManager+Path.h b/Foundation/GTMNSFileManager+Path.h index 2ed6888..f377215 100644 --- a/Foundation/GTMNSFileManager+Path.h +++ b/Foundation/GTMNSFileManager+Path.h @@ -22,7 +22,7 @@ /// A few useful methods for dealing with paths. @interface NSFileManager (GMFileManagerPathAdditions) -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 /// For the Unix-y at heart, this is "mkdir -p". It tries to create /// the directory specified by |path|, and any intervening directories that @@ -47,7 +47,7 @@ - (BOOL)gtm_createFullPathToDirectory:(NSString *)path attributes:(NSDictionary *)attributes; -#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 /// Return an the paths for all resources in |directoryPath| that have the /// |extension| file extension. diff --git a/Foundation/GTMNSFileManager+Path.m b/Foundation/GTMNSFileManager+Path.m index 6dec24b..2d71729 100644 --- a/Foundation/GTMNSFileManager+Path.m +++ b/Foundation/GTMNSFileManager+Path.m @@ -20,7 +20,7 @@ @implementation NSFileManager (GMFileManagerPathAdditions) -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - (BOOL)gtm_createFullPathToDirectory:(NSString *)path attributes:(NSDictionary *)attributes { @@ -52,7 +52,7 @@ return YES; } -#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - (NSArray *)gtm_filePathsWithExtension:(NSString *)extension inDirectory:(NSString *)directoryPath { diff --git a/Foundation/GTMNSFileManager+PathTest.m b/Foundation/GTMNSFileManager+PathTest.m index ac36cac..db40c68 100644 --- a/Foundation/GTMNSFileManager+PathTest.m +++ b/Foundation/GTMNSFileManager+PathTest.m @@ -48,7 +48,7 @@ if (baseDir_) { // clean up our directory NSFileManager *fm = [NSFileManager defaultManager]; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 NSError *error = nil; [fm removeItemAtPath:baseDir_ error:&error]; STAssertNil(error, @@ -62,7 +62,7 @@ } } -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - (void)testCreateFullPathToDirectoryAttributes { STAssertNotNil(baseDir_, @"setUp failed"); @@ -91,7 +91,7 @@ @"Should have failed when passed (nil)"); } -#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - (void)testfilePathsWithExtensionsInDirectory { STAssertNotNil(baseDir_, @"setUp failed"); diff --git a/Foundation/GTMNSString+Replace.h b/Foundation/GTMNSString+Replace.h index edc8478..71a98c5 100644 --- a/Foundation/GTMNSString+Replace.h +++ b/Foundation/GTMNSString+Replace.h @@ -21,7 +21,7 @@ /// Give easy search-n-replace functionality to NSString. @interface NSString (GTMStringReplaceAdditions) -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 // 10.5 has stringByReplacingOccurrencesOfString:withString:, use that directly. /// Returns a new autoreleased string by replacing all occurrences of @@ -40,6 +40,6 @@ - (NSString *)gtm_stringByReplacingString:(NSString *)target withString:(NSString *)replacement; -#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 @end diff --git a/Foundation/GTMNSString+Replace.m b/Foundation/GTMNSString+Replace.m index f617945..ddaad5e 100644 --- a/Foundation/GTMNSString+Replace.m +++ b/Foundation/GTMNSString+Replace.m @@ -21,7 +21,7 @@ @implementation NSString (GTMStringReplaceAdditions) -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 // 10.5 has stringByReplacingOccurrencesOfString:withString:, use that directly. - (NSString *)gtm_stringByReplacingString:(NSString *)target @@ -48,6 +48,6 @@ return result; } -#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 @end diff --git a/Foundation/GTMNSString+ReplaceTest.m b/Foundation/GTMNSString+ReplaceTest.m index 805136c..4561af6 100644 --- a/Foundation/GTMNSString+ReplaceTest.m +++ b/Foundation/GTMNSString+ReplaceTest.m @@ -24,7 +24,7 @@ @implementation GTMNSString_ReplaceTest -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - (void)testStringByReplacingStringWithString { NSString *testString = @"a bc debc gh"; @@ -54,6 +54,6 @@ @"replacing '' with anything should yield the original string"); } -#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 @end diff --git a/Foundation/GTMObjC2Runtime.h b/Foundation/GTMObjC2Runtime.h index f94e680..f901d1e 100644 --- a/Foundation/GTMObjC2Runtime.h +++ b/Foundation/GTMObjC2Runtime.h @@ -47,7 +47,7 @@ #import <objc/Object.h> #endif -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 #import "objc/Protocol.h" #ifdef __cplusplus diff --git a/Foundation/GTMObjC2Runtime.m b/Foundation/GTMObjC2Runtime.m index ba0cb74..9835654 100644 --- a/Foundation/GTMObjC2Runtime.m +++ b/Foundation/GTMObjC2Runtime.m @@ -18,7 +18,7 @@ #import "GTMObjC2Runtime.h" -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 #import <stdlib.h> #import <string.h> diff --git a/Foundation/GTMObjC2RuntimeTest.m b/Foundation/GTMObjC2RuntimeTest.m index 1bc0ee3..d0ce4f7 100644 --- a/Foundation/GTMObjC2RuntimeTest.m +++ b/Foundation/GTMObjC2RuntimeTest.m @@ -299,7 +299,7 @@ AT_REQUIRED // Apparently it was a bug that we could call setImplementation with a nil // so we now test to make sure that setting to nil works as expected on // all systems. -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 // Built for less then leopard gives us the behaviors we defined... // (doesn't take nil) IMP nullImp = method_setImplementation(list[0], nil); diff --git a/Foundation/GTMPathTest.m b/Foundation/GTMPathTest.m index 3a60ea7..211206c 100644 --- a/Foundation/GTMPathTest.m +++ b/Foundation/GTMPathTest.m @@ -46,7 +46,7 @@ // Make sure it's safe to remove this directory before nuking it. STAssertNotNil(testDirectory_, nil); STAssertNotEqualObjects(testDirectory_, @"/", nil); -#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 [[NSFileManager defaultManager] removeFileAtPath:testDirectory_ handler:nil]; #else [[NSFileManager defaultManager] removeItemAtPath:testDirectory_ error:NULL]; diff --git a/Foundation/GTMStackTrace.h b/Foundation/GTMStackTrace.h index 3b6965d..9726da5 100644 --- a/Foundation/GTMStackTrace.h +++ b/Foundation/GTMStackTrace.h @@ -51,7 +51,17 @@ struct GTMAddressDescriptor { // #6 0x000025b9 tart () [/Users/me/./StackLog] // +#ifdef GTM_MACOS_SDK // currently not supported on iPhone NSString *GTMStackTrace(void); +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 +// Returns a string containing a nicely formatted stack trace from the +// exception. Only available on 10.5 or later, uses +// -[NSException callStackReturnAddresses]. +// +NSString *GTMStackTraceFromException(NSException *e); +#endif // Returns an array of program counters from the current thread's stack. // *** You should probably use GTMStackTrace() instead of this function *** @@ -67,7 +77,9 @@ NSString *GTMStackTrace(void); // Returns: // The number of program counters actually added to outPcs. // +#ifdef GTM_MACOS_SDK // currently not supported on iPhone NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count); +#endif // Returns an array of GTMAddressDescriptors from the current thread's stack. // *** You should probably use GTMStackTrace() instead of this function *** @@ -85,8 +97,11 @@ NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count); // Returns: // The number of program counters actually added to outPcs. // +#ifdef GTM_MACOS_SDK // currently not supported on iPhone NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], NSUInteger count); +#endif + #ifdef __cplusplus } #endif diff --git a/Foundation/GTMStackTrace.m b/Foundation/GTMStackTrace.m index 8a14bcf..c22c153 100644 --- a/Foundation/GTMStackTrace.m +++ b/Foundation/GTMStackTrace.m @@ -87,49 +87,11 @@ static void GTMFreeClassDescriptions(struct GTMClassDescription *class_descs, 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. -// -NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger 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 - - NSUInteger level = 0; - while (level < count) { - if (fp == NULL) { - level--; - break; - } - outPcs[level] = fp->saved_pc; - level++; - fp = (struct GTMStackFrame *)fp->saved_fp; - } - - return level; -} - -NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], - NSUInteger count) { - if (count < 1 || !outDescs) return 0; +static NSUInteger GTMGetStackAddressDescriptorsForAddresses(void *pcs[], + struct GTMAddressDescriptor outDescs[], + NSUInteger count) { + if (count < 1 || !pcs || !outDescs) return 0; - void **pcs = calloc(count, sizeof(void*)); - if (!pcs) return 0; - - NSUInteger newSize = GTMGetStackProgramCounters(pcs, count); NSUInteger class_desc_count; // Get our obj-c class descriptions. This is expensive, so we do it once @@ -139,7 +101,7 @@ NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], = GTMClassDescriptions(&class_desc_count); // Iterate through the stack. - for (NSUInteger i = 0; i < newSize; ++i) { + for (NSUInteger i = 0; i < count; ++i) { const char *class_name = NULL; Boolean is_class_method = FALSE; size_t smallest_diff = SIZE_MAX; @@ -194,8 +156,84 @@ NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], currDesc->filename = info.dli_fname; } GTMFreeClassDescriptions(class_descs, class_desc_count); + return count; +} + +static NSString *GTMStackTraceFromAddressDescriptors(struct GTMAddressDescriptor descs[], + NSUInteger count) { + NSMutableString *trace = [NSMutableString string]; + + for (NSUInteger i = 0; i < count; i++) { + // Newline between all the lines + if (i) { + [trace appendString:@"\n"]; + } + if (descs[i].class_name) { + [trace appendFormat:@"#%-2u %#08lx %s[%s %s] (%s)", + i, 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:@"#%-2u %#08lx %s() (%s)", + i, descs[i].address, + (descs[i].symbol ? descs[i].symbol : "??"), + (descs[i].filename ? descs[i].filename : "??")]; + } + } + return trace; +} + +#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. +// +#ifdef GTM_MACOS_SDK // currently not supported on iPhone +NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger 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 + + NSUInteger level = 0; + while (level < count) { + if (fp == NULL) { + level--; + break; + } + outPcs[level] = fp->saved_pc; + level++; + fp = (struct GTMStackFrame *)fp->saved_fp; + } + + return level; +} + +NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[], + NSUInteger count) { + if (count < 1 || !outDescs) return 0; + + void **pcs = calloc(count, sizeof(void*)); + if (!pcs) return 0; + + NSUInteger newSize = GTMGetStackProgramCounters(pcs, count); + + NSUInteger result + = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, newSize); free(pcs); - return newSize; + return result; } NSString *GTMStackTrace(void) { @@ -206,25 +244,47 @@ NSString *GTMStackTrace(void) { size_t depth = sizeof(descs) / sizeof(struct GTMAddressDescriptor); 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 size_t kTracesToStrip = 2; - for (size_t 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 : "??")]; + if (depth > kTracesToStrip) { + return GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip], + (depth - kTracesToStrip)); + } + // If we didn't have enough frames, return an empty string + return @""; +} +#endif // GTM_MACOS_SDK + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + +NSString *GTMStackTraceFromException(NSException *e) { + NSString *trace = @""; + + // collect the addresses + NSArray *addresses = [e callStackReturnAddresses]; + NSUInteger count = [addresses count]; + if (count) { + void **pcs = calloc(count, sizeof(void*)); + struct GTMAddressDescriptor *descs + = calloc(count, sizeof(struct GTMAddressDescriptor)); + if (pcs && descs) { + void **pcsScanner = pcs; + for (NSNumber *address in addresses) { + NSUInteger addr = [address unsignedIntegerValue]; + *pcsScanner = (void *)addr; + ++pcsScanner; + } + // Fill in the desc structures + count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count); + // Build the trace + trace = GTMStackTraceFromAddressDescriptors(descs, count); } + if (pcs) free(pcs); + if (descs) free(descs); } + return trace; } + +#endif // MAC_OS_X_VERSION_MIN_REQUIRED diff --git a/Foundation/GTMStackTraceTest.m b/Foundation/GTMStackTraceTest.m index dc5ea57..01b02a3 100644 --- a/Foundation/GTMStackTraceTest.m +++ b/Foundation/GTMStackTraceTest.m @@ -25,6 +25,7 @@ @implementation GTMStackTraceTest +#ifdef GTM_MACOS_SDK // currently not supported on iPhone - (void)testStackTraceBasic { NSString *stacktrace = GTMStackTrace(); NSArray *stacklines = [stacktrace componentsSeparatedByString:@"\n"]; @@ -44,7 +45,46 @@ @"First frame should contain #0, stack trace: %@", stacktrace); } +#endif // GTM_MACOS_SDK +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + +- (void)helperThatThrows { + [NSException raise:@"TestException" format:@"TestExceptionDescription"]; +} + +- (void)testStackExceptionTrace { + NSException *exception = nil; + @try { + [self helperThatThrows]; + } + @catch (NSException * e) { + exception = e; + } + STAssertNotNil(exception, nil); + NSString *stacktrace = GTMStackTraceFromException(exception); + NSArray *stacklines = [stacktrace componentsSeparatedByString:@"\n"]; + + STAssertGreaterThan([stacklines count], (NSUInteger)4, + @"stack trace must have > 4 lines"); + STAssertLessThan([stacklines count], (NSUInteger)25, + @"stack trace must have < 25 lines"); + STAssertEquals([stacklines count], + [[exception callStackReturnAddresses] count], + @"stack trace should have the same number of lines as the " + @" array of return addresses. stack trace: %@", stacktrace); + + // we can't look for it on a specific frame because NSException doesn't + // really document how deep the stack will be + NSRange range = [stacktrace rangeOfString:@"testStackExceptionTrace"]; + STAssertNotEquals(range.location, (NSUInteger)NSNotFound, + @"Stack trace should contain testStackExceptionTrace," + " stack trace: %@", stacktrace); +} + +#endif + +#ifdef GTM_MACOS_SDK // currently not supported on iPhone - (void)testProgramCountersBasic { void *pcs[10]; NSUInteger depth = 10; @@ -80,5 +120,6 @@ void *current_pc = __builtin_return_address(0); STAssertEquals(pcs2[1], current_pc, @"pcs[1] should equal the current PC"); } +#endif // GTM_MACOS_SDK @end |