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