ExceptionUtils.m
author Jens Alfke <jens@mooseyard.com>
Sat May 31 11:26:17 2008 -0700 (2008-05-31)
changeset 12 66b870428f85
parent 10 82a37ccf6b8c
child 16 ce9c83f7ec14
permissions -rw-r--r--
* Worked around compiler warnings in Test.h when building for iPhone.
* Made Mercurial ignore the documentation files.
     1 //
     2 //  ExceptionUtils.m
     3 //  MYUtilities
     4 //
     5 //  Created by Jens Alfke on 1/5/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //  See BSD license at bottom of file.
     8 //
     9 
    10 #import "ExceptionUtils.h"
    11 
    12 #import "Logging.h"
    13 #import "Test.h"
    14 
    15 #include <sys/sysctl.h>
    16 #include <unistd.h>
    17 
    18 
    19 #ifndef Warn
    20 #define Warn NSLog
    21 #endif
    22 
    23 
    24 static void (*sExceptionReporter)(NSException*);
    25 
    26 void MYSetExceptionReporter( void (*reporter)(NSException*) )
    27 {
    28     sExceptionReporter = reporter;
    29 }
    30 
    31 void MYReportException( NSException *x, NSString *where, ... )
    32 {
    33     va_list args;
    34     va_start(args,where);
    35     where = [[NSString alloc] initWithFormat: where arguments: args];
    36     va_end(args);
    37     if( sExceptionReporter ) {
    38         Warn(@"Exception caught in %@:\n\t%@",where,x);
    39         sExceptionReporter(x);
    40     }else
    41         Warn(@"Exception caught in %@:\n\t%@\n%@",where,x,x.my_callStack);
    42     [where release];
    43 }
    44 
    45 
    46 @implementation NSException (MooseyardUtil)
    47 
    48 
    49 - (NSArray*) my_callStackReturnAddresses
    50 {
    51     // On 10.5 or later, can get the backtrace:
    52     if( [self respondsToSelector: @selector(callStackReturnAddresses)] )
    53         return [self valueForKey: @"callStackReturnAddresses"];
    54     else
    55         return nil;
    56 }
    57 
    58 - (NSArray*) my_callStackReturnAddressesSkipping: (unsigned)skip limit: (unsigned)limit
    59 {
    60     NSArray *addresses = [self my_callStackReturnAddresses];
    61     if( addresses ) {
    62         unsigned n = [addresses count];
    63         skip = MIN(skip,n);
    64         limit = MIN(limit,n-skip);
    65         addresses = [addresses subarrayWithRange: NSMakeRange(skip,limit)];
    66     }
    67     return addresses;
    68 }
    69 
    70 
    71 - (NSString*) my_callStack
    72 {
    73     NSArray *addresses = [self my_callStackReturnAddressesSkipping: 2 limit: 15];
    74     if (!addresses)
    75         return nil;
    76     
    77     // We pipe the hex return addresses through the 'atos' tool to get symbolic names:
    78     // Adapted from <http://paste.lisp.org/display/47196>:
    79     NSMutableString *cmd = [NSMutableString stringWithFormat: @"/usr/bin/atos -p %d", getpid()];
    80     NSValue *addr;
    81     foreach(addr,addresses) {
    82         [cmd appendFormat: @" %p", [addr pointerValue]];
    83     }
    84     FILE *file = popen( [cmd UTF8String], "r" );
    85     if( ! file )
    86         return nil;
    87     
    88     NSMutableData *output = [NSMutableData data];
    89     char buffer[512];
    90     size_t length;
    91     while ((length = fread( buffer, 1, sizeof( buffer ), file ) ))
    92         [output appendBytes: buffer length: length];
    93     pclose( file );
    94     NSString *outStr = [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease];
    95     
    96     NSMutableString *result = [NSMutableString string];
    97     NSString *line;
    98     foreach( line, [outStr componentsSeparatedByString: @"\n"] ) {
    99         // Skip  frames that are part of the exception/assertion handling itself:
   100         if( [line hasPrefix: @"-[NSAssertionHandler"] || [line hasPrefix: @"+[NSException"] 
   101                 || [line hasPrefix: @"-[NSException"] || [line hasPrefix: @"_AssertFailed"] )
   102             continue;
   103         if( result.length )
   104             [result appendString: @"\n"];
   105         [result appendString: @"\t"];
   106         [result appendString: line];
   107         // Don't show the "__start" frame below "main":
   108         if( [line hasPrefix: @"main "] )
   109             break;
   110     }
   111     return result;
   112 }
   113 
   114 @end
   115 
   116 
   117 
   118 
   119 #ifdef NSAppKitVersionNumber10_4 // only enable this in a project that uses AppKit
   120 
   121 @implementation MYExceptionReportingApplication
   122 
   123 
   124 static void report( NSException *x ) {
   125     [NSApp reportException: x];
   126 }
   127 
   128 - (id) init
   129 {
   130     self = [super init];
   131     if (self != nil) {
   132         MYSetExceptionReporter(&report);
   133     }
   134     return self;
   135 }
   136 
   137 
   138 - (void)reportException:(NSException *)x
   139 {
   140     [super reportException: x];
   141     [self performSelector: @selector(_showExceptionAlert:) withObject: x afterDelay: 0.0];
   142     MYSetExceptionReporter(NULL);     // ignore further exceptions till alert is dismissed
   143 }
   144 
   145 - (void) _showExceptionAlert: (NSException*)x
   146 {
   147     NSString *stack = [x my_callStack] ?:@"";
   148     int r = NSRunCriticalAlertPanel( @"Internal Error!",
   149                             [NSString stringWithFormat: @"Uncaught exception: %@\n%@\n\n%@\n\n"
   150                              "Please report this bug (you can copy & paste the text).",
   151                              [x name], [x reason], stack],
   152                             @"Continue",@"Quit",nil);
   153     if( r == NSAlertAlternateReturn )
   154         exit(1);
   155     MYSetExceptionReporter(&report);
   156 }
   157 
   158 @end
   159 
   160 #endif
   161 
   162 
   163 
   164 BOOL IsGDBAttached( void )
   165 {
   166     // From: <http://lists.apple.com/archives/Xcode-users/2004/Feb/msg00241.html>
   167     int mib[4];
   168     size_t bufSize = 0;
   169     int local_error = 0;
   170     struct kinfo_proc kp;
   171     
   172     mib[0] = CTL_KERN;
   173     mib[1] = KERN_PROC;
   174     mib[2] = KERN_PROC_PID;
   175     mib[3] = getpid();
   176     
   177     bufSize = sizeof (kp);
   178     if ((local_error = sysctl(mib, 4, &kp, &bufSize, NULL, 0)) < 0) {
   179         Warn(@"Failure calling sysctl");
   180         return NO;
   181     }
   182     return (kp.kp_proc.p_flag & P_TRACED) != 0;
   183 }
   184 
   185 
   186 
   187 /*
   188  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   189  
   190  Redistribution and use in source and binary forms, with or without modification, are permitted
   191  provided that the following conditions are met:
   192  
   193  * Redistributions of source code must retain the above copyright notice, this list of conditions
   194  and the following disclaimer.
   195  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   196  and the following disclaimer in the documentation and/or other materials provided with the
   197  distribution.
   198  
   199  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   200  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   201  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   202  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   203  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   204   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   205  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   206  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   207  */