aboutsummaryrefslogtreecommitdiff
path: root/Foundation
diff options
context:
space:
mode:
authorGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-11-04 20:10:52 +0000
committerGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-11-04 20:10:52 +0000
commit8ddb49cefd01b220ad5e1d2f0060b2a0ad54efdb (patch)
treeb1cb7590d31d8086eb3929028303e1d16324e115 /Foundation
parent750b28c89618586a0450cacb86e28cd709374c9d (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.h2
-rw-r--r--Foundation/GTMLogger.h2
-rw-r--r--Foundation/GTMLoggerTest.m4
-rw-r--r--Foundation/GTMNSAppleEvent+HandlerTest.applescript6
-rw-r--r--Foundation/GTMNSAppleScript+Handler.h4
-rw-r--r--Foundation/GTMNSAppleScript+Handler.m15
-rw-r--r--Foundation/GTMNSAppleScript+HandlerTest.m10
-rw-r--r--Foundation/GTMNSFileManager+Path.h4
-rw-r--r--Foundation/GTMNSFileManager+Path.m4
-rw-r--r--Foundation/GTMNSFileManager+PathTest.m6
-rw-r--r--Foundation/GTMNSString+Replace.h4
-rw-r--r--Foundation/GTMNSString+Replace.m4
-rw-r--r--Foundation/GTMNSString+ReplaceTest.m4
-rw-r--r--Foundation/GTMObjC2Runtime.h2
-rw-r--r--Foundation/GTMObjC2Runtime.m2
-rw-r--r--Foundation/GTMObjC2RuntimeTest.m2
-rw-r--r--Foundation/GTMPathTest.m2
-rw-r--r--Foundation/GTMStackTrace.h15
-rw-r--r--Foundation/GTMStackTrace.m178
-rw-r--r--Foundation/GTMStackTraceTest.m41
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