Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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