Skip to content
This repository was archived by the owner on Sep 14, 2020. It is now read-only.

Commit b96502c

Browse files
committed
Upgrade bugsnag-cocoa to 5.20.0
1 parent afdf343 commit b96502c

23 files changed

Lines changed: 595 additions & 65 deletions

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ Changelog
77

88
* (iOS) Prevent delivering duplicate fatal JS crash reports when using enhanced
99
native integration.
10+
[#337](https://github.com/bugsnag/bugsnag-react-native/pull/337)
11+
12+
### Enhancements
13+
14+
* (iOS) Upgrade to bugsnag-cocoa v5.20.0:
15+
* Persist breadcrumbs on disk to allow reading upon next boot in the event of
16+
an uncatchable app termination.
17+
* Add `+[Bugsnag appDidCrashLastLaunch]` as a helper to determine if the
18+
previous launch of the app ended in a crash or otherwise unexpected
19+
termination.
20+
* Report unexpected app terminations on iOS as likely out of memory events
21+
where the operating system killed the app
1022

1123
## 2.16.0 (2019-04-04)
1224

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@class BugsnagConfiguration;
4+
5+
@interface BSGOutOfMemoryWatchdog : NSObject
6+
7+
@property(nonatomic, strong, readonly) NSDictionary *lastBootCachedFileInfo;
8+
9+
/**
10+
* Create a new watchdog using the sentinel path to store app/device state
11+
*/
12+
- (instancetype)initWithSentinelPath:(NSString *)sentinelFilePath
13+
configuration:(BugsnagConfiguration *)config NS_DESIGNATED_INITIALIZER;
14+
/**
15+
* @return YES if the app was killed to end the previous app launch
16+
*/
17+
- (BOOL)didOOMLastLaunch;
18+
/**
19+
* Begin monitoring for lifecycle events and report the OOM from the last launch (if any)
20+
*/
21+
- (void)enable;
22+
/**
23+
* Stop monitoring for lifecycle events
24+
*/
25+
- (void)disable;
26+
@end
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
#if (TARGET_OS_TV || TARGET_OS_IPHONE)
2+
#define BSGOOMAvailable 1
3+
#else
4+
#define BSGOOMAvailable 0
5+
#endif
6+
7+
#if BSGOOMAvailable
8+
#import <UIKit/UIKit.h>
9+
#endif
10+
#import "BSGOutOfMemoryWatchdog.h"
11+
#import "BSG_KSSystemInfo.h"
12+
#import "BugsnagLogger.h"
13+
#import "Bugsnag.h"
14+
#import "BugsnagKSCrashSysInfoParser.h"
15+
#import "BugsnagSessionTracker.h"
16+
17+
@interface BSGOutOfMemoryWatchdog ()
18+
@property(nonatomic, getter=isWatching) BOOL watching;
19+
@property(nonatomic, strong) NSString *sentinelFilePath;
20+
@property(nonatomic, getter=didOOMLastLaunch) BOOL oomLastLaunch;
21+
@property(nonatomic, strong, readwrite) NSMutableDictionary *cachedFileInfo;
22+
@property(nonatomic, strong, readwrite) NSDictionary *lastBootCachedFileInfo;
23+
@end
24+
25+
@implementation BSGOutOfMemoryWatchdog
26+
27+
- (instancetype)init {
28+
self = [self initWithSentinelPath:nil configuration:nil];
29+
return self;
30+
}
31+
32+
- (instancetype)initWithSentinelPath:(NSString *)sentinelFilePath
33+
configuration:(BugsnagConfiguration *)config {
34+
if (sentinelFilePath.length == 0) {
35+
return nil; // disallow enabling a watcher without a file path
36+
}
37+
if (self = [super init]) {
38+
_sentinelFilePath = sentinelFilePath;
39+
#ifdef BSGOOMAvailable
40+
_oomLastLaunch = [self computeDidOOMLastLaunchWithConfig:config];
41+
_cachedFileInfo = [self generateCacheInfoWithConfig:config];
42+
#endif
43+
}
44+
return self;
45+
}
46+
47+
- (void)enable {
48+
#if BSGOOMAvailable
49+
if ([self isWatching]) {
50+
return;
51+
}
52+
[self writeSentinelFile];
53+
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
54+
[center addObserver:self
55+
selector:@selector(disable:)
56+
name:UIApplicationWillTerminateNotification
57+
object:nil];
58+
[center addObserver:self
59+
selector:@selector(handleTransitionToBackground:)
60+
name:UIApplicationDidEnterBackgroundNotification
61+
object:nil];
62+
[center addObserver:self
63+
selector:@selector(handleTransitionToForeground:)
64+
name:UIApplicationWillEnterForegroundNotification
65+
object:nil];
66+
[center addObserver:self
67+
selector:@selector(handleLowMemoryChange:)
68+
name:UIApplicationDidReceiveMemoryWarningNotification
69+
object:nil];
70+
[center addObserver:self
71+
selector:@selector(handleUpdateSession:)
72+
name:BSGSessionUpdateNotification
73+
object:nil];
74+
[[Bugsnag configuration]
75+
addObserver:self
76+
forKeyPath:NSStringFromSelector(@selector(releaseStage))
77+
options:NSKeyValueObservingOptionNew
78+
context:nil];
79+
self.watching = YES;
80+
#endif
81+
}
82+
83+
- (void)disable:(NSNotification *)note {
84+
[self disable];
85+
}
86+
87+
- (void)disable {
88+
if (![self isWatching]) {
89+
// Avoid unsubscribing from KVO when not observing
90+
// From the docs:
91+
// > Asking to be removed as an observer if not already registered as
92+
// > one results in an NSRangeException. You either call
93+
// > `removeObserver:forKeyPath:context: exactly once for the
94+
// > corresponding call to `addObserver:forKeyPath:options:context:`
95+
return;
96+
}
97+
self.watching = NO;
98+
[self deleteSentinelFile];
99+
[[NSNotificationCenter defaultCenter] removeObserver:self];
100+
@try {
101+
[[Bugsnag configuration]
102+
removeObserver:self
103+
forKeyPath:NSStringFromSelector(@selector(releaseStage))];
104+
} @catch (NSException *exception) {
105+
// Shouldn't happen, but if for some reason, unregistration happens
106+
// without registration, catch the resulting exception.
107+
}
108+
}
109+
110+
- (void)observeValueForKeyPath:(NSString *)keyPath
111+
ofObject:(id)object
112+
change:(NSDictionary<NSString *, id> *)change
113+
context:(void *)context {
114+
self.cachedFileInfo[@"app"][@"releaseStage"] = change[NSKeyValueChangeNewKey];
115+
[self writeSentinelFile];
116+
}
117+
- (void)handleTransitionToForeground:(NSNotification *)note {
118+
self.cachedFileInfo[@"app"][@"inForeground"] = @YES;
119+
[self writeSentinelFile];
120+
}
121+
122+
- (void)handleTransitionToBackground:(NSNotification *)note {
123+
self.cachedFileInfo[@"app"][@"inForeground"] = @NO;
124+
[self writeSentinelFile];
125+
}
126+
127+
- (void)handleLowMemoryChange:(NSNotification *)note {
128+
self.cachedFileInfo[@"device"][@"lowMemory"] = [[Bugsnag payloadDateFormatter]
129+
stringFromDate:[NSDate date]];
130+
[self writeSentinelFile];
131+
}
132+
133+
- (void)handleUpdateSession:(NSNotification *)note {
134+
id session = [note object];
135+
NSMutableDictionary *cache = (id)self.cachedFileInfo;
136+
if (session) {
137+
cache[@"session"] = session;
138+
} else {
139+
[cache removeObjectForKey:@"session"];
140+
}
141+
[self writeSentinelFile];
142+
}
143+
144+
- (BOOL)computeDidOOMLastLaunchWithConfig:(BugsnagConfiguration *)config {
145+
if ([[NSFileManager defaultManager] fileExistsAtPath:self.sentinelFilePath]) {
146+
NSDictionary *lastBootInfo = [self readSentinelFile];
147+
if (lastBootInfo != nil) {
148+
self.lastBootCachedFileInfo = lastBootInfo;
149+
NSString *lastBootAppVersion =
150+
[lastBootInfo valueForKeyPath:@"app.version"];
151+
NSString *lastBootOSVersion =
152+
[lastBootInfo valueForKeyPath:@"device.osBuild"];
153+
BOOL lastBootInForeground =
154+
[[lastBootInfo valueForKeyPath:@"app.inForeground"] boolValue];
155+
NSString *osVersion = [BSG_KSSystemInfo osBuildVersion];
156+
NSDictionary *appInfo = [[NSBundle mainBundle] infoDictionary];
157+
NSString *appVersion =
158+
[appInfo valueForKey:(__bridge NSString *)kCFBundleVersionKey];
159+
BOOL sameVersions = [lastBootOSVersion isEqualToString:osVersion] &&
160+
[lastBootAppVersion isEqualToString:appVersion];
161+
BOOL shouldReport = config.reportBackgroundOOMs || lastBootInForeground;
162+
[self deleteSentinelFile];
163+
return sameVersions && shouldReport;
164+
}
165+
}
166+
return NO;
167+
}
168+
169+
- (void)deleteSentinelFile {
170+
NSError *error = nil;
171+
[[NSFileManager defaultManager] removeItemAtPath:self.sentinelFilePath
172+
error:&error];
173+
if (error) {
174+
bsg_log_err(@"Failed to delete oom watchdog file: %@", error);
175+
unlink([self.sentinelFilePath UTF8String]);
176+
}
177+
}
178+
179+
- (NSDictionary *)readSentinelFile {
180+
NSError *error = nil;
181+
NSData *data = [NSData dataWithContentsOfFile:self.sentinelFilePath options:0 error:&error];
182+
if (error) {
183+
bsg_log_err(@"Failed to read oom watchdog file: %@", error);
184+
return nil;
185+
}
186+
NSDictionary *contents = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
187+
if (error) {
188+
bsg_log_err(@"Failed to read oom watchdog file: %@", error);
189+
return nil;
190+
}
191+
return contents;
192+
}
193+
194+
195+
- (void)writeSentinelFile {
196+
NSError *error = nil;
197+
if (![NSJSONSerialization isValidJSONObject:self.cachedFileInfo]) {
198+
bsg_log_err(@"Cached oom watchdog data cannot be written as JSON");
199+
return;
200+
}
201+
NSData *data = [NSJSONSerialization dataWithJSONObject:self.cachedFileInfo options:0 error:&error];
202+
if (error) {
203+
bsg_log_err(@"Cached oom watchdog data cannot be written as JSON: %@", error);
204+
return;
205+
}
206+
[data writeToFile:self.sentinelFilePath atomically:YES];
207+
}
208+
209+
- (NSMutableDictionary *)generateCacheInfoWithConfig:(BugsnagConfiguration *)config {
210+
NSDictionary *systemInfo = [BSG_KSSystemInfo systemInfo];
211+
NSMutableDictionary *cache = [NSMutableDictionary new];
212+
NSMutableDictionary *app = [NSMutableDictionary new];
213+
214+
app[@"id"] = systemInfo[@BSG_KSSystemField_BundleID] ?: @"";
215+
app[@"name"] = systemInfo[@BSG_KSSystemField_BundleName] ?: @"";
216+
app[@"releaseStage"] = config.releaseStage;
217+
app[@"version"] = systemInfo[@BSG_KSSystemField_BundleVersion] ?: @"";
218+
app[@"inForeground"] = @YES;
219+
#if TARGET_OS_TV
220+
app[@"type"] = @"tvOS";
221+
#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
222+
app[@"type"] = @"iOS";
223+
#endif
224+
cache[@"app"] = app;
225+
226+
NSMutableDictionary *device = [NSMutableDictionary new];
227+
device[@"id"] = systemInfo[@BSG_KSSystemField_DeviceAppHash];
228+
// device[@"lowMemory"] is initially unset
229+
device[@"osBuild"] = systemInfo[@BSG_KSSystemField_OSVersion];
230+
device[@"osVersion"] = systemInfo[@BSG_KSSystemField_SystemVersion];
231+
device[@"model"] = systemInfo[@BSG_KSSystemField_Machine];
232+
device[@"wordSize"] = @(PLATFORM_WORD_SIZE);
233+
#if TARGET_OS_SIMULATOR
234+
device[@"simulator"] = @YES;
235+
#else
236+
device[@"simulator"] = @NO;
237+
#endif
238+
cache[@"device"] = device;
239+
240+
return cache;
241+
}
242+
243+
@end

cocoa/vendor/bugsnag-cocoa/Source/Bugsnag.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ static NSString *_Nonnull const BugsnagSeverityInfo = @"info";
6767
+ (void)startBugsnagWithConfiguration:
6868
(BugsnagConfiguration *_Nonnull)configuration;
6969

70+
/**
71+
* @return YES if Bugsnag has been started and the previous launch crashed
72+
*/
73+
+ (BOOL)appDidCrashLastLaunch;
74+
7075
/** Send a custom or caught exception to Bugsnag.
7176
*
7277
* The exception will be sent to Bugsnag in the background allowing your

cocoa/vendor/bugsnag-cocoa/Source/Bugsnag.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ + (BugsnagNotifier *)notifier {
7676
return bsg_g_bugsnag_notifier;
7777
}
7878

79+
+ (BOOL)appDidCrashLastLaunch {
80+
if ([self bugsnagStarted]) {
81+
return [self.notifier appCrashedLastLaunch];
82+
}
83+
return NO;
84+
}
85+
7986
+ (void)notify:(NSException *)exception {
8087
if ([self bugsnagStarted]) {
8188
[self.notifier notifyException:exception

cocoa/vendor/bugsnag-cocoa/Source/BugsnagBreadcrumb.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ typedef void (^BSGBreadcrumbConfiguration)(BugsnagBreadcrumb *_Nonnull);
9494

9595
/** Number of breadcrumbs accumulated */
9696
@property(assign, readonly) NSUInteger count;
97+
/**
98+
* Path where breadcrumbs are persisted on disk
99+
*/
100+
@property (nonatomic, readonly, strong, nullable) NSString *cachePath;
97101

98102
/**
99103
* Store a new breadcrumb with a provided message.
@@ -125,4 +129,9 @@ typedef void (^BSGBreadcrumbConfiguration)(BugsnagBreadcrumb *_Nonnull);
125129
*/
126130
- (NSArray *_Nullable)arrayValue;
127131

132+
/**
133+
* Reads and return breadcrumb data currently stored on disk
134+
*/
135+
- (NSDictionary *_Nullable)cachedBreadcrumbs;
136+
128137
@end

cocoa/vendor/bugsnag-cocoa/Source/BugsnagBreadcrumb.m

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,18 @@ @implementation BugsnagBreadcrumbs
181181
NSUInteger BreadcrumbsDefaultCapacity = 20;
182182

183183
- (instancetype)init {
184+
static NSString *const BSGBreadcrumbCacheFileName = @"bugsnag_breadcrumbs.json";
184185
if (self = [super init]) {
185186
_breadcrumbs = [NSMutableArray new];
186187
_capacity = BreadcrumbsDefaultCapacity;
187188
_readWriteQueue = dispatch_queue_create("com.bugsnag.BreadcrumbRead",
188189
DISPATCH_QUEUE_SERIAL);
190+
NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(
191+
NSCachesDirectory, NSUserDomainMask, YES) firstObject];
192+
if (cacheDir != nil) {
193+
_cachePath = [cacheDir stringByAppendingPathComponent:
194+
BSGBreadcrumbCacheFileName];
195+
}
189196
}
190197
return self;
191198
}
@@ -205,10 +212,42 @@ - (void)addBreadcrumbWithBlock:
205212
if (crumb) {
206213
[self resizeToFitCapacity:self.capacity - 1];
207214
dispatch_barrier_sync(self.readWriteQueue, ^{
208-
[self.breadcrumbs addObject:crumb];
215+
[self.breadcrumbs addObject:crumb];
216+
// Serialize crumbs to disk inside barrier to avoid simultaneous
217+
// access to the file
218+
if (self.cachePath != nil) {
219+
static NSString *const arrayKeyPath = @"objectValue";
220+
NSArray *items = [self.breadcrumbs valueForKeyPath:arrayKeyPath];
221+
if ([NSJSONSerialization isValidJSONObject:items]) {
222+
NSError *error = nil;
223+
NSData *data = [NSJSONSerialization dataWithJSONObject:items
224+
options:0
225+
error:&error];
226+
[data writeToFile:self.cachePath atomically:NO];
227+
if (error != nil) {
228+
bsg_log_err(@"Failed to write breadcrumbs to disk: %@", error);
229+
}
230+
}
231+
}
209232
});
210233
}
211234
}
235+
236+
- (NSDictionary *)cachedBreadcrumbs {
237+
__block NSDictionary *cache = nil;
238+
dispatch_barrier_sync(self.readWriteQueue, ^{
239+
NSError *error = nil;
240+
NSData *data = [NSData dataWithContentsOfFile:self.cachePath options:0 error:&error];
241+
if (error == nil) {
242+
cache = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
243+
}
244+
if (error != nil) {
245+
bsg_log_err(@"Failed to read breadcrumbs from disk: %@", error);
246+
}
247+
});
248+
return cache;
249+
}
250+
212251
@synthesize capacity = _capacity;
213252

214253
- (NSUInteger)capacity {

0 commit comments

Comments
 (0)