From cc480299eb9d96a702821fc944a7550568374ca6 Mon Sep 17 00:00:00 2001 From: Andreas Linde Date: Mon, 10 Feb 2014 01:52:15 +0100 Subject: [PATCH] Multiple improvements for heuristic based kill detection handling - Marked the feature as `EXPERIMENTAL` - renamed property to `enableAppNotTerminatingCleanlyDetection` - Added details about the heuristic algorithm - Added optional delegate to let the developer influence if a report should be considered as a crash or not - Adjusted the description string in the generated report to make it more clear what actually happened. --- Classes/BITCrashManager.h | 18 +++++++++++++----- Classes/BITCrashManager.m | 23 +++++++++++++++-------- Classes/BITCrashManagerDelegate.h | 21 +++++++++++++++++++++ 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/Classes/BITCrashManager.h b/Classes/BITCrashManager.h index 37ac20711c..c56d1091a8 100644 --- a/Classes/BITCrashManager.h +++ b/Classes/BITCrashManager.h @@ -167,7 +167,7 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) { /** - * Enables heuristics to detect the app getting killed while being in the foreground + * EXPERIMENTAL: Enable heuristics to detect the app not terminating cleanly * * This allows it to get a crash report if the app got killed while being in the foreground * because of now of the following reasons: @@ -176,6 +176,7 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) { * - The app tried to allocate too much memory. If iOS did send a memory warning before killing the app because of this reason, `didReceiveMemoryWarningInLastSession` returns `YES`. * - Permitted background duration if main thread is running in an endless loop * - App failed to resume in time if main thread is running in an endless loop + * - If `enableMachExceptionHandler` is not activated, crashed due to stackoverflow will also be reported * * The following kills can _NOT_ be detected: * - Terminating the app takes too long @@ -186,16 +187,23 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) { * Crash reports triggered by this mechanisms do _NOT_ contain any stack traces since the time of the kill * cannot be intercepted and hence no stack trace of the time of the kill event can't be gathered. * + * The heuristic is implemented as follows: + * If the app never gets a `UIApplicationDidEnterBackgroundNotification` or `UIApplicationWillTerminateNotification` + * notification, PLCrashReporter doesn't detect a crash itself, and the app starts up again, it is assumed that + * the app got either killed by iOS while being in foreground or a crash occured that couldn't be detected. + * * Default: _NO_ * - * @warning This is a heuristic and it _MAY_ report false positives! + * @warning This is a heuristic and it _MAY_ report false positives! It has been tested with iOS 6.1 and iOS 7. + * Depending on Apple changing notification events, new iOS version may cause more false positives! * * @see wasKilledInLastSession * @see didReceiveMemoryWarningInLastSession + * @see `BITCrashManagerDelegate considerAppNotTerminatedCleanlyReportForCrashManager:` * @see [Apple Technical Note TN2151](https://developer.apple.com/library/ios/technotes/tn2151/_index.html) * @see [Apple Technical Q&A QA1693](https://developer.apple.com/library/ios/qa/qa1693/_index.html) */ -@property (nonatomic, assign, getter = isAppKillDetectionWhileInForegroundEnabled) BOOL enableAppKillDetectionWhileInForeground; +@property (nonatomic, assign, getter = isAppNotTerminatingCleanlyDetectionEnabled) BOOL enableAppNotTerminatingCleanlyDetection; /** @@ -271,7 +279,7 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) { @warning This property only has a correct value, once `[BITHockeyManager startManager]` was invoked! In addition, it is automatically disabled while a debugger session is active! - @see enableAppKillDetectionWhileInForeground + @see enableAppNotTerminatingCleanlyDetection @see didReceiveMemoryWarningInLastSession */ @property (nonatomic, readonly) BOOL wasKilledInLastSession; @@ -292,7 +300,7 @@ typedef NS_ENUM(NSUInteger, BITCrashManagerStatus) { @warning This property only has a correct value, once `[BITHockeyManager startManager]` was invoked! - @see enableAppKillDetectionWhileInForeground + @see enableAppNotTerminatingCleanlyDetection @see wasKilledInLastSession */ @property (nonatomic, readonly) BOOL didReceiveMemoryWarningInLastSession; diff --git a/Classes/BITCrashManager.m b/Classes/BITCrashManager.m index 76f2a4c811..e7280a92ec 100644 --- a/Classes/BITCrashManager.m +++ b/Classes/BITCrashManager.m @@ -370,7 +370,7 @@ NSString *const kBITFakeCrashReport = @"BITFakeCrashAppString"; } - (void)leavingAppSafely { - if (self.isAppKillDetectionWhileInForegroundEnabled) + if (self.isAppNotTerminatingCleanlyDetectionEnabled) [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppWentIntoBackgroundSafely]; } @@ -378,7 +378,7 @@ NSString *const kBITFakeCrashReport = @"BITFakeCrashAppString"; // we disable kill detection while the debugger is running, since we'd get only false positives if the app is terminated by the user using the debugger if (self.isDebuggerAttached) { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITAppWentIntoBackgroundSafely]; - } else if (self.isAppKillDetectionWhileInForegroundEnabled) { + } else if (self.isAppNotTerminatingCleanlyDetectionEnabled) { [[NSUserDefaults standardUserDefaults] setBool:NO forKey:kBITAppWentIntoBackgroundSafely]; static dispatch_once_t predAppData; @@ -862,19 +862,26 @@ NSString *const kBITFakeCrashReport = @"BITFakeCrashAppString"; if ([[NSUserDefaults standardUserDefaults] valueForKey:kBITAppDidReceiveLowMemoryNotification]) _didReceiveMemoryWarningInLastSession = [[NSUserDefaults standardUserDefaults] boolForKey:kBITAppDidReceiveLowMemoryNotification]; - if (!_didCrashInLastSession && self.isAppKillDetectionWhileInForegroundEnabled) { + if (!_didCrashInLastSession && self.isAppNotTerminatingCleanlyDetectionEnabled) { BOOL didAppSwitchToBackgroundSafely = YES; if ([[NSUserDefaults standardUserDefaults] valueForKey:kBITAppWentIntoBackgroundSafely]) didAppSwitchToBackgroundSafely = [[NSUserDefaults standardUserDefaults] boolForKey:kBITAppWentIntoBackgroundSafely]; if (!didAppSwitchToBackgroundSafely) { - NSLog(@"AppHasBeenKilled!"); + BOOL considerReport = YES; - [self createCrashReportForAppKill]; + if (self.delegate && + [self.delegate respondsToSelector:@selector(considerAppNotTerminatedCleanlyReportForCrashManager:)]) { + considerReport = [self.delegate considerAppNotTerminatedCleanlyReportForCrashManager:self]; + } - _wasKilledInLastSession = YES; - _didCrashInLastSession = YES; + if (considerReport) { + [self createCrashReportForAppKill]; + + _wasKilledInLastSession = YES; + _didCrashInLastSession = YES; + } } } [self appEnteredForeground]; @@ -924,7 +931,7 @@ NSString *const kBITFakeCrashReport = @"BITFakeCrashAppString"; [fakeReportString appendString:@"Exception Codes: 00000020 at 0x8badf00d\n"]; [fakeReportString appendString:@"\n"]; [fakeReportString appendString:@"Application Specific Information:\n"]; - [fakeReportString appendString:@"The application was killed by the iOS watchdog."]; + [fakeReportString appendString:@"The application did not terminate cleanly but no crash occured."]; if (self.didReceiveMemoryWarningInLastSession) { [fakeReportString appendString:@" The app received at least one Low Memory Warning."]; } diff --git a/Classes/BITCrashManagerDelegate.h b/Classes/BITCrashManagerDelegate.h index e2792b5ce4..0c3174c5a1 100644 --- a/Classes/BITCrashManagerDelegate.h +++ b/Classes/BITCrashManagerDelegate.h @@ -130,4 +130,25 @@ */ - (void)crashManagerDidFinishSendingCrashReport:(BITCrashManager *)crashManager; +///----------------------------------------------------------------------------- +/// @name Experimental +///----------------------------------------------------------------------------- + +/** Define if a report should be considered as a crash report + + Due to the risk, that these reports may be false positives, this delegates allows the + developer to influence which reports detected by the heuristic should actually be reported. + + The developer can use the following property to get more information about the crash scenario: + - `[BITCrashManager didReceiveMemoryWarningInLastSession]`: Did the app receive a low memory warning + + This allows only reports to be considered where at least one low memory warning notification was + received by the app to reduce to possibility of having false positives. + + @param crashManager The `BITCrashManager` instance invoking this delegate + @return `YES` if the heuristic based detected report should be reported, otherwise `NO` + @see `[BITCrashManager didReceiveMemoryWarningInLastSession]` + */ +-(BOOL)considerAppNotTerminatedCleanlyReportForCrashManager:(BITCrashManager *)crashManager; + @end