1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 // Undo the damage that exception_defines.h does.
8 #undef try
9 #undef catch
10 
11 #ifndef nsObjCExceptions_h_
12 #define nsObjCExceptions_h_
13 
14 #import <Foundation/Foundation.h>
15 
16 #ifdef DEBUG
17 #import <ExceptionHandling/NSExceptionHandler.h>
18 #endif
19 
20 #if defined(__cplusplus)
21 #include "nsICrashReporter.h"
22 #include "nsCOMPtr.h"
23 #include "nsServiceManagerUtils.h"
24 #endif
25 
26 #include <unistd.h>
27 #include <signal.h>
28 #include "nsError.h"
29 
30 // Undo the damage that exception_defines.h does.
31 #undef try
32 #undef catch
33 
34 /* NOTE: Macros that claim to abort no longer abort, see bug 486574.
35  * If you actually want to log and abort, call "nsObjCExceptionLogAbort"
36  * from an exception handler. At some point we will fix this by replacing
37  * all macros in the tree with appropriately-named macros.
38  */
39 
40 // See Mozilla bug 163260.
41 // This file can only be included in an Objective-C context.
42 
nsObjCExceptionLog(NSException * aException)43 __attribute__((unused)) static void nsObjCExceptionLog(
44     NSException* aException) {
45   NSLog(@"Mozilla has caught an Obj-C exception [%@: %@]", [aException name],
46         [aException reason]);
47 
48 #if defined(__cplusplus)
49   // Attach exception info to the crash report.
50   nsCOMPtr<nsICrashReporter> crashReporter =
51       do_GetService("@mozilla.org/toolkit/crash-reporter;1");
52   if (crashReporter) {
53     crashReporter->AppendObjCExceptionInfoToAppNotes(
54         static_cast<void*>(aException));
55   }
56 #endif
57 
58 #ifdef DEBUG
59   @try {
60     // Try to get stack information out of the exception. 10.5 returns the stack
61     // info with the callStackReturnAddresses selector.
62     NSArray* stackTrace = nil;
63     if ([aException respondsToSelector:@selector(callStackReturnAddresses)]) {
64       NSArray* addresses = (NSArray*)[aException
65           performSelector:@selector(callStackReturnAddresses)];
66       if ([addresses count]) {
67         stackTrace = addresses;
68       }
69     }
70 
71     // 10.4 doesn't respond to callStackReturnAddresses so we'll try to pull the
72     // stack info out of the userInfo. It might not be there, sadly :(
73     if (!stackTrace) {
74       stackTrace = [[aException userInfo] objectForKey:NSStackTraceKey];
75     }
76 
77     if (stackTrace) {
78       // The command line should look like this:
79       //   /usr/bin/atos -p <pid> -printHeader <stack frame addresses>
80       NSMutableArray* args =
81           [NSMutableArray arrayWithCapacity:[stackTrace count] + 3];
82 
83       [args addObject:@"-p"];
84       int pid = [[NSProcessInfo processInfo] processIdentifier];
85       [args addObject:[NSString stringWithFormat:@"%d", pid]];
86 
87       [args addObject:@"-printHeader"];
88 
89       unsigned int stackCount = [stackTrace count];
90       unsigned int stackIndex = 0;
91       for (; stackIndex < stackCount; stackIndex++) {
92         unsigned long address =
93             [[stackTrace objectAtIndex:stackIndex] unsignedLongValue];
94         [args addObject:[NSString stringWithFormat:@"0x%lx", address]];
95       }
96 
97       NSPipe* outPipe = [NSPipe pipe];
98 
99       NSTask* task = [[NSTask alloc] init];
100       [task setLaunchPath:@"/usr/bin/atos"];
101       [task setArguments:args];
102       [task setStandardOutput:outPipe];
103       [task setStandardError:outPipe];
104 
105       NSLog(@"Generating stack trace for Obj-C exception...");
106 
107       // This will throw an exception if the atos tool cannot be found, and in
108       // that case we'll just hit our @catch block below.
109       [task launch];
110 
111       [task waitUntilExit];
112       [task release];
113 
114       NSData* outData = [[outPipe fileHandleForReading] readDataToEndOfFile];
115       NSString* outString =
116           [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
117 
118       NSLog(@"Stack trace:\n%@", outString);
119 
120       [outString release];
121     } else {
122       NSLog(@"<No stack information available for Obj-C exception>");
123     }
124   } @catch (NSException* exn) {
125     NSLog(@"Failed to generate stack trace for Obj-C exception [%@: %@]",
126           [exn name], [exn reason]);
127   }
128 #endif
129 }
130 
nsObjCExceptionAbort()131 __attribute__((unused)) static void nsObjCExceptionAbort() {
132   // We need to raise a mach-o signal here, the Mozilla crash reporter on
133   // Mac OS X does not respond to POSIX signals. Raising mach-o signals directly
134   // is tricky so we do it by just derefing a null pointer.
135   int* foo = nullptr;
136   *foo = 1;
137 }
138 
nsObjCExceptionLogAbort(NSException * aException)139 __attribute__((unused)) static void nsObjCExceptionLogAbort(
140     NSException* aException) {
141   nsObjCExceptionLog(aException);
142   nsObjCExceptionAbort();
143 }
144 
145 #define NS_OBJC_TRY(_e, _fail)    \
146   @try {                          \
147     _e;                           \
148   } @catch (NSException * _exn) { \
149     nsObjCExceptionLog(_exn);     \
150     _fail;                        \
151   }
152 
153 #define NS_OBJC_TRY_EXPR(_e, _fail) \
154   ({                                \
155     typeof(_e) _tmp;                \
156     @try {                          \
157       _tmp = (_e);                  \
158     } @catch (NSException * _exn) { \
159       nsObjCExceptionLog(_exn);     \
160       _fail;                        \
161     }                               \
162     _tmp;                           \
163   })
164 
165 #define NS_OBJC_TRY_EXPR_NULL(_e) NS_OBJC_TRY_EXPR(_e, 0)
166 
167 #define NS_OBJC_TRY_IGNORE(_e) NS_OBJC_TRY(_e, )
168 
169   // To reduce code size the abort versions do not reuse above macros. This
170   // allows catch blocks to only contain one call.
171 
172 #define NS_OBJC_TRY_ABORT(_e)     \
173   @try {                          \
174     _e;                           \
175   } @catch (NSException * _exn) { \
176     nsObjCExceptionLog(_exn);     \
177   }
178 
179 #define NS_OBJC_TRY_EXPR_ABORT(_e)  \
180   ({                                \
181     typeof(_e) _tmp;                \
182     @try {                          \
183       _tmp = (_e);                  \
184     } @catch (NSException * _exn) { \
185       nsObjCExceptionLog(_exn);     \
186     }                               \
187     _tmp;                           \
188   })
189 
190 // For wrapping blocks of Obj-C calls. Does not actually terminate.
191 #define NS_OBJC_BEGIN_TRY_ABORT_BLOCK @try {
192 #define NS_OBJC_END_TRY_ABORT_BLOCK \
193   }                                 \
194   @catch (NSException * _exn) {     \
195     nsObjCExceptionLog(_exn);       \
196   }
197 
198   // Same as above ABORT_BLOCK but returns a value after the try/catch block to
199   // suppress compiler warnings. This allows us to avoid having to refactor code
200   // to get scoping right when wrapping an entire method.
201 
202 #define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL @try {
203 #define NS_OBJC_END_TRY_ABORT_BLOCK_NIL \
204   }                                     \
205   @catch (NSException * _exn) {         \
206     nsObjCExceptionLog(_exn);           \
207   }                                     \
208   return nil;
209 
210 #define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL @try {
211 #define NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL \
212   }                                        \
213   @catch (NSException * _exn) {            \
214     nsObjCExceptionLog(_exn);              \
215   }                                        \
216   return nullptr;
217 
218 #define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT @try {
219 #define NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT \
220   }                                          \
221   @catch (NSException * _exn) {              \
222     nsObjCExceptionLog(_exn);                \
223   }                                          \
224   return NS_ERROR_FAILURE;
225 
226 #define NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN @try {
227 #define NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(_rv) \
228   }                                             \
229   @catch (NSException * _exn) {                 \
230     nsObjCExceptionLog(_exn);                   \
231   }                                             \
232   return _rv;
233 
234 #endif  // nsObjCExceptions_h_
235