1 |
efrain |
1 |
// Copyright 2019 Google
|
|
|
2 |
//
|
|
|
3 |
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
4 |
// you may not use this file except in compliance with the License.
|
|
|
5 |
// You may obtain a copy of the License at
|
|
|
6 |
//
|
|
|
7 |
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
8 |
//
|
|
|
9 |
// Unless required by applicable law or agreed to in writing, software
|
|
|
10 |
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
11 |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
12 |
// See the License for the specific language governing permissions and
|
|
|
13 |
// limitations under the License.
|
|
|
14 |
|
|
|
15 |
#import <Foundation/Foundation.h>
|
|
|
16 |
|
|
|
17 |
#include "Crashlytics/Crashlytics/Handlers/FIRCLSException.h"
|
|
|
18 |
|
|
|
19 |
#import "Crashlytics/Crashlytics/Private/FIRExceptionModel_Private.h"
|
|
|
20 |
#import "Crashlytics/Crashlytics/Private/FIRStackFrame_Private.h"
|
|
|
21 |
|
|
|
22 |
#include "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
|
|
|
23 |
#include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
|
|
|
24 |
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
|
|
|
25 |
#include "Crashlytics/Crashlytics/Components/FIRCLSProcess.h"
|
|
|
26 |
#import "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
|
|
|
27 |
|
|
|
28 |
#include "Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h"
|
|
|
29 |
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
|
|
|
30 |
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
|
|
|
31 |
#import "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
|
|
|
32 |
|
|
|
33 |
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h"
|
|
|
34 |
#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
|
|
|
35 |
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
|
|
|
36 |
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
|
|
|
37 |
#include "Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.h"
|
|
|
38 |
|
|
|
39 |
// C++/Objective-C exception handling
|
|
|
40 |
#include <cxxabi.h>
|
|
|
41 |
#include <exception>
|
|
|
42 |
#include <string>
|
|
|
43 |
#include <typeinfo>
|
|
|
44 |
|
|
|
45 |
#if !TARGET_OS_IPHONE
|
|
|
46 |
#import <AppKit/NSApplication.h>
|
|
|
47 |
#import <objc/runtime.h>
|
|
|
48 |
#endif
|
|
|
49 |
|
|
|
50 |
#pragma mark Prototypes
|
|
|
51 |
static void FIRCLSTerminateHandler(void);
|
|
|
52 |
#if !TARGET_OS_IPHONE
|
|
|
53 |
void FIRCLSNSApplicationReportException(id self, SEL cmd, NSException *exception);
|
|
|
54 |
|
|
|
55 |
typedef void (*NSApplicationReportExceptionFunction)(id, SEL, NSException *);
|
|
|
56 |
|
|
|
57 |
static BOOL FIRCLSIsNSApplicationCrashOnExceptionsEnabled(void);
|
|
|
58 |
static NSApplicationReportExceptionFunction FIRCLSOriginalNSExceptionReportExceptionFunction(void);
|
|
|
59 |
static Method FIRCLSGetNSApplicationReportExceptionMethod(void);
|
|
|
60 |
|
|
|
61 |
#endif
|
|
|
62 |
|
|
|
63 |
#pragma mark - API
|
|
|
64 |
void FIRCLSExceptionInitialize(FIRCLSExceptionReadOnlyContext *roContext,
|
|
|
65 |
FIRCLSExceptionWritableContext *rwContext) {
|
|
|
66 |
if (!FIRCLSUnlinkIfExists(roContext->path)) {
|
|
|
67 |
FIRCLSSDKLog("Unable to reset the exception file %s\n", strerror(errno));
|
|
|
68 |
}
|
|
|
69 |
|
|
|
70 |
roContext->originalTerminateHandler = std::set_terminate(FIRCLSTerminateHandler);
|
|
|
71 |
|
|
|
72 |
#if !TARGET_OS_IPHONE
|
|
|
73 |
// If FIRCLSApplicationSharedInstance is null, we don't need this
|
|
|
74 |
if (FIRCLSIsNSApplicationCrashOnExceptionsEnabled() && FIRCLSApplicationSharedInstance()) {
|
|
|
75 |
Method m = FIRCLSGetNSApplicationReportExceptionMethod();
|
|
|
76 |
|
|
|
77 |
roContext->originalNSApplicationReportException =
|
|
|
78 |
(void *)method_setImplementation(m, (IMP)FIRCLSNSApplicationReportException);
|
|
|
79 |
}
|
|
|
80 |
#endif
|
|
|
81 |
|
|
|
82 |
rwContext->customExceptionCount = 0;
|
|
|
83 |
}
|
|
|
84 |
|
|
|
85 |
void FIRCLSExceptionRecordModel(FIRExceptionModel *exceptionModel) {
|
|
|
86 |
const char *name = [[exceptionModel.name copy] UTF8String];
|
|
|
87 |
const char *reason = [[exceptionModel.reason copy] UTF8String] ?: "";
|
|
|
88 |
|
|
|
89 |
FIRCLSExceptionRecord(FIRCLSExceptionTypeCustom, name, reason, [exceptionModel.stackTrace copy]);
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
NSString *FIRCLSExceptionRecordOnDemandModel(FIRExceptionModel *exceptionModel,
|
|
|
93 |
int previousRecordedOnDemandExceptions,
|
|
|
94 |
int previousDroppedOnDemandExceptions) {
|
|
|
95 |
const char *name = [[exceptionModel.name copy] UTF8String];
|
|
|
96 |
const char *reason = [[exceptionModel.reason copy] UTF8String] ?: "";
|
|
|
97 |
|
|
|
98 |
return FIRCLSExceptionRecordOnDemand(FIRCLSExceptionTypeCustom, name, reason,
|
|
|
99 |
[exceptionModel.stackTrace copy], exceptionModel.isFatal,
|
|
|
100 |
previousRecordedOnDemandExceptions,
|
|
|
101 |
previousDroppedOnDemandExceptions);
|
|
|
102 |
}
|
|
|
103 |
|
|
|
104 |
void FIRCLSExceptionRecordNSException(NSException *exception) {
|
|
|
105 |
FIRCLSSDKLog("Recording an NSException\n");
|
|
|
106 |
|
|
|
107 |
NSArray *returnAddresses = [exception callStackReturnAddresses];
|
|
|
108 |
|
|
|
109 |
NSString *name = [exception name];
|
|
|
110 |
NSString *reason = [exception reason] ?: @"";
|
|
|
111 |
|
|
|
112 |
// It's tempting to try to make use of callStackSymbols here. But, the output
|
|
|
113 |
// of that function is not intended to be machine-readible. We could parse it,
|
|
|
114 |
// but that isn't really worthwhile, considering that address-based symbolication
|
|
|
115 |
// needs to work anyways.
|
|
|
116 |
|
|
|
117 |
// package our frames up into the appropriate format
|
|
|
118 |
NSMutableArray *frames = [NSMutableArray new];
|
|
|
119 |
|
|
|
120 |
for (NSNumber *address in returnAddresses) {
|
|
|
121 |
[frames addObject:[FIRStackFrame stackFrameWithAddress:[address unsignedIntegerValue]]];
|
|
|
122 |
}
|
|
|
123 |
|
|
|
124 |
FIRCLSExceptionRecord(FIRCLSExceptionTypeObjectiveC, [name UTF8String], [reason UTF8String],
|
|
|
125 |
frames);
|
|
|
126 |
}
|
|
|
127 |
|
|
|
128 |
static void FIRCLSExceptionRecordFrame(FIRCLSFile *file, FIRStackFrame *frame) {
|
|
|
129 |
FIRCLSFileWriteHashStart(file);
|
|
|
130 |
|
|
|
131 |
FIRCLSFileWriteHashEntryUint64(file, "pc", [frame address]);
|
|
|
132 |
|
|
|
133 |
NSString *string = [frame symbol];
|
|
|
134 |
if (string) {
|
|
|
135 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "symbol", [string UTF8String]);
|
|
|
136 |
}
|
|
|
137 |
|
|
|
138 |
FIRCLSFileWriteHashEntryUint64(file, "offset", [frame offset]);
|
|
|
139 |
|
|
|
140 |
string = [frame library];
|
|
|
141 |
if (string) {
|
|
|
142 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "library", [string UTF8String]);
|
|
|
143 |
}
|
|
|
144 |
|
|
|
145 |
string = [frame fileName];
|
|
|
146 |
if (string) {
|
|
|
147 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "file", [string UTF8String]);
|
|
|
148 |
}
|
|
|
149 |
|
|
|
150 |
FIRCLSFileWriteHashEntryUint64(file, "line", [frame lineNumber]);
|
|
|
151 |
|
|
|
152 |
FIRCLSFileWriteHashEnd(file);
|
|
|
153 |
}
|
|
|
154 |
|
|
|
155 |
static bool FIRCLSExceptionIsNative(FIRCLSExceptionType type) {
|
|
|
156 |
return type == FIRCLSExceptionTypeObjectiveC || type == FIRCLSExceptionTypeCpp;
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
static const char *FIRCLSExceptionNameForType(FIRCLSExceptionType type) {
|
|
|
160 |
switch (type) {
|
|
|
161 |
case FIRCLSExceptionTypeObjectiveC:
|
|
|
162 |
return "objective-c";
|
|
|
163 |
case FIRCLSExceptionTypeCpp:
|
|
|
164 |
return "c++";
|
|
|
165 |
case FIRCLSExceptionTypeCustom:
|
|
|
166 |
return "custom";
|
|
|
167 |
default:
|
|
|
168 |
break;
|
|
|
169 |
}
|
|
|
170 |
|
|
|
171 |
return "unknown";
|
|
|
172 |
}
|
|
|
173 |
|
|
|
174 |
void FIRCLSExceptionWrite(FIRCLSFile *file,
|
|
|
175 |
FIRCLSExceptionType type,
|
|
|
176 |
const char *name,
|
|
|
177 |
const char *reason,
|
|
|
178 |
NSArray<FIRStackFrame *> *frames) {
|
|
|
179 |
FIRCLSFileWriteSectionStart(file, "exception");
|
|
|
180 |
|
|
|
181 |
FIRCLSFileWriteHashStart(file);
|
|
|
182 |
|
|
|
183 |
FIRCLSFileWriteHashEntryString(file, "type", FIRCLSExceptionNameForType(type));
|
|
|
184 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "name", name);
|
|
|
185 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "reason", reason);
|
|
|
186 |
FIRCLSFileWriteHashEntryUint64(file, "time", time(NULL));
|
|
|
187 |
|
|
|
188 |
if ([frames count]) {
|
|
|
189 |
FIRCLSFileWriteHashKey(file, "frames");
|
|
|
190 |
FIRCLSFileWriteArrayStart(file);
|
|
|
191 |
|
|
|
192 |
for (FIRStackFrame *frame in frames) {
|
|
|
193 |
FIRCLSExceptionRecordFrame(file, frame);
|
|
|
194 |
}
|
|
|
195 |
|
|
|
196 |
FIRCLSFileWriteArrayEnd(file);
|
|
|
197 |
}
|
|
|
198 |
|
|
|
199 |
FIRCLSFileWriteHashEnd(file);
|
|
|
200 |
|
|
|
201 |
FIRCLSFileWriteSectionEnd(file);
|
|
|
202 |
}
|
|
|
203 |
|
|
|
204 |
void FIRCLSExceptionRecord(FIRCLSExceptionType type,
|
|
|
205 |
const char *name,
|
|
|
206 |
const char *reason,
|
|
|
207 |
NSArray<FIRStackFrame *> *frames) {
|
|
|
208 |
if (!FIRCLSContextIsInitialized()) {
|
|
|
209 |
return;
|
|
|
210 |
}
|
|
|
211 |
|
|
|
212 |
bool native = FIRCLSExceptionIsNative(type);
|
|
|
213 |
|
|
|
214 |
FIRCLSSDKLog("Recording an exception structure (%d)\n", native);
|
|
|
215 |
|
|
|
216 |
// exceptions can happen on multiple threads at the same time
|
|
|
217 |
if (native) {
|
|
|
218 |
dispatch_sync(_firclsExceptionQueue, ^{
|
|
|
219 |
const char *path = _firclsContext.readonly->exception.path;
|
|
|
220 |
FIRCLSFile file;
|
|
|
221 |
|
|
|
222 |
if (!FIRCLSFileInitWithPath(&file, path, false)) {
|
|
|
223 |
FIRCLSSDKLog("Unable to open exception file\n");
|
|
|
224 |
return;
|
|
|
225 |
}
|
|
|
226 |
|
|
|
227 |
FIRCLSExceptionWrite(&file, type, name, reason, frames);
|
|
|
228 |
|
|
|
229 |
// We only want to do this work if we have the expectation that we'll actually crash
|
|
|
230 |
FIRCLSHandler(&file, mach_thread_self(), NULL);
|
|
|
231 |
|
|
|
232 |
FIRCLSFileClose(&file);
|
|
|
233 |
});
|
|
|
234 |
} else {
|
|
|
235 |
FIRCLSUserLoggingWriteAndCheckABFiles(
|
|
|
236 |
&_firclsContext.readonly->logging.customExceptionStorage,
|
|
|
237 |
&_firclsContext.writable->logging.activeCustomExceptionPath, ^(FIRCLSFile *file) {
|
|
|
238 |
FIRCLSExceptionWrite(file, type, name, reason, frames);
|
|
|
239 |
});
|
|
|
240 |
}
|
|
|
241 |
|
|
|
242 |
FIRCLSSDKLog("Finished recording an exception structure\n");
|
|
|
243 |
}
|
|
|
244 |
|
|
|
245 |
// Prepares a new active report for on-demand delivery and returns the path to the report.
|
|
|
246 |
// Should only be used for platforms in which exceptions do not crash the app (flutter, Unity, etc).
|
|
|
247 |
NSString *FIRCLSExceptionRecordOnDemand(FIRCLSExceptionType type,
|
|
|
248 |
const char *name,
|
|
|
249 |
const char *reason,
|
|
|
250 |
NSArray<FIRStackFrame *> *frames,
|
|
|
251 |
BOOL fatal,
|
|
|
252 |
int previousRecordedOnDemandExceptions,
|
|
|
253 |
int previousDroppedOnDemandExceptions) {
|
|
|
254 |
if (!FIRCLSContextIsInitialized()) {
|
|
|
255 |
return nil;
|
|
|
256 |
}
|
|
|
257 |
|
|
|
258 |
FIRCLSSDKLog("Recording an exception structure on demand\n");
|
|
|
259 |
|
|
|
260 |
FIRCLSFileManager *fileManager = [[FIRCLSFileManager alloc] init];
|
|
|
261 |
|
|
|
262 |
// Create paths for new report.
|
|
|
263 |
NSString *currentReportPath =
|
|
|
264 |
[NSString stringWithUTF8String:_firclsContext.readonly->initialReportPath];
|
|
|
265 |
NSString *newReportID = [[[FIRCLSExecutionIdentifierModel alloc] init] executionID];
|
|
|
266 |
NSString *newReportPath = [fileManager.activePath stringByAppendingPathComponent:newReportID];
|
|
|
267 |
NSString *customFatalIndicatorFilePath =
|
|
|
268 |
[newReportPath stringByAppendingPathComponent:FIRCLSCustomFatalIndicatorFile];
|
|
|
269 |
NSString *newKVPath =
|
|
|
270 |
[newReportPath stringByAppendingPathComponent:FIRCLSReportInternalIncrementalKVFile];
|
|
|
271 |
|
|
|
272 |
// Create new report and copy into it the current state of custom keys and log and the sdk.log,
|
|
|
273 |
// binary_images.clsrecord, and metadata.clsrecord files.
|
|
|
274 |
NSError *error = nil;
|
|
|
275 |
BOOL copied = [fileManager.underlyingFileManager copyItemAtPath:currentReportPath
|
|
|
276 |
toPath:newReportPath
|
|
|
277 |
error:&error];
|
|
|
278 |
if (error || !copied) {
|
|
|
279 |
FIRCLSSDKLog("Unable to create a new report to record on-demand exeption.");
|
|
|
280 |
return nil;
|
|
|
281 |
}
|
|
|
282 |
|
|
|
283 |
// Once the report is copied, remove non-fatal events from current report.
|
|
|
284 |
if ([fileManager
|
|
|
285 |
fileExistsAtPath:[NSString stringWithUTF8String:_firclsContext.readonly->logging
|
|
|
286 |
.customExceptionStorage.aPath]]) {
|
|
|
287 |
[fileManager
|
|
|
288 |
removeItemAtPath:[NSString stringWithUTF8String:_firclsContext.readonly->logging
|
|
|
289 |
.customExceptionStorage.aPath]];
|
|
|
290 |
}
|
|
|
291 |
if ([fileManager
|
|
|
292 |
fileExistsAtPath:[NSString stringWithUTF8String:_firclsContext.readonly->logging
|
|
|
293 |
.customExceptionStorage.bPath]]) {
|
|
|
294 |
[fileManager
|
|
|
295 |
removeItemAtPath:[NSString stringWithUTF8String:_firclsContext.readonly->logging
|
|
|
296 |
.customExceptionStorage.bPath]];
|
|
|
297 |
}
|
|
|
298 |
*_firclsContext.readonly->logging.customExceptionStorage.entryCount = 0;
|
|
|
299 |
_firclsContext.writable->exception.customExceptionCount = 0;
|
|
|
300 |
|
|
|
301 |
// Record how many on-demand exceptions occurred before this one as well as how many were dropped.
|
|
|
302 |
FIRCLSFile kvFile;
|
|
|
303 |
if (!FIRCLSFileInitWithPath(&kvFile, [newKVPath UTF8String], true)) {
|
|
|
304 |
FIRCLSSDKLogError("Unable to open k-v file\n");
|
|
|
305 |
return nil;
|
|
|
306 |
}
|
|
|
307 |
FIRCLSFileWriteSectionStart(&kvFile, "kv");
|
|
|
308 |
FIRCLSFileWriteHashStart(&kvFile);
|
|
|
309 |
FIRCLSFileWriteHashEntryHexEncodedString(&kvFile, "key",
|
|
|
310 |
[FIRCLSOnDemandRecordedExceptionsKey UTF8String]);
|
|
|
311 |
FIRCLSFileWriteHashEntryHexEncodedString(
|
|
|
312 |
&kvFile, "value",
|
|
|
313 |
[[[NSNumber numberWithInt:previousRecordedOnDemandExceptions] stringValue] UTF8String]);
|
|
|
314 |
FIRCLSFileWriteHashEnd(&kvFile);
|
|
|
315 |
FIRCLSFileWriteSectionEnd(&kvFile);
|
|
|
316 |
FIRCLSFileWriteSectionStart(&kvFile, "kv");
|
|
|
317 |
FIRCLSFileWriteHashStart(&kvFile);
|
|
|
318 |
FIRCLSFileWriteHashEntryHexEncodedString(&kvFile, "key",
|
|
|
319 |
[FIRCLSOnDemandDroppedExceptionsKey UTF8String]);
|
|
|
320 |
FIRCLSFileWriteHashEntryHexEncodedString(
|
|
|
321 |
&kvFile, "value",
|
|
|
322 |
[[[NSNumber numberWithInt:previousDroppedOnDemandExceptions] stringValue] UTF8String]);
|
|
|
323 |
FIRCLSFileWriteHashEnd(&kvFile);
|
|
|
324 |
FIRCLSFileWriteSectionEnd(&kvFile);
|
|
|
325 |
FIRCLSFileClose(&kvFile);
|
|
|
326 |
|
|
|
327 |
// If the event was fatal, write out an empty file to indicate that the report contains a fatal
|
|
|
328 |
// event. This is used to report events to Analytics for CFU calculations.
|
|
|
329 |
if (fatal && ![fileManager createFileAtPath:customFatalIndicatorFilePath
|
|
|
330 |
contents:nil
|
|
|
331 |
attributes:nil]) {
|
|
|
332 |
FIRCLSSDKLog("Unable to create custom exception file. On demand exception will not be logged "
|
|
|
333 |
"with analytics.");
|
|
|
334 |
}
|
|
|
335 |
|
|
|
336 |
// Write out the exception in the new report.
|
|
|
337 |
const char *newActiveCustomExceptionPath =
|
|
|
338 |
fatal ? [[newReportPath stringByAppendingPathComponent:FIRCLSReportExceptionFile] UTF8String]
|
|
|
339 |
: [[newReportPath stringByAppendingPathComponent:FIRCLSReportCustomExceptionAFile]
|
|
|
340 |
UTF8String];
|
|
|
341 |
FIRCLSFile file;
|
|
|
342 |
if (!FIRCLSFileInitWithPath(&file, newActiveCustomExceptionPath, true)) {
|
|
|
343 |
FIRCLSSDKLog("Unable to open log file for on demand custom exception\n");
|
|
|
344 |
return nil;
|
|
|
345 |
}
|
|
|
346 |
FIRCLSExceptionWrite(&file, type, name, reason, frames);
|
|
|
347 |
FIRCLSHandler(&file, mach_thread_self(), NULL);
|
|
|
348 |
FIRCLSFileClose(&file);
|
|
|
349 |
|
|
|
350 |
// Return the path to the new report.
|
|
|
351 |
FIRCLSSDKLog("Finished recording on demand exception structure\n");
|
|
|
352 |
return newReportPath;
|
|
|
353 |
}
|
|
|
354 |
|
|
|
355 |
// Ignore this message here, because we know that this call will not leak.
|
|
|
356 |
#pragma clang diagnostic push
|
|
|
357 |
#pragma clang diagnostic ignored "-Winvalid-noreturn"
|
|
|
358 |
void FIRCLSExceptionRaiseTestObjCException(void) {
|
|
|
359 |
[NSException raise:@"CrashlyticsTestException"
|
|
|
360 |
format:@"This is an Objective-C exception used for testing."];
|
|
|
361 |
}
|
|
|
362 |
|
|
|
363 |
void FIRCLSExceptionRaiseTestCppException(void) {
|
|
|
364 |
throw "Crashlytics C++ Test Exception";
|
|
|
365 |
}
|
|
|
366 |
#pragma clang diagnostic pop
|
|
|
367 |
|
|
|
368 |
static const char *FIRCLSExceptionDemangle(const char *symbol) {
|
|
|
369 |
return [[FIRCLSDemangleOperation demangleCppSymbol:symbol] UTF8String];
|
|
|
370 |
}
|
|
|
371 |
|
|
|
372 |
static void FIRCLSCatchAndRecordActiveException(std::type_info *typeInfo) {
|
|
|
373 |
if (!FIRCLSIsValidPointer(typeInfo)) {
|
|
|
374 |
FIRCLSSDKLog("Error: invalid parameter\n");
|
|
|
375 |
return;
|
|
|
376 |
}
|
|
|
377 |
|
|
|
378 |
const char *name = typeInfo->name();
|
|
|
379 |
FIRCLSSDKLog("Recording exception of type '%s'\n", name);
|
|
|
380 |
|
|
|
381 |
// This is a funny technique to get the exception object. The inner @try
|
|
|
382 |
// has the ability to capture NSException-derived objects. It seems that
|
|
|
383 |
// c++ trys can do that in some cases, but I was warned by the WWDC labs
|
|
|
384 |
// that there are cases where that will not work (like for NSException subclasses).
|
|
|
385 |
try {
|
|
|
386 |
@try {
|
|
|
387 |
// This could potentially cause a call to std::terminate() if there is actually no active
|
|
|
388 |
// exception.
|
|
|
389 |
throw;
|
|
|
390 |
} @catch (NSException *exception) {
|
|
|
391 |
#if TARGET_OS_IPHONE
|
|
|
392 |
FIRCLSExceptionRecordNSException(exception);
|
|
|
393 |
#else
|
|
|
394 |
// There's no need to record this here, because we're going to get
|
|
|
395 |
// the value forward to us by AppKit
|
|
|
396 |
FIRCLSSDKLog("Skipping ObjC exception at this point\n");
|
|
|
397 |
#endif
|
|
|
398 |
}
|
|
|
399 |
} catch (const char *exc) {
|
|
|
400 |
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "const char *", exc, nil);
|
|
|
401 |
} catch (const std::string &exc) {
|
|
|
402 |
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::string", exc.c_str(), nil);
|
|
|
403 |
} catch (const std::exception &exc) {
|
|
|
404 |
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc.what(), nil);
|
|
|
405 |
} catch (const std::exception *exc) {
|
|
|
406 |
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc->what(), nil);
|
|
|
407 |
} catch (const std::bad_alloc &exc) {
|
|
|
408 |
// it is especially important to avoid demangling in this case, because the expetation at this
|
|
|
409 |
// point is that all allocations could fail
|
|
|
410 |
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::bad_alloc", exc.what(), nil);
|
|
|
411 |
} catch (...) {
|
|
|
412 |
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), "", nil);
|
|
|
413 |
}
|
|
|
414 |
}
|
|
|
415 |
|
|
|
416 |
#pragma mark - Handlers
|
|
|
417 |
static void FIRCLSTerminateHandler(void) {
|
|
|
418 |
FIRCLSSDKLog("C++ terminate handler invoked\n");
|
|
|
419 |
|
|
|
420 |
void (*handler)(void) = _firclsContext.readonly->exception.originalTerminateHandler;
|
|
|
421 |
if (handler == FIRCLSTerminateHandler) {
|
|
|
422 |
FIRCLSSDKLog("Error: original handler was set recursively\n");
|
|
|
423 |
handler = NULL;
|
|
|
424 |
}
|
|
|
425 |
|
|
|
426 |
// Restore pre-existing handler, if any. Do this early, so that
|
|
|
427 |
// if std::terminate is called while we are executing here, we do not recurse.
|
|
|
428 |
if (handler) {
|
|
|
429 |
FIRCLSSDKLog("restoring pre-existing handler\n");
|
|
|
430 |
|
|
|
431 |
// To prevent infinite recursion in this function, check that we aren't resetting the terminate
|
|
|
432 |
// handler to the same function again, which would be this function in the event that we can't
|
|
|
433 |
// actually change the handler during a terminate.
|
|
|
434 |
if (std::set_terminate(handler) == handler) {
|
|
|
435 |
FIRCLSSDKLog("handler has already been restored, aborting\n");
|
|
|
436 |
abort();
|
|
|
437 |
}
|
|
|
438 |
}
|
|
|
439 |
|
|
|
440 |
// we can use typeInfo to record the type of the exception,
|
|
|
441 |
// but we must use a catch to get the value
|
|
|
442 |
std::type_info *typeInfo = __cxxabiv1::__cxa_current_exception_type();
|
|
|
443 |
if (typeInfo) {
|
|
|
444 |
FIRCLSCatchAndRecordActiveException(typeInfo);
|
|
|
445 |
} else {
|
|
|
446 |
FIRCLSSDKLog("no active exception\n");
|
|
|
447 |
}
|
|
|
448 |
|
|
|
449 |
// only do this if there was a pre-existing handler
|
|
|
450 |
if (handler) {
|
|
|
451 |
FIRCLSSDKLog("invoking pre-existing handler\n");
|
|
|
452 |
handler();
|
|
|
453 |
}
|
|
|
454 |
|
|
|
455 |
FIRCLSSDKLog("aborting\n");
|
|
|
456 |
abort();
|
|
|
457 |
}
|
|
|
458 |
|
|
|
459 |
void FIRCLSExceptionCheckHandlers(void *delegate) {
|
|
|
460 |
#if !TARGET_OS_IPHONE
|
|
|
461 |
// Check this on OS X all the time, even if the debugger is attached. This is a common
|
|
|
462 |
// source of errors, so we want to be extra verbose in this case.
|
|
|
463 |
if (FIRCLSApplicationSharedInstance()) {
|
|
|
464 |
if (!FIRCLSIsNSApplicationCrashOnExceptionsEnabled()) {
|
|
|
465 |
FIRCLSWarningLog(@"Warning: NSApplicationCrashOnExceptions is not set. This will "
|
|
|
466 |
@"result in poor top-level uncaught exception reporting.");
|
|
|
467 |
}
|
|
|
468 |
}
|
|
|
469 |
#endif
|
|
|
470 |
|
|
|
471 |
if (_firclsContext.readonly->debuggerAttached) {
|
|
|
472 |
return;
|
|
|
473 |
}
|
|
|
474 |
|
|
|
475 |
void *ptr = NULL;
|
|
|
476 |
|
|
|
477 |
ptr = (void *)std::get_terminate();
|
|
|
478 |
if (ptr != FIRCLSTerminateHandler) {
|
|
|
479 |
FIRCLSLookupFunctionPointer(ptr, ^(const char *name, const char *lib) {
|
|
|
480 |
FIRCLSWarningLog(@"Warning: std::get_terminate is '%s' in '%s'", name, lib);
|
|
|
481 |
});
|
|
|
482 |
}
|
|
|
483 |
|
|
|
484 |
#if TARGET_OS_IPHONE
|
|
|
485 |
ptr = (void *)NSGetUncaughtExceptionHandler();
|
|
|
486 |
if (ptr) {
|
|
|
487 |
FIRCLSLookupFunctionPointer(ptr, ^(const char *name, const char *lib) {
|
|
|
488 |
FIRCLSWarningLog(@"Warning: NSUncaughtExceptionHandler is '%s' in '%s'", name, lib);
|
|
|
489 |
});
|
|
|
490 |
}
|
|
|
491 |
#else
|
|
|
492 |
if (FIRCLSApplicationSharedInstance() && FIRCLSIsNSApplicationCrashOnExceptionsEnabled()) {
|
|
|
493 |
// In this case, we *might* be able to intercept exceptions. But, verify we've still
|
|
|
494 |
// swizzled the method.
|
|
|
495 |
Method m = FIRCLSGetNSApplicationReportExceptionMethod();
|
|
|
496 |
|
|
|
497 |
if (method_getImplementation(m) != (IMP)FIRCLSNSApplicationReportException) {
|
|
|
498 |
FIRCLSWarningLog(
|
|
|
499 |
@"Warning: top-level NSApplication-reported exceptions cannot be intercepted");
|
|
|
500 |
}
|
|
|
501 |
}
|
|
|
502 |
#endif
|
|
|
503 |
}
|
|
|
504 |
|
|
|
505 |
#pragma mark - AppKit Handling
|
|
|
506 |
#if !TARGET_OS_IPHONE
|
|
|
507 |
static BOOL FIRCLSIsNSApplicationCrashOnExceptionsEnabled(void) {
|
|
|
508 |
return [[NSUserDefaults standardUserDefaults] boolForKey:@"NSApplicationCrashOnExceptions"];
|
|
|
509 |
}
|
|
|
510 |
|
|
|
511 |
static Method FIRCLSGetNSApplicationReportExceptionMethod(void) {
|
|
|
512 |
return class_getInstanceMethod(NSClassFromString(@"NSApplication"), @selector(reportException:));
|
|
|
513 |
}
|
|
|
514 |
|
|
|
515 |
static NSApplicationReportExceptionFunction FIRCLSOriginalNSExceptionReportExceptionFunction(void) {
|
|
|
516 |
return (NSApplicationReportExceptionFunction)
|
|
|
517 |
_firclsContext.readonly->exception.originalNSApplicationReportException;
|
|
|
518 |
}
|
|
|
519 |
|
|
|
520 |
void FIRCLSNSApplicationReportException(id self, SEL cmd, NSException *exception) {
|
|
|
521 |
FIRCLSExceptionRecordNSException(exception);
|
|
|
522 |
|
|
|
523 |
// Call the original implementation
|
|
|
524 |
FIRCLSOriginalNSExceptionReportExceptionFunction()(self, cmd, exception);
|
|
|
525 |
}
|
|
|
526 |
|
|
|
527 |
#endif
|