/ NSException.m
NSException.m
  1  //
  2  //  NSException.m
  3  //  CoreFoundation
  4  //
  5  //  Copyright (c) 2014 Apportable. All rights reserved.
  6  //
  7  
  8  #import <Foundation/NSException.h>
  9  #import <Foundation/NSDictionary.h>
 10  #import <Foundation/NSString.h>
 11  #import <Foundation/NSArray.h>
 12  #import "CFString.h"
 13  #import "CFNumber.h"
 14  #import <dispatch/dispatch.h>
 15  #import <objc/runtime.h>
 16  #import <execinfo.h>
 17  
 18  @interface NSException ()
 19  - (BOOL)_installStackTraceKeyIfNeeded;
 20  @end
 21  
 22  typedef id (*objc_exception_preprocessor)(id exception);
 23  extern objc_exception_preprocessor objc_setExceptionPreprocessor(objc_exception_preprocessor fn);
 24  
 25  static NSException *__exceptionPreprocess(NSException *exception)
 26  {
 27      // this is quite expensive (1/3 sec lag), when it can be made more performant (under 1/60 sec) this should be re-enabled
 28      // UPDATE(facekapow): i've re-enabled this even though nothing has really changed in the code, but from limited testing
 29      //                    it seems that this is pretty performant. besides, exceptions are just that: *exceptions*.
 30      //                    you shouldn't be constantly throwing around exceptions.
 31  #if 1
 32      [exception _installStackTraceKeyIfNeeded];
 33  #endif
 34      return exception;
 35  }
 36  
 37  static void NSExceptionInitializer() __attribute__((constructor));
 38  static void NSExceptionInitializer()
 39  {
 40  #ifndef __i386__
 41      objc_setExceptionPreprocessor(&__exceptionPreprocess);
 42  #endif
 43  }
 44  
 45  NSString *const NSGenericException = @"NSGenericException";
 46  NSString *const NSRangeException = @"NSRangeException";
 47  NSString *const NSInvalidArgumentException = @"NSInvalidArgumentException";
 48  NSString *const NSInternalInconsistencyException = @"NSInternalInconsistencyException";
 49  NSString *const NSMallocException = @"NSMallocException";
 50  NSString *const NSObjectInaccessibleException = @"NSObjectInaccessibleException";
 51  NSString *const NSObjectNotAvailableException = @"NSObjectNotAvailableException";
 52  NSString *const NSDestinationInvalidException = @"NSDestinationInvalidException";
 53  NSString *const NSPortTimeoutException = @"NSPortTimeoutException";
 54  NSString *const NSInvalidSendPortException = @"NSInvalidSendPortException";
 55  NSString *const NSInvalidReceivePortException = @"NSInvalidReceivePortException";
 56  NSString *const NSPortSendException = @"NSPortSendException";
 57  NSString *const NSPortReceiveException = @"NSPortReceiveException";
 58  NSString *const NSCharacterConversionException = @"NSCharacterConversionException";
 59  NSString *const NSFileHandleOperationException = @"NSFileHandleOperationException";
 60  
 61  @implementation NSException
 62  
 63  - (instancetype) init {
 64      [self release]; // initWithName:reason:userInfo: is the only acceptable init method
 65      return nil;
 66  }
 67  
 68  - (instancetype) initWithName: (NSExceptionName) name
 69                         reason: (NSString *) reason
 70                       userInfo: (NSDictionary *) userInfo
 71  {
 72      self = [super init];
 73      if (self) {
 74          _name = [name copy];
 75          _reason = [reason copy];
 76          _userInfo = [userInfo copy];
 77      }
 78      return self;
 79  }
 80  
 81  - (instancetype) copyWithZone: (NSZone *) zone {
 82      return [self retain];
 83  }
 84  
 85  - (void) dealloc {
 86      [_name release];
 87      [_reason release];
 88      [_userInfo release];
 89      [_reserved release];
 90      [super dealloc];
 91  }
 92  
 93  - (void) raise {
 94      @throw self;
 95  }
 96  
 97  + (void) raise: (NSExceptionName) name
 98          format: (NSString *) format, ...
 99  {
100      va_list args;
101      va_start(args, format);
102      CFStringRef reason = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, (CFStringRef) format, args);
103      va_end(args);
104      NSException *exc = [self exceptionWithName: name
105                                          reason: reason
106                                        userInfo: nil];
107      [exc raise];
108      CFRelease(reason);
109  }
110  
111  + (void) raise: (NSExceptionName) name
112          format: (NSString *) format
113       arguments: (va_list) args
114  {
115      CFStringRef reason = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, (CFStringRef) format, args);
116      NSException *exc = [self exceptionWithName: name
117                                          reason: reason
118                                        userInfo: nil];
119      [exc raise];
120      CFRelease(reason);
121  }
122  
123  + (NSException *) exceptionWithName: (NSExceptionName) name
124                               reason: (NSString *) reason
125                             userInfo: (NSDictionary *) userInfo
126  {
127      return [[[self alloc] initWithName: name
128                                  reason: reason
129                                userInfo: userInfo] autorelease];
130  }
131  
132  - (NSExceptionName) name {
133      return _name;
134  }
135  
136  - (NSString *) reason {
137      return _reason;
138  }
139  
140  - (NSDictionary *) userInfo {
141      return _userInfo;
142  }
143  
144  - (BOOL) _installStackTraceKeyIfNeeded {
145      if (_reserved == NULL) {
146          _reserved = [[NSMutableDictionary alloc] init];
147      }
148  
149      NSArray *callStackSymbols = nil;
150      if (_userInfo != nil) {
151          callStackSymbols = _userInfo[@"NSStackTraceKey"];
152      }
153  
154      if (callStackSymbols == nil) {
155          callStackSymbols = _reserved[@"callStackSymbols"];
156      } else {
157          _reserved[@"callStackSymbols"] = callStackSymbols;
158      }
159  
160      if (callStackSymbols == nil) {
161          void *stack[128] = { NULL };
162          CFStringRef symbols[128] = { nil };
163          CFNumberRef returnAddresses[128] = { nil };
164  
165          int count = backtrace(stack, sizeof(stack) / sizeof(stack[0]));
166          char **sym = backtrace_symbols(stack, count);
167          if (sym == NULL) {
168              return NO;
169          }
170  
171          // Make sure to skip this frame since it is just an instantiator.
172          for (int i = 1; i < count; i++) {
173              returnAddresses[i - 1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &stack[i]);
174              symbols[i - 1] = CFStringCreateWithCString(kCFAllocatorDefault, sym[i], kCFStringEncodingUTF8);
175          }
176  
177          free(sym);
178          callStackSymbols = [[NSArray alloc] initWithObjects: (id *) symbols
179                                                        count: count - 1];
180          NSArray *callStackReturnAddresses = [[NSArray alloc] initWithObjects: (id *) returnAddresses
181                                                                         count: count - 1];
182          _reserved[@"callStackSymbols"] = callStackSymbols;
183          _reserved[@"callStackReturnAddresses"] = callStackReturnAddresses;
184  
185          for (int i = 1; i < count; i++) {
186              CFRelease(returnAddresses[i - 1]);
187              CFRelease(symbols[i - 1]);
188          }
189  
190          [callStackSymbols release];
191          [callStackReturnAddresses release];
192      }
193  
194      return callStackSymbols != nil;
195  }
196  
197  - (NSArray *) callStackReturnAddresses {
198      return _reserved[@"callStackReturnAddresses"];
199  }
200  
201  - (NSArray *) callStackSymbols {
202      return _reserved[@"callStackSymbols"];
203  }
204  
205  - (NSString *) description {
206      if (_reason != nil) {
207          return _reason;
208      }
209      return _name;
210  }
211  
212  @end