From 030767dba34e465f3547fc847d36f6342e4038b7 Mon Sep 17 00:00:00 2001 From: Steve Kirkland Date: Sun, 15 Nov 2020 21:27:13 +0000 Subject: [PATCH 01/11] iOS 11 model switched for greater stability --- .buildkite/pipeline.yml | 4 ++-- features/steps/ios_steps.rb | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index b7c5b9edd..1a5ab1391 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -87,11 +87,11 @@ steps: command: - "--app=/app/build/iOSTestApp.ipa" - "--farm=bs" - - "--device=IOS_11" + - "--device=IOS_11_0_IPHONE_8_PLUS" - "--username=$BROWSER_STACK_USERNAME" - "--access-key=$BROWSER_STACK_ACCESS_KEY" - "--resilient" - - "--appium-version=1.15.0" + - "--appium-version=1.16.0" - "--fail-fast" concurrency: 10 concurrency_group: browserstack-app diff --git a/features/steps/ios_steps.rb b/features/steps/ios_steps.rb index 4b57ea0bf..9902b2cd8 100644 --- a/features/steps/ios_steps.rb +++ b/features/steps/ios_steps.rb @@ -181,13 +181,17 @@ def request_fields_are_equal(key, index_a, index_b) Then("the payload field {string} matches the test device model") do |field| internal_names = { + "iPhone 6" => %w[iPhone7,2], + "iPhone 6 Plus" => %w[iPhone7,1], + "iPhone 6S" => %w[iPhone8,1], "iPhone 7" => %w[iPhone9,1 iPhone9,2 iPhone9,3 iPhone9,4], - "iPhone 8" => %w[iPhone10,1 iPhone10,2 iPhone10,4 iPhone10,5], + "iPhone 8" => %w[iPhone10,1 iPhone10,4], + "iPhone 8 Plus" => %w[iPhone10,2 iPhone10,5], "iPhone 11" => %w[iPhone12,1], "iPhone 11 Pro" => %w[iPhone12,3], "iPhone 11 Pro Max" => %w[iPhone12,5], "iPhone X" => %w[iPhone10,3 iPhone10,6], - "iPhone XR" => ["iPhone11,8"], + "iPhone XR" => %w[iPhone11,8], "iPhone XS" => %w[iPhone11,2 iPhone11,4 iPhone11,8] } expected_model = MazeRunner.config.capabilities["device"] From 01c2cbc3215eeac577c7ad9ec608488aebcf092c Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 19 Nov 2020 09:18:39 +0000 Subject: [PATCH 02/11] Handle HTTP error status codes --- Bugsnag/BugsnagErrorReportSink.m | 18 +-- Bugsnag/Delivery/BugsnagApiClient.h | 23 ++- Bugsnag/Delivery/BugsnagApiClient.m | 146 ++++++++---------- .../BugsnagSessionTrackingApiClient.m | 36 +++-- Tests/BugsnagApiClientTest.m | 4 +- 5 files changed, 110 insertions(+), 117 deletions(-) diff --git a/Bugsnag/BugsnagErrorReportSink.m b/Bugsnag/BugsnagErrorReportSink.m index 4bac7ee6f..f098f6945 100644 --- a/Bugsnag/BugsnagErrorReportSink.m +++ b/Bugsnag/BugsnagErrorReportSink.m @@ -92,10 +92,10 @@ - (instancetype)initWithApiClient:(BugsnagErrorReportApiClient *)apiClient { } - (void)finishActiveRequest:(NSString *)requestId - success:(BOOL)success + completed:(BOOL)completed error:(NSError *)error block:(BSGOnErrorSentBlock)block { - block(requestId, success, error); + block(requestId, completed, error); @synchronized (self.activeRequests) { [self.activeRequests removeObject:requestId]; } @@ -123,7 +123,7 @@ - (void)sendStoredReports:(NSDictionary *)ksCrashRe if ([event shouldBeSent] && [self runOnSendBlocks:configuration event:event]) { storedEvents[fileKey] = event; } else { // delete the report as the user has discarded it - [self finishActiveRequest:fileKey success:true error:nil block:block]; + [self finishActiveRequest:fileKey completed:YES error:nil block:block]; } } [self deliverStoredEvents:storedEvents configuration:configuration block:block]; @@ -138,13 +138,11 @@ - (void)deliverStoredEvents:(NSMutableDictionary *)s NSMutableDictionary *apiHeaders = [[configuration errorApiHeaders] mutableCopy]; BSGDictSetSafeObject(apiHeaders, event.apiKey, BSGHeaderApiKey); - [self.apiClient sendItems:1 - withPayload:requestPayload - toURL:configuration.notifyURL - headers:apiHeaders - onCompletion:^(NSUInteger reportCount, BOOL success, NSError *error) { - [self finishActiveRequest:filename success:success error:error block:block]; - }]; + [self.apiClient sendJSONPayload:requestPayload headers:apiHeaders toURL:configuration.notifyURL + completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError *error) { + BOOL completed = status == BugsnagApiClientDeliveryStatusDelivered || status == BugsnagApiClientDeliveryStatusUndeliverable; + [self finishActiveRequest:filename completed:completed error:error block:block]; + }]; } } diff --git a/Bugsnag/Delivery/BugsnagApiClient.h b/Bugsnag/Delivery/BugsnagApiClient.h index d1cca8eab..420897dce 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.h +++ b/Bugsnag/Delivery/BugsnagApiClient.h @@ -7,7 +7,16 @@ @class BugsnagConfiguration; -typedef void (^RequestCompletion)(NSUInteger reportCount, BOOL success, NSError *error); +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { + /// The payload was delivered successfully and can be deleted. + BugsnagApiClientDeliveryStatusDelivered, + /// The payload was not delivered but can be retried, e.g. when there was a loss of connectivity. + BugsnagApiClientDeliveryStatusFailed, + /// The payload cannot be delivered and should be deleted without attempting to retry. + BugsnagApiClientDeliveryStatusUndeliverable, +}; @interface BugsnagApiClient : NSObject @@ -21,14 +30,14 @@ typedef void (^RequestCompletion)(NSUInteger reportCount, BOOL success, NSError - (NSOperation *)deliveryOperation; -- (void)sendItems:(NSUInteger)count - withPayload:(NSDictionary *)payload - toURL:(NSURL *)url - headers:(NSDictionary *)headers - onCompletion:(RequestCompletion)onCompletion; +- (void)sendJSONPayload:(NSDictionary *)payload + headers:(NSDictionary *)headers + toURL:(NSURL *)url + completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error))completionHandler; @property(readonly) NSOperationQueue *sendQueue; @property(readonly) BugsnagConfiguration *config; - @end + +NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Delivery/BugsnagApiClient.m b/Bugsnag/Delivery/BugsnagApiClient.m index 7f89c966d..9707fd720 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.m +++ b/Bugsnag/Delivery/BugsnagApiClient.m @@ -11,11 +11,25 @@ #import "Private.h" #import "BSGJSONSerialization.h" -@interface BSGDelayOperation : NSOperation -@end +typedef NS_ENUM(NSInteger, HTTPStatusCode) { + /// 402 Payment Required: a nonstandard client error status response code that is reserved for future use. + /// + /// This status code is returned by ngrok when a tunnel has expired. + HTTPStatusCodePaymentRequired = 402, + + /// 407 Proxy Authentication Required: the request has not been applied because it lacks valid authentication credentials + /// for a proxy server that is between the browser and the server that can access the requested resource. + HTTPStatusCodeProxyAuthenticationRequired = 407, + + /// 408 Request Timeout: the server would like to shut down this unused connection. + HTTPStatusCodeClientTimeout = 408, + + /// 429 Too Many Requests: the user has sent too many requests in a given amount of time ("rate limiting"). + HTTPStatusCodeTooManyRequests = 429, +}; @interface BugsnagApiClient() -@property (nonatomic, strong) NSURLSession *generatedSession; +@property (nonatomic, strong) NSURLSession *session; @end @implementation BugsnagApiClient @@ -26,6 +40,7 @@ - (instancetype)initWithConfig:(BugsnagConfiguration *)configuration _sendQueue = [NSOperationQueue new]; _sendQueue.maxConcurrentOperationCount = 1; _config = configuration; + _session = configuration.session ?: [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; if ([_sendQueue respondsToSelector:@selector(qualityOfService)]) { _sendQueue.qualityOfService = NSQualityOfServiceUtility; @@ -37,89 +52,67 @@ - (instancetype)initWithConfig:(BugsnagConfiguration *)configuration - (void)flushPendingData { [self.sendQueue cancelAllOperations]; - BSGDelayOperation *delay = [BSGDelayOperation new]; + NSOperation *delay = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:1]; }]; NSOperation *deliver = [self deliveryOperation]; [deliver addDependency:delay]; [self.sendQueue addOperations:@[delay, deliver] waitUntilFinished:NO]; } - (NSOperation *)deliveryOperation { - bsg_log_err(@"Should override deliveryOperation in super class"); + bsg_log_err(@"Should override deliveryOperation in subclass"); return [NSOperation new]; } #pragma mark - Delivery - -- (void)sendItems:(NSUInteger)count - withPayload:(NSDictionary *)payload - toURL:(NSURL *)url - headers:(NSDictionary *)headers - onCompletion:(RequestCompletion)onCompletion { - - @try { - NSError *error = nil; - NSData *jsonData = - [BSGJSONSerialization dataWithJSONObject:payload - options:0 - error:&error]; - - if (jsonData == nil) { - if (onCompletion) { - onCompletion(0, NO, error); - } - return; +- (void)sendJSONPayload:(NSDictionary *)payload + headers:(NSDictionary *)headers + toURL:(NSURL *)url + completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error))completionHandler { + + if (![BSGJSONSerialization isValidJSONObject:payload]) { + bsg_log_err(@"Error: Invalid JSON payload passed to %s", __PRETTY_FUNCTION__); + return completionHandler(BugsnagApiClientDeliveryStatusUndeliverable, nil); + } + + NSError *error = nil; + NSData *data = [BSGJSONSerialization dataWithJSONObject:payload options:0 error:&error]; + if (!data) { + bsg_log_err(@"Error: Could not encode JSON payload passed to %s", __PRETTY_FUNCTION__); + return completionHandler(BugsnagApiClientDeliveryStatusUndeliverable, error); + } + + NSMutableURLRequest *request = [self prepareRequest:url headers:headers]; + + [[self.session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (![response isKindOfClass:[NSHTTPURLResponse class]]) { + return completionHandler(BugsnagApiClientDeliveryStatusFailed, error ?: + [NSError errorWithDomain:@"BugsnagApiClientErrorDomain" code:0 userInfo:@{ + NSLocalizedDescriptionKey: @"Request failed: no response was received", + NSURLErrorFailingURLErrorKey: url }]); } - NSMutableURLRequest *request = [self prepareRequest:url headers:headers]; - - if ([NSURLSession class]) { - NSURLSession *session = [self prepareSession]; - NSURLSessionTask *task = [session - uploadTaskWithRequest:request - fromData:jsonData - completionHandler:^(NSData *_Nullable responseBody, - NSURLResponse *_Nullable response, - NSError *_Nullable requestErr) { - if (onCompletion) { - onCompletion(count, requestErr == nil, requestErr); - } - }]; - [task resume]; - } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSURLResponse *response = nil; - request.HTTPBody = jsonData; - [NSURLConnection sendSynchronousRequest:request - returningResponse:&response - error:&error]; - if (onCompletion) { - onCompletion(count, error == nil, error); - } -#pragma clang diagnostic pop + + NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; + + if (statusCode / 100 == 2) { + return completionHandler(BugsnagApiClientDeliveryStatusDelivered, nil); } - } @catch (NSException *exception) { - if (onCompletion) { - onCompletion(count, NO, - [NSError errorWithDomain:exception.reason - code:420 - userInfo:@{BSGKeyException: exception}]); + + error = [NSError errorWithDomain:@"BugsnagApiClientErrorDomain" code:1 userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Request failed: unacceptable status code %ld (%@)", + (long)statusCode, [NSHTTPURLResponse localizedStringForStatusCode:statusCode]], + NSURLErrorFailingURLErrorKey: url }]; + + if (statusCode / 100 == 4 && + statusCode != HTTPStatusCodePaymentRequired && + statusCode != HTTPStatusCodeProxyAuthenticationRequired && + statusCode != HTTPStatusCodeClientTimeout && + statusCode != HTTPStatusCodeTooManyRequests) { + return completionHandler(BugsnagApiClientDeliveryStatusUndeliverable, error); } - } -} - -- (NSURLSession *)prepareSession { - NSURLSession *session = [Bugsnag configuration].session; - if (session) { - return session; - } else { - if (!self.generatedSession) { - _generatedSession = [NSURLSession - sessionWithConfiguration:[NSURLSessionConfiguration - defaultSessionConfiguration]]; - } - return self.generatedSession; - } + + return completionHandler(BugsnagApiClientDeliveryStatusFailed, error); + }] resume]; } - (NSMutableURLRequest *)prepareRequest:(NSURL *)url @@ -142,12 +135,3 @@ - (void)dealloc { } @end - -@implementation BSGDelayOperation -const NSTimeInterval BSG_SEND_DELAY_SECS = 1; - -- (void)main { - [NSThread sleepForTimeInterval:BSG_SEND_DELAY_SECS]; -} - -@end diff --git a/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m b/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m index b087b6c46..cb740fcf8 100644 --- a/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m +++ b/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m @@ -73,23 +73,25 @@ - (void)deliverSessionsInStore:(BugsnagSessionFileStore *)store { @"Bugsnag-API-Key": apiKey, @"Bugsnag-Sent-At": [BSG_RFC3339DateTool stringFromDate:[NSDate new]] }; - [self sendItems:1 - withPayload:data - toURL:sessionURL - headers:HTTPHeaders - onCompletion:^(NSUInteger sentCount, BOOL success, NSError *error) { - if (success && error == nil) { - bsg_log_info(@"Sent session %@ to Bugsnag", session.id); - [store deleteFileWithId:fileId]; - } else { - bsg_log_warn(@"Failed to send sessions to Bugsnag: %@", error); - } - - // remove request - @synchronized (self.activeIds) { - [self.activeIds removeObject:fileId]; - } - }]; + [self sendJSONPayload:data headers:HTTPHeaders toURL:sessionURL + completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError *error) { + switch (status) { + case BugsnagApiClientDeliveryStatusDelivered: + bsg_log_info(@"Sent session %@ to Bugsnag", session.id); + [store deleteFileWithId:fileId]; + break; + case BugsnagApiClientDeliveryStatusFailed: + bsg_log_warn(@"Failed to send sessions to Bugsnag: %@", error); + break; + case BugsnagApiClientDeliveryStatusUndeliverable: + bsg_log_warn(@"Failed to send sessions to Bugsnag: %@", error); + [store deleteFileWithId:fileId]; + break; + } + @synchronized (self.activeIds) { + [self.activeIds removeObject:fileId]; + } + }]; }]; } } diff --git a/Tests/BugsnagApiClientTest.m b/Tests/BugsnagApiClientTest.m index 9889f9921..9905838a2 100644 --- a/Tests/BugsnagApiClientTest.m +++ b/Tests/BugsnagApiClientTest.m @@ -20,8 +20,8 @@ @implementation BugsnagApiClientTest - (void)testBadJSON { BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; BugsnagApiClient* client = [[BugsnagApiClient alloc] initWithConfig:config queueName:@"test"]; - [client sendItems:1 withPayload:@{@1: @"a"} toURL:[NSURL URLWithString:@"file:///dev/null"] headers:@{@1: @"a"} onCompletion:^(NSUInteger reportCount, BOOL success, NSError *error) { - }]; + XCTAssertNoThrow([client sendJSONPayload:(id)@{@1: @"a"} headers:(id)@{@1: @"a"} toURL:[NSURL URLWithString:@"file:///dev/null"] + completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) {}]); } @end From 1dcf6a11a2034bcc5723de4ac016165a05a460af Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 19 Nov 2020 11:13:02 +0000 Subject: [PATCH 03/11] Add unit test cases for HTTP status code handling --- Bugsnag.xcodeproj/project.pbxproj | 14 +++++ Bugsnag/Client/BugsnagClient.m | 3 +- Bugsnag/Delivery/BugsnagApiClient.h | 6 +- Bugsnag/Delivery/BugsnagApiClient.m | 7 +-- .../BugsnagSessionTrackingApiClient.h | 3 + .../BugsnagSessionTrackingApiClient.m | 7 ++- Tests/BugsnagApiClientTest.m | 60 ++++++++++++++++++- Tests/URLSessionMock.h | 19 ++++++ Tests/URLSessionMock.m | 48 +++++++++++++++ 9 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 Tests/URLSessionMock.h create mode 100644 Tests/URLSessionMock.m diff --git a/Bugsnag.xcodeproj/project.pbxproj b/Bugsnag.xcodeproj/project.pbxproj index 62ba98e1e..683920736 100644 --- a/Bugsnag.xcodeproj/project.pbxproj +++ b/Bugsnag.xcodeproj/project.pbxproj @@ -658,12 +658,17 @@ 00AD1F302486A17900A27979 /* BugsnagSessionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 00AD1F012486A17900A27979 /* BugsnagSessionTracker.m */; }; 00AD1F312486A17900A27979 /* BugsnagSessionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 00AD1F012486A17900A27979 /* BugsnagSessionTracker.m */; }; 00E636C224878D84006CBF1A /* BSG_RFC3339DateTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 008969142486DAD000DC48C2 /* BSG_RFC3339DateTool.m */; }; + 014475FD2566844F0018AB94 /* BugsnagApiClientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9103632502320A00E9D1E2 /* BugsnagApiClientTest.m */; }; + 01447605256684500018AB94 /* BugsnagApiClientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9103632502320A00E9D1E2 /* BugsnagApiClientTest.m */; }; 01B14C56251CE55F00118748 /* report-react-native-promise-rejection.json in Resources */ = {isa = PBXBuildFile; fileRef = 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */; }; 01B14C57251CE55F00118748 /* report-react-native-promise-rejection.json in Resources */ = {isa = PBXBuildFile; fileRef = 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */; }; 01B14C58251CE55F00118748 /* report-react-native-promise-rejection.json in Resources */ = {isa = PBXBuildFile; fileRef = 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */; }; 01C17AE72542ED7F00C102C9 /* KSCrashReportWriterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01C17AE62542ED7F00C102C9 /* KSCrashReportWriterTests.m */; }; 01C17AE82542ED7F00C102C9 /* KSCrashReportWriterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01C17AE62542ED7F00C102C9 /* KSCrashReportWriterTests.m */; }; 01C17AE92542ED7F00C102C9 /* KSCrashReportWriterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01C17AE62542ED7F00C102C9 /* KSCrashReportWriterTests.m */; }; + 01E8765E256684E700F4B70A /* URLSessionMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 01E8765D256684E700F4B70A /* URLSessionMock.m */; }; + 01E8765F256684E700F4B70A /* URLSessionMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 01E8765D256684E700F4B70A /* URLSessionMock.m */; }; + 01E87660256684E700F4B70A /* URLSessionMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 01E8765D256684E700F4B70A /* URLSessionMock.m */; }; 3A700A9424A63ABC0068CD1B /* BugsnagThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A700A8024A63A8E0068CD1B /* BugsnagThread.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3A700A9524A63AC50068CD1B /* BugsnagSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A700A8124A63A8E0068CD1B /* BugsnagSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3A700A9624A63AC60068CD1B /* BugsnagStackframe.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A700A8224A63A8E0068CD1B /* BugsnagStackframe.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1276,6 +1281,8 @@ 00E636C324878FFC006CBF1A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "report-react-native-promise-rejection.json"; sourceTree = ""; }; 01C17AE62542ED7F00C102C9 /* KSCrashReportWriterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSCrashReportWriterTests.m; sourceTree = ""; }; + 01E8765C256684E700F4B70A /* URLSessionMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = URLSessionMock.h; sourceTree = ""; }; + 01E8765D256684E700F4B70A /* URLSessionMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = URLSessionMock.m; sourceTree = ""; }; 3A700A8024A63A8E0068CD1B /* BugsnagThread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagThread.h; sourceTree = ""; }; 3A700A8124A63A8E0068CD1B /* BugsnagSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagSession.h; sourceTree = ""; }; 3A700A8224A63A8E0068CD1B /* BugsnagStackframe.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagStackframe.h; sourceTree = ""; }; @@ -1693,6 +1700,8 @@ 008966AE2486D43500DC48C2 /* Swift Tests */, CBA22499251E429C00B87416 /* TestSupport.h */, CBA2249A251E429C00B87416 /* TestSupport.m */, + 01E8765C256684E700F4B70A /* URLSessionMock.h */, + 01E8765D256684E700F4B70A /* URLSessionMock.m */, ); path = Tests; sourceTree = ""; @@ -2581,6 +2590,7 @@ 008967542486D43700DC48C2 /* BugsnagOnCrashTest.m in Sources */, 008966F72486D43700DC48C2 /* RegisterErrorDataTest.m in Sources */, 008967152486D43700DC48C2 /* BugsnagCollectionsBSGDictMergeTest.m in Sources */, + 01E8765E256684E700F4B70A /* URLSessionMock.m in Sources */, 008967632486D43700DC48C2 /* BugsnagCollectionsBSGDictSetSafeObjectTest.m in Sources */, 008967AB2486D43700DC48C2 /* KSMach_Tests.m in Sources */, 0089672A2486D43700DC48C2 /* BugsnagStacktraceTest.m in Sources */, @@ -2708,6 +2718,7 @@ 008967132486D43700DC48C2 /* BugsnagEventTests.m in Sources */, 0089675B2486D43700DC48C2 /* BugsnagEnabledBreadcrumbTest.m in Sources */, 008966EC2486D43700DC48C2 /* BugsnagDeviceTest.m in Sources */, + 014475FD2566844F0018AB94 /* BugsnagApiClientTest.m in Sources */, 008967462486D43700DC48C2 /* BugsnagTests.m in Sources */, 008967A62486D43700DC48C2 /* KSString_Tests.m in Sources */, 004E353D2487B3B8007FBAE4 /* BugsnagSwiftTests.swift in Sources */, @@ -2750,6 +2761,7 @@ 008967432486D43700DC48C2 /* BugsnagSessionTrackerStopTest.m in Sources */, 008967972486D43700DC48C2 /* KSCrashState_Tests.m in Sources */, 008967762486D43700DC48C2 /* XCTestCase+KSCrash.m in Sources */, + 01E8765F256684E700F4B70A /* URLSessionMock.m in Sources */, 008967312486D43700DC48C2 /* BugsnagStateEventTest.m in Sources */, 004E35362487AFF2007FBAE4 /* BugsnagHandledStateTest.m in Sources */, 01C17AE82542ED7F00C102C9 /* KSCrashReportWriterTests.m in Sources */, @@ -2891,6 +2903,7 @@ 0089674A2486D43700DC48C2 /* BugsnagUserTest.m in Sources */, 0089673B2486D43700DC48C2 /* BugsnagEventFromKSCrashReportTest.m in Sources */, 0089674D2486D43700DC48C2 /* BSGConnectivityTest.m in Sources */, + 01E87660256684E700F4B70A /* URLSessionMock.m in Sources */, 008966F02486D43700DC48C2 /* BugsnagClientPayloadInfoTest.m in Sources */, 008967652486D43700DC48C2 /* BugsnagCollectionsBSGDictSetSafeObjectTest.m in Sources */, E701FAA92490EF77008D842F /* ClientApiValidationTest.m in Sources */, @@ -2915,6 +2928,7 @@ 01C17AE92542ED7F00C102C9 /* KSCrashReportWriterTests.m in Sources */, 00896A422486DBDD00DC48C2 /* BSGConfigurationBuilderTests.m in Sources */, 008967682486D43700DC48C2 /* BugsnagNotifierTest.m in Sources */, + 01447605256684500018AB94 /* BugsnagApiClientTest.m in Sources */, 0089676E2486D43700DC48C2 /* BugsnagTestsDummyClass.m in Sources */, 008967412486D43700DC48C2 /* BugsnagAppTest.m in Sources */, 008967052486D43700DC48C2 /* BugsnagThreadSerializationTest.m in Sources */, diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index fc956798e..444c1da61 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -394,8 +394,7 @@ - (instancetype)initWithConfiguration:(BugsnagConfiguration *)initConfiguration self.extraRuntimeInfo = [NSMutableDictionary new]; self.metadataLock = [[NSLock alloc] init]; self.crashSentry = [BugsnagCrashSentry new]; - self.errorReportApiClient = [[BugsnagErrorReportApiClient alloc] initWithConfig:configuration - queueName:@"Error API queue"]; + self.errorReportApiClient = [[BugsnagErrorReportApiClient alloc] initWithSession:configuration.session queueName:@"Error API queue"]; bsg_g_bugsnag_data.onCrash = (void (*)(const BSG_KSCrashReportWriter *))self.configuration.onCrashHandler; static dispatch_once_t once_t; diff --git a/Bugsnag/Delivery/BugsnagApiClient.h b/Bugsnag/Delivery/BugsnagApiClient.h index 420897dce..0e00dba98 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.h +++ b/Bugsnag/Delivery/BugsnagApiClient.h @@ -5,8 +5,6 @@ #import -@class BugsnagConfiguration; - NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { @@ -20,8 +18,7 @@ typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { @interface BugsnagApiClient : NSObject -- (instancetype)initWithConfig:(BugsnagConfiguration *)configuration - queueName:(NSString *)queueName; +- (instancetype)initWithSession:(nullable NSURLSession *)session queueName:(NSString *)queueName; /** * Send outstanding reports @@ -36,7 +33,6 @@ typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error))completionHandler; @property(readonly) NSOperationQueue *sendQueue; -@property(readonly) BugsnagConfiguration *config; @end diff --git a/Bugsnag/Delivery/BugsnagApiClient.m b/Bugsnag/Delivery/BugsnagApiClient.m index 9707fd720..eb3ca9f8a 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.m +++ b/Bugsnag/Delivery/BugsnagApiClient.m @@ -34,18 +34,15 @@ @interface BugsnagApiClient() @implementation BugsnagApiClient -- (instancetype)initWithConfig:(BugsnagConfiguration *)configuration - queueName:(NSString *)queueName { +- (instancetype)initWithSession:(nullable NSURLSession *)session queueName:(NSString *)queueName { if (self = [super init]) { _sendQueue = [NSOperationQueue new]; _sendQueue.maxConcurrentOperationCount = 1; - _config = configuration; - _session = configuration.session ?: [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; - if ([_sendQueue respondsToSelector:@selector(qualityOfService)]) { _sendQueue.qualityOfService = NSQualityOfServiceUtility; } _sendQueue.name = queueName; + _session = session ?: [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; } return self; } diff --git a/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.h b/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.h index 025097330..0d1ad3367 100644 --- a/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.h +++ b/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.h @@ -6,10 +6,13 @@ #import #import "BugsnagApiClient.h" +@class BugsnagConfiguration; @class BugsnagSessionFileStore; @interface BugsnagSessionTrackingApiClient : BugsnagApiClient +- (instancetype)initWithConfig:(BugsnagConfiguration *)configuration queueName:(NSString *)queueName; + /** * Asynchronously delivers sessions written to the store * diff --git a/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m b/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m index cb740fcf8..720a9060c 100644 --- a/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m +++ b/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m @@ -21,15 +21,16 @@ @interface BugsnagConfiguration () @interface BugsnagSessionTrackingApiClient () @property NSMutableSet *activeIds; @property(nonatomic) NSString *codeBundleId; +@property(nonatomic) BugsnagConfiguration *config; @end @implementation BugsnagSessionTrackingApiClient -- (instancetype)initWithConfig:(BugsnagConfiguration *)configuration - queueName:(NSString *)queueName { - if (self = [super initWithConfig:configuration queueName:queueName]) { +- (instancetype)initWithConfig:(BugsnagConfiguration *)configuration queueName:(NSString *)queueName { + if ((self = [super initWithSession:configuration.session queueName:queueName])) { _activeIds = [NSMutableSet new]; + _config = configuration; } return self; } diff --git a/Tests/BugsnagApiClientTest.m b/Tests/BugsnagApiClientTest.m index 9905838a2..2acfb4aa4 100644 --- a/Tests/BugsnagApiClientTest.m +++ b/Tests/BugsnagApiClientTest.m @@ -10,6 +10,7 @@ #import "BugsnagApiClient.h" #import #import "BugsnagTestConstants.h" +#import "URLSessionMock.h" @interface BugsnagApiClientTest : XCTestCase @@ -18,10 +19,65 @@ @interface BugsnagApiClientTest : XCTestCase @implementation BugsnagApiClientTest - (void)testBadJSON { - BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; - BugsnagApiClient* client = [[BugsnagApiClient alloc] initWithConfig:config queueName:@"test"]; + BugsnagApiClient *client = [[BugsnagApiClient alloc] initWithSession:nil queueName:@"test"]; XCTAssertNoThrow([client sendJSONPayload:(id)@{@1: @"a"} headers:(id)@{@1: @"a"} toURL:[NSURL URLWithString:@"file:///dev/null"] completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) {}]); } +- (void)testHTTPStatusCodes { + NSURL *url = [NSURL URLWithString:@"https://example.com"]; + URLSessionMock *session = [[URLSessionMock alloc] init]; + BugsnagApiClient *client = [[BugsnagApiClient alloc] initWithSession:(id)session queueName:@""]; + + void (^ test)(NSInteger, BugsnagApiClientDeliveryStatus, BOOL) = + ^(NSInteger statusCode, BugsnagApiClientDeliveryStatus expectedDeliveryStatus, BOOL expectError) { + XCTestExpectation *expectation = [self expectationWithDescription:@"completionHandler should be called"]; + id response = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:statusCode HTTPVersion:@"1.1" headerFields:nil]; + [session mockData:[NSData data] response:response error:nil]; + [client sendJSONPayload:@{} headers:@{} toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { + XCTAssertEqual(status, expectedDeliveryStatus); + expectError ? XCTAssertNotNil(error) : XCTAssertNil(error); + [expectation fulfill]; + }]; + }; + + test(200, BugsnagApiClientDeliveryStatusDelivered, NO); + + // Permanent failures + test(400, BugsnagApiClientDeliveryStatusUndeliverable, YES); + test(401, BugsnagApiClientDeliveryStatusUndeliverable, YES); + test(403, BugsnagApiClientDeliveryStatusUndeliverable, YES); + test(404, BugsnagApiClientDeliveryStatusUndeliverable, YES); + test(405, BugsnagApiClientDeliveryStatusUndeliverable, YES); + test(406, BugsnagApiClientDeliveryStatusUndeliverable, YES); + + // Transient failures + test(402, BugsnagApiClientDeliveryStatusFailed, YES); + test(407, BugsnagApiClientDeliveryStatusFailed, YES); + test(408, BugsnagApiClientDeliveryStatusFailed, YES); + test(429, BugsnagApiClientDeliveryStatusFailed, YES); + test(500, BugsnagApiClientDeliveryStatusFailed, YES); + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testNotConnectedToInternetError { + NSURL *url = [NSURL URLWithString:@"https://example.com"]; + URLSessionMock *session = [[URLSessionMock alloc] init]; + BugsnagApiClient *client = [[BugsnagApiClient alloc] initWithSession:(id)session queueName:@""]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"completionHandler should be called"]; + [session mockData:nil response:nil error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorNotConnectedToInternet userInfo:@{ + NSURLErrorFailingURLErrorKey: url, + }]]; + [client sendJSONPayload:@{} headers:@{} toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { + XCTAssertEqual(status, BugsnagApiClientDeliveryStatusFailed); + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, NSURLErrorDomain); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + @end diff --git a/Tests/URLSessionMock.h b/Tests/URLSessionMock.h new file mode 100644 index 000000000..564374cb7 --- /dev/null +++ b/Tests/URLSessionMock.h @@ -0,0 +1,19 @@ +// +// URLSessionMock.h +// Bugsnag +// +// Created by Nick Dowell on 19/11/2020. +// Copyright © 2020 Bugsnag Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface URLSessionMock : NSObject + +- (void)mockData:(nullable NSData *)data response:(nullable NSURLResponse *)response error:(nullable NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/URLSessionMock.m b/Tests/URLSessionMock.m new file mode 100644 index 000000000..03a35ab2f --- /dev/null +++ b/Tests/URLSessionMock.m @@ -0,0 +1,48 @@ +// +// URLSessionMock.m +// Bugsnag +// +// Created by Nick Dowell on 19/11/2020. +// Copyright © 2020 Bugsnag Inc. All rights reserved. +// + +#import "URLSessionMock.h" + +@interface URLSessionUploadTaskMock : NSURLSessionUploadTask + +@property dispatch_block_t mock; + +@end + +#pragma mark - + +@implementation URLSessionMock { + NSData *_data; + NSURLResponse *_response; + NSError *_error; +} + +- (void)mockData:(NSData *)data response:(NSURLResponse *)response error:(NSError *)error { + _data = data; + _response = response; + _error = error; +} + +- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData + completionHandler:(void (^)(NSData *, NSURLResponse *, NSError *))completionHandler { + URLSessionUploadTaskMock *task = [[URLSessionUploadTaskMock alloc] init]; + task.mock = ^{ completionHandler(self->_data, self->_response, self->_error); }; + return task; +} + +@end + +#pragma mark - + +@implementation URLSessionUploadTaskMock + +- (void)resume { + self.mock(); +} + +@end From 06645e9747332667835337b078223d300a49659d Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 19 Nov 2020 13:36:58 +0000 Subject: [PATCH 04/11] Add Cucumber tests for delivery retry behaviour --- features/delivery.feature | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 features/delivery.feature diff --git a/features/delivery.feature b/features/delivery.feature new file mode 100644 index 000000000..20cfe4602 --- /dev/null +++ b/features/delivery.feature @@ -0,0 +1,23 @@ +Feature: Delivery of errors + + Background: + Given I clear all persistent data + + Scenario: Delivery is retried after an HTTP 500 error + When I set the HTTP status code for the next request to 500 + And I run "HandledExceptionScenario" + And I wait to receive a request + And I relaunch the app + And I clear the request queue + And I configure Bugsnag for "HandledExceptionScenario" + And I wait to receive a request + Then the request is valid for the error reporting API version "4.0" for the "iOS Bugsnag Notifier" notifier + + Scenario: Delivery is not retried after an HTTP 400 error + When I set the HTTP status code for the next request to 400 + And I run "HandledExceptionScenario" + And I wait to receive a request + And I relaunch the app + And I clear the request queue + And I configure Bugsnag for "HandledExceptionScenario" + Then I should receive no requests From a69490e8991791dee9e533121b62d6e3949ea2b9 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 19 Nov 2020 15:44:12 +0000 Subject: [PATCH 05/11] Add #895 to changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad764bb15..d815e310d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## TBD + +### Bug fixes + +* Error and session deliveries that fail with an HTTP error status code will now be retried if appropriate. + [#895](https://github.com/bugsnag/bugsnag-cocoa/pull/895) + ## 6.2.5 (2020-11-18) ### Bug fixes From 33f2a447ac50d3154f42a07931633e8bfec2d866 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 19 Nov 2020 16:49:21 +0000 Subject: [PATCH 06/11] Add BugsnagStackframe.type --- Bugsnag.xcodeproj/project.pbxproj | 2 + Bugsnag/Payload/BugsnagError.m | 8 +--- Bugsnag/Payload/BugsnagEvent.m | 11 ------ Bugsnag/Payload/BugsnagStackframe+Private.h | 26 ++++++++++++ Bugsnag/Payload/BugsnagStackframe.m | 6 ++- Bugsnag/Payload/BugsnagStacktrace.m | 10 +---- Bugsnag/Payload/BugsnagThread.m | 7 +--- Bugsnag/include/Bugsnag/BugsnagStackframe.h | 15 ++++++- Tests/BugsnagStackframeTest.m | 44 +++++++++++++++++---- 9 files changed, 90 insertions(+), 39 deletions(-) create mode 100644 Bugsnag/Payload/BugsnagStackframe+Private.h diff --git a/Bugsnag.xcodeproj/project.pbxproj b/Bugsnag.xcodeproj/project.pbxproj index 62ba98e1e..cf8dfac37 100644 --- a/Bugsnag.xcodeproj/project.pbxproj +++ b/Bugsnag.xcodeproj/project.pbxproj @@ -1274,6 +1274,7 @@ 00E636C02487031D006CBF1A /* Bugsnag.podspec.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Bugsnag.podspec.json; sourceTree = SOURCE_ROOT; }; 00E636C12487031D006CBF1A /* docker-compose.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = "docker-compose.yml"; sourceTree = SOURCE_ROOT; }; 00E636C324878FFC006CBF1A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0198762E2567D5AB000A7AF3 /* BugsnagStackframe+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagStackframe+Private.h"; sourceTree = ""; }; 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "report-react-native-promise-rejection.json"; sourceTree = ""; }; 01C17AE62542ED7F00C102C9 /* KSCrashReportWriterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSCrashReportWriterTests.m; sourceTree = ""; }; 3A700A8024A63A8E0068CD1B /* BugsnagThread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagThread.h; sourceTree = ""; }; @@ -1795,6 +1796,7 @@ 008968562486DA9400DC48C2 /* BugsnagSessionTrackingPayload.h */, 008968542486DA9400DC48C2 /* BugsnagSessionTrackingPayload.m */, 008968532486DA9400DC48C2 /* BugsnagStackframe.m */, + 0198762E2567D5AB000A7AF3 /* BugsnagStackframe+Private.h */, 0089684F2486DA9400DC48C2 /* BugsnagStacktrace.h */, 0089685C2486DA9500DC48C2 /* BugsnagStacktrace.m */, 008968552486DA9400DC48C2 /* BugsnagStateEvent.h */, diff --git a/Bugsnag/Payload/BugsnagError.m b/Bugsnag/Payload/BugsnagError.m index ba6a536da..9a8fc7ddd 100644 --- a/Bugsnag/Payload/BugsnagError.m +++ b/Bugsnag/Payload/BugsnagError.m @@ -7,8 +7,9 @@ // #import "BugsnagError.h" + #import "BugsnagKeys.h" -#import "BugsnagStackframe.h" +#import "BugsnagStackframe+Private.h" #import "BugsnagStacktrace.h" #import "BugsnagCollections.h" #import "RegisterErrorData.h" @@ -71,11 +72,6 @@ BSGErrorType BSGParseErrorType(NSString *errorType) { return error[BSGKeyReason] ?: @""; } -@interface BugsnagStackframe () -- (NSDictionary *)toDictionary; -+ (BugsnagStackframe *)frameFromJson:(NSDictionary *)json; -@end - @interface BugsnagStacktrace () @property NSMutableArray *trace; @end diff --git a/Bugsnag/Payload/BugsnagEvent.m b/Bugsnag/Payload/BugsnagEvent.m index fbbba4dfe..a8c3485e6 100644 --- a/Bugsnag/Payload/BugsnagEvent.m +++ b/Bugsnag/Payload/BugsnagEvent.m @@ -81,17 +81,6 @@ - (NSDictionary *)toDictionary; - (instancetype)deepCopy; @end -@interface BugsnagStackframe () -+ (BugsnagStackframe *)frameFromDict:(NSDictionary *)dict - withImages:(NSArray *)binaryImages; -@end - -@interface BugsnagStackframe () -+ (BugsnagStackframe *)frameFromDict:(NSDictionary *)dict - withImages:(NSArray *)binaryImages; -+ (BugsnagStackframe *)frameFromJson:(NSDictionary *)json; -@end - @interface BugsnagThread () @property BugsnagStacktrace *trace; - (NSDictionary *)toDictionary; diff --git a/Bugsnag/Payload/BugsnagStackframe+Private.h b/Bugsnag/Payload/BugsnagStackframe+Private.h new file mode 100644 index 000000000..2f3becb2c --- /dev/null +++ b/Bugsnag/Payload/BugsnagStackframe+Private.h @@ -0,0 +1,26 @@ +// +// BugsnagStackframe+Private.h +// Bugsnag +// +// Created by Nick Dowell on 20/11/2020. +// Copyright © 2020 Bugsnag Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface BugsnagStackframe () + +/// Constructs a stackframe object from a stackframe dictionary and list of images captured by KSCrash. ++ (nullable instancetype)frameFromDict:(NSDictionary *)dict withImages:(NSArray *> *)binaryImages; + +/// Constructs a stackframe object from a JSON object (typically loaded from disk.) ++ (instancetype)frameFromJson:(NSDictionary *)json; + +/// Returns a JSON compatible representation of the stackframe. +- (NSDictionary *)toDictionary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Payload/BugsnagStackframe.m b/Bugsnag/Payload/BugsnagStackframe.m index 87665a34e..a248b01a8 100644 --- a/Bugsnag/Payload/BugsnagStackframe.m +++ b/Bugsnag/Payload/BugsnagStackframe.m @@ -6,7 +6,7 @@ // Copyright © 2020 Bugsnag. All rights reserved. // -#import "BugsnagStackframe.h" +#import "BugsnagStackframe+Private.h" #import "BSG_KSBacktrace.h" #import "BSG_KSDynamicLinker.h" @@ -14,6 +14,8 @@ #import "BugsnagKeys.h" #import "BugsnagLogger.h" +BugsnagStackframeType const BugsnagStackframeTypeCocoa = @"cocoa"; + @implementation BugsnagStackframe + (NSDictionary *_Nullable)findImageAddr:(unsigned long)addr inImages:(NSArray *)images { @@ -36,6 +38,7 @@ + (BugsnagStackframe *)frameFromJson:(NSDictionary *)json { frame.frameAddress = [self readInt:json key:BSGKeyFrameAddress]; frame.symbolAddress = [self readInt:json key:BSGKeySymbolAddr]; frame.machoLoadAddress = [self readInt:json key:BSGKeyMachoLoadAddr]; + frame.type = json[BSGKeyType]; return frame; } @@ -183,6 +186,7 @@ - (NSDictionary *)toDictionary { if (self.isLr) { BSGDictSetSafeObject(dict, @(self.isLr), BSGKeyIsLR); } + dict[BSGKeyType] = self.type; return dict; } diff --git a/Bugsnag/Payload/BugsnagStacktrace.m b/Bugsnag/Payload/BugsnagStacktrace.m index f13fc9ead..106d4f5f0 100644 --- a/Bugsnag/Payload/BugsnagStacktrace.m +++ b/Bugsnag/Payload/BugsnagStacktrace.m @@ -7,15 +7,9 @@ // #import "BugsnagStacktrace.h" -#import "BugsnagStackframe.h" -#import "BugsnagKeys.h" -@interface BugsnagStackframe () -+ (BugsnagStackframe *)frameFromDict:(NSDictionary *)dict - withImages:(NSArray *)binaryImages; -- (NSDictionary *)toDictionary; -+ (instancetype)frameFromJson:(NSDictionary *)json; -@end +#import "BugsnagKeys.h" +#import "BugsnagStackframe+Private.h" @interface BugsnagStacktrace () @property NSMutableArray *trace; diff --git a/Bugsnag/Payload/BugsnagThread.m b/Bugsnag/Payload/BugsnagThread.m index 90e51e8fb..a9bf3da9b 100644 --- a/Bugsnag/Payload/BugsnagThread.m +++ b/Bugsnag/Payload/BugsnagThread.m @@ -7,8 +7,9 @@ // #import "BugsnagThread.h" + #import "BugsnagCollections.h" -#import "BugsnagStackframe.h" +#import "BugsnagStackframe+Private.h" #import "BugsnagStacktrace.h" #import "BugsnagKeys.h" @@ -29,10 +30,6 @@ + (instancetype)stacktraceFromJson:(NSDictionary *)json; @property NSMutableArray *trace; @end -@interface BugsnagStackframe () -- (NSDictionary *)toDictionary; -@end - @implementation BugsnagThread + (instancetype)threadFromJson:(NSDictionary *)json { diff --git a/Bugsnag/include/Bugsnag/BugsnagStackframe.h b/Bugsnag/include/Bugsnag/BugsnagStackframe.h index 9aefaba8d..9224ba3dc 100644 --- a/Bugsnag/include/Bugsnag/BugsnagStackframe.h +++ b/Bugsnag/include/Bugsnag/BugsnagStackframe.h @@ -8,6 +8,12 @@ #import +NS_ASSUME_NONNULL_BEGIN + +typedef NSString * BugsnagStackframeType NS_TYPED_ENUM; + +FOUNDATION_EXPORT BugsnagStackframeType const BugsnagStackframeTypeCocoa; + /** * Represents a single stackframe from a stacktrace. */ @@ -58,11 +64,18 @@ */ @property BOOL isLr; +/** + * The type of the stack frame, if it differs from that of the containing error or event. + */ +@property(nullable) BugsnagStackframeType type; + /** * Returns an array of stackframe objects representing the provided call stack strings. * * The call stack strings should follow the format used by `[NSThread callStackSymbols]` and `backtrace_symbols()`. */ -+ (NSArray *_Nullable)stackframesWithCallStackSymbols:(NSArray *_Nonnull)callStackSymbols; ++ (nullable NSArray *)stackframesWithCallStackSymbols:(NSArray *)callStackSymbols; @end + +NS_ASSUME_NONNULL_END diff --git a/Tests/BugsnagStackframeTest.m b/Tests/BugsnagStackframeTest.m index 3ceeb4e6a..a120496be 100644 --- a/Tests/BugsnagStackframeTest.m +++ b/Tests/BugsnagStackframeTest.m @@ -9,12 +9,7 @@ #import #import "BSG_KSMachHeaders.h" -#import "BugsnagStackframe.h" - -@interface BugsnagStackframe () -- (NSDictionary *)toDictionary; -+ (BugsnagStackframe *)frameFromDict:(NSDictionary *)dict withImages:(NSArray *)binaryImages; -@end +#import "BugsnagStackframe+Private.h" @interface BugsnagStackframeTest : XCTestCase @property NSDictionary *frameDict; @@ -48,6 +43,7 @@ - (void)testStackframeFromDict { XCTAssertEqualObjects(@0x10b574fa0, frame.symbolAddress); XCTAssertEqualObjects(@0x10b54b000, frame.machoLoadAddress); XCTAssertEqualObjects(@0x10b5756bf, frame.frameAddress); + XCTAssertNil(frame.type); XCTAssertFalse(frame.isPc); XCTAssertFalse(frame.isLr); } @@ -55,6 +51,7 @@ - (void)testStackframeFromDict { - (void)testStackframeToDict { BugsnagStackframe *frame = [BugsnagStackframe frameFromDict:self.frameDict withImages:self.binaryImages]; NSDictionary *dict = [frame toDictionary]; + XCTAssertTrue([NSJSONSerialization isValidJSONObject:dict]); XCTAssertEqualObjects(@"-[BugsnagClient notify:handledState:block:]", dict[@"method"]); XCTAssertEqualObjects(@"/Users/foo/Bugsnag.h", dict[@"machoFile"]); XCTAssertEqualObjects(@"B6D80CB5-A772-3D2F-B5A1-A3A137B8B58F", dict[@"machoUUID"]); @@ -62,10 +59,41 @@ - (void)testStackframeToDict { XCTAssertEqualObjects(@"0x10b574fa0", dict[@"symbolAddress"]); XCTAssertEqualObjects(@"0x10b54b000", dict[@"machoLoadAddress"]); XCTAssertEqualObjects(@"0x10b5756bf", dict[@"frameAddress"]); + XCTAssertNil(frame.type); XCTAssertNil(dict[@"isPC"]); XCTAssertNil(dict[@"isLR"]); } +- (void)testStackframeFromJson { + BugsnagStackframe *frame = [BugsnagStackframe frameFromJson:@{ + @"frameAddress": @"0x10b5756bf", + @"isLR": @NO, + @"isPC": @NO, + @"machoFile": @"/Users/foo/Bugsnag.h", + @"machoLoadAddress": @"0x10b54b000", + @"machoUUID": @"B6D80CB5-A772-3D2F-B5A1-A3A137B8B58F", + @"machoVMAddress": @"0x102340922", + @"method": @"-[BugsnagClient notify:handledState:block:]", + @"symbolAddress": @"0x10b574fa0", + @"type": @"cocoa", + }]; + XCTAssertEqual(frame.isLr, NO); + XCTAssertEqual(frame.isPc, NO); + XCTAssertEqualObjects(frame.frameAddress, @0x10b5756bf); + XCTAssertEqualObjects(frame.machoFile, @"/Users/foo/Bugsnag.h"); + XCTAssertEqualObjects(frame.machoLoadAddress, @0x10b54b000); + XCTAssertEqualObjects(frame.machoUuid, @"B6D80CB5-A772-3D2F-B5A1-A3A137B8B58F"); + XCTAssertEqualObjects(frame.machoVmAddress, @0x102340922); + XCTAssertEqualObjects(frame.method, @"-[BugsnagClient notify:handledState:block:]"); + XCTAssertEqualObjects(frame.symbolAddress, @0x10b574fa0); + XCTAssertEqualObjects(frame.type, BugsnagStackframeTypeCocoa); +} + +- (void)testStackframeFromJsonWithoutType { + BugsnagStackframe *frame = [BugsnagStackframe frameFromJson:@{}]; + XCTAssertNil(frame.type); +} + - (void)testStackframeToDictPcLr { BugsnagStackframe *frame = [BugsnagStackframe frameFromDict:self.frameDict withImages:self.binaryImages]; frame.isPc = true; @@ -98,7 +126,8 @@ - (void)testInvalidFrame { #define AssertStackframeValues(stackframe_, machoFile_, frameAddress_, method_) \ XCTAssertEqualObjects(stackframe_.method, method_); \ XCTAssertEqualObjects(stackframe_.machoFile, machoFile_); \ - XCTAssertEqualObjects(stackframe_.frameAddress, @(frameAddress_)); + XCTAssertEqualObjects(stackframe_.frameAddress, @(frameAddress_)); \ + XCTAssertNil(stackframe_.type); - (void)testDummyCallStackSymbols { bsg_mach_headers_initialize(); // Prevent symbolication @@ -174,6 +203,7 @@ - (void)testRealCallStackSymbols { XCTAssertNotNil(stackframe.machoVmAddress); XCTAssertNotNil(stackframe.machoLoadAddress); XCTAssertNotNil(stackframe.symbolAddress); + XCTAssertNil(stackframe.type); XCTAssertTrue([callStackSymbols[idx] containsString:stackframe.method]); }]; } From 65d98c64ba928c24c7ef900256fe40622f75bd03 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Mon, 23 Nov 2020 13:24:50 +0000 Subject: [PATCH 07/11] Add BugsnagHTTPHeaderName --- Bugsnag/BugsnagErrorReportSink.m | 2 +- Bugsnag/Configuration/BugsnagConfiguration.m | 17 ++++++++--------- Bugsnag/Delivery/BugsnagApiClient.h | 8 +++++++- Bugsnag/Delivery/BugsnagApiClient.m | 6 +++++- .../Delivery/BugsnagSessionTrackingApiClient.m | 6 +++--- Bugsnag/Helpers/BugsnagKeys.h | 3 --- Bugsnag/Helpers/BugsnagKeys.m | 3 --- 7 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Bugsnag/BugsnagErrorReportSink.m b/Bugsnag/BugsnagErrorReportSink.m index f098f6945..5d4567592 100644 --- a/Bugsnag/BugsnagErrorReportSink.m +++ b/Bugsnag/BugsnagErrorReportSink.m @@ -137,7 +137,7 @@ - (void)deliverStoredEvents:(NSMutableDictionary *)s NSDictionary *requestPayload = [self prepareEventPayload:event]; NSMutableDictionary *apiHeaders = [[configuration errorApiHeaders] mutableCopy]; - BSGDictSetSafeObject(apiHeaders, event.apiKey, BSGHeaderApiKey); + apiHeaders[BugsnagHTTPHeaderNameApiKey] = event.apiKey; [self.apiClient sendJSONPayload:requestPayload headers:apiHeaders toURL:configuration.notifyURL completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError *error) { BOOL completed = status == BugsnagApiClientDeliveryStatusDelivered || status == BugsnagApiClientDeliveryStatusUndeliverable; diff --git a/Bugsnag/Configuration/BugsnagConfiguration.m b/Bugsnag/Configuration/BugsnagConfiguration.m index c71e60c3d..ce4fbbc4f 100644 --- a/Bugsnag/Configuration/BugsnagConfiguration.m +++ b/Bugsnag/Configuration/BugsnagConfiguration.m @@ -27,6 +27,7 @@ #import "BugsnagConfiguration.h" #import "BSGConfigurationBuilder.h" +#import "BugsnagApiClient.h" #import "Private.h" static const int BSGApiKeyLength = 32; @@ -306,19 +307,17 @@ - (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block { } - (NSDictionary *)errorApiHeaders { - return @{ - BSGHeaderApiPayloadVersion: @"4.0", - BSGHeaderApiKey: self.apiKey, - BSGHeaderApiSentAt: [BSG_RFC3339DateTool stringFromDate:[NSDate new]] + return @{BugsnagHTTPHeaderNameApiKey: self.apiKey ?: @"", + BugsnagHTTPHeaderNamePayloadVersion: @"4.0", + BugsnagHTTPHeaderNameSentAt: [BSG_RFC3339DateTool stringFromDate:[NSDate date]] }; } - (NSDictionary *)sessionApiHeaders { - return @{ - BSGHeaderApiPayloadVersion: @"1.0", - BSGHeaderApiKey: self.apiKey, - BSGHeaderApiSentAt: [BSG_RFC3339DateTool stringFromDate:[NSDate new]] - }; + return @{BugsnagHTTPHeaderNameApiKey: self.apiKey ?: @"", + BugsnagHTTPHeaderNamePayloadVersion: @"1.0", + BugsnagHTTPHeaderNameSentAt: [BSG_RFC3339DateTool stringFromDate:[NSDate date]] + }; } - (void)setEndpoints:(BugsnagEndpointConfiguration *)endpoints { diff --git a/Bugsnag/Delivery/BugsnagApiClient.h b/Bugsnag/Delivery/BugsnagApiClient.h index 0e00dba98..18032ac65 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.h +++ b/Bugsnag/Delivery/BugsnagApiClient.h @@ -7,6 +7,12 @@ NS_ASSUME_NONNULL_BEGIN +typedef NSString * BugsnagHTTPHeaderName NS_TYPED_ENUM; + +extern BugsnagHTTPHeaderName const BugsnagHTTPHeaderNameApiKey; +extern BugsnagHTTPHeaderName const BugsnagHTTPHeaderNamePayloadVersion; +extern BugsnagHTTPHeaderName const BugsnagHTTPHeaderNameSentAt; + typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { /// The payload was delivered successfully and can be deleted. BugsnagApiClientDeliveryStatusDelivered, @@ -28,7 +34,7 @@ typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { - (NSOperation *)deliveryOperation; - (void)sendJSONPayload:(NSDictionary *)payload - headers:(NSDictionary *)headers + headers:(NSDictionary *)headers toURL:(NSURL *)url completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error))completionHandler; diff --git a/Bugsnag/Delivery/BugsnagApiClient.m b/Bugsnag/Delivery/BugsnagApiClient.m index eb3ca9f8a..38172c057 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.m +++ b/Bugsnag/Delivery/BugsnagApiClient.m @@ -11,6 +11,10 @@ #import "Private.h" #import "BSGJSONSerialization.h" +BugsnagHTTPHeaderName const BugsnagHTTPHeaderNameApiKey = @"Bugsnag-Api-Key"; +BugsnagHTTPHeaderName const BugsnagHTTPHeaderNamePayloadVersion = @"Bugsnag-Payload-Version"; +BugsnagHTTPHeaderName const BugsnagHTTPHeaderNameSentAt = @"Bugsnag-Sent-At"; + typedef NS_ENUM(NSInteger, HTTPStatusCode) { /// 402 Payment Required: a nonstandard client error status response code that is reserved for future use. /// @@ -63,7 +67,7 @@ - (NSOperation *)deliveryOperation { #pragma mark - Delivery - (void)sendJSONPayload:(NSDictionary *)payload - headers:(NSDictionary *)headers + headers:(NSDictionary *)headers toURL:(NSURL *)url completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error))completionHandler { diff --git a/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m b/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m index 720a9060c..374dc2e17 100644 --- a/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m +++ b/Bugsnag/Delivery/BugsnagSessionTrackingApiClient.m @@ -70,9 +70,9 @@ - (void)deliverSessionsInStore:(BugsnagSessionFileStore *)store { codeBundleId:self.codeBundleId]; NSMutableDictionary *data = [payload toJson]; NSDictionary *HTTPHeaders = @{ - @"Bugsnag-Payload-Version": @"1.0", - @"Bugsnag-API-Key": apiKey, - @"Bugsnag-Sent-At": [BSG_RFC3339DateTool stringFromDate:[NSDate new]] + BugsnagHTTPHeaderNameApiKey: apiKey ?: @"", + BugsnagHTTPHeaderNamePayloadVersion: @"1.0", + BugsnagHTTPHeaderNameSentAt: [BSG_RFC3339DateTool stringFromDate:[NSDate date]] }; [self sendJSONPayload:data headers:HTTPHeaders toURL:sessionURL completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError *error) { diff --git a/Bugsnag/Helpers/BugsnagKeys.h b/Bugsnag/Helpers/BugsnagKeys.h index 9901e59cc..bfa014f13 100644 --- a/Bugsnag/Helpers/BugsnagKeys.h +++ b/Bugsnag/Helpers/BugsnagKeys.h @@ -101,9 +101,6 @@ extern NSString *const BSGKeyUser; extern NSString *const BSGKeyUuid; extern NSString *const BSGKeyVersion; extern NSString *const BSGKeyWarning; -extern NSString *const BSGHeaderApiKey; -extern NSString *const BSGHeaderApiPayloadVersion; -extern NSString *const BSGHeaderApiSentAt; #define BSGKeyHwCputype "hw.cputype" #define BSGKeyHwCpusubtype "hw.cpusubtype" diff --git a/Bugsnag/Helpers/BugsnagKeys.m b/Bugsnag/Helpers/BugsnagKeys.m index 806c5c874..36119c9f9 100644 --- a/Bugsnag/Helpers/BugsnagKeys.m +++ b/Bugsnag/Helpers/BugsnagKeys.m @@ -97,6 +97,3 @@ NSString *const BSGKeyUuid = @"uuid"; NSString *const BSGKeyVersion = @"version"; NSString *const BSGKeyWarning = @"warning"; -NSString *const BSGHeaderApiKey = @"Bugsnag-Api-Key"; -NSString *const BSGHeaderApiPayloadVersion = @"Bugsnag-Payload-Version"; -NSString *const BSGHeaderApiSentAt = @"Bugsnag-Sent-At"; From 8fb9fb90af75667a68e80ae90a628bb9cc35dd92 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Mon, 23 Nov 2020 14:16:07 +0000 Subject: [PATCH 08/11] Use strongSelf in connectivity callback block --- Bugsnag/Client/BugsnagClient.m | 8 +++----- CHANGELOG.md | 3 +++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index 444c1da61..573cbdae8 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -823,11 +823,9 @@ - (void)setupConnectivityListener { [strongSelf flushPendingReports]; } - [self addAutoBreadcrumbOfType:BSGBreadcrumbTypeState - withMessage:@"Connectivity changed" - andMetadata:@{ - @"type" : connectionType - }]; + [strongSelf addAutoBreadcrumbOfType:BSGBreadcrumbTypeState + withMessage:@"Connectivity changed" + andMetadata:@{@"type": connectionType}]; }]; } diff --git a/CHANGELOG.md b/CHANGELOG.md index d815e310d..bdeb14b1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Changelog * Error and session deliveries that fail with an HTTP error status code will now be retried if appropriate. [#895](https://github.com/bugsnag/bugsnag-cocoa/pull/895) +* Fixed a rare crash in BugsnagClient's network connectivity observer block. + [#904](https://github.com/bugsnag/bugsnag-cocoa/pull/904) + ## 6.2.5 (2020-11-18) ### Bug fixes From 2392986640d0e26711bd521f4ff1bab190171796 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 24 Nov 2020 08:49:27 +0000 Subject: [PATCH 09/11] Add private headers for error, event & thread --- Bugsnag.xcodeproj/project.pbxproj | 6 + Bugsnag/Bugsnag.m | 4 - Bugsnag/BugsnagErrorReportSink.m | 15 +-- Bugsnag/Client/BugsnagClient.m | 74 ++++-------- .../Source/KSCrash/Recording/BSG_KSCrash.m | 9 +- Bugsnag/Payload/BugsnagError+Private.h | 34 ++++++ Bugsnag/Payload/BugsnagEvent+Private.h | 70 +++++++++++ Bugsnag/Payload/BugsnagEvent.m | 109 ++---------------- Bugsnag/Payload/BugsnagThread+Private.h | 40 +++++++ Tests/BugsnagErrorReportSinkTests.m | 15 +-- Tests/BugsnagErrorTest.m | 17 +-- Tests/BugsnagEventFromKSCrashReportTest.m | 12 +- Tests/BugsnagEventPersistLoadTest.m | 11 +- Tests/BugsnagEventTests.m | 22 +--- Tests/BugsnagMetadataRedactionTest.m | 7 +- Tests/BugsnagOnCrashTest.m | 5 +- Tests/BugsnagTests.m | 8 +- Tests/BugsnagThreadSerializationTest.m | 8 +- Tests/BugsnagThreadTest.m | 12 +- Tests/BugsnagUserTest.m | 10 +- Tests/EventApiValidationTest.m | 6 +- 21 files changed, 209 insertions(+), 285 deletions(-) create mode 100644 Bugsnag/Payload/BugsnagError+Private.h create mode 100644 Bugsnag/Payload/BugsnagEvent+Private.h create mode 100644 Bugsnag/Payload/BugsnagThread+Private.h diff --git a/Bugsnag.xcodeproj/project.pbxproj b/Bugsnag.xcodeproj/project.pbxproj index 3bda93e69..286b2d3dc 100644 --- a/Bugsnag.xcodeproj/project.pbxproj +++ b/Bugsnag.xcodeproj/project.pbxproj @@ -1279,6 +1279,9 @@ 00E636C02487031D006CBF1A /* Bugsnag.podspec.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Bugsnag.podspec.json; sourceTree = SOURCE_ROOT; }; 00E636C12487031D006CBF1A /* docker-compose.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = "docker-compose.yml"; sourceTree = SOURCE_ROOT; }; 00E636C324878FFC006CBF1A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0134524A256BCF7C0088C548 /* BugsnagError+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagError+Private.h"; sourceTree = ""; }; + 0134524B256BD00A0088C548 /* BugsnagThread+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagThread+Private.h"; sourceTree = ""; }; + 0195FC3B256BC81400DE6646 /* BugsnagEvent+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagEvent+Private.h"; sourceTree = ""; }; 0198762E2567D5AB000A7AF3 /* BugsnagStackframe+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagStackframe+Private.h"; sourceTree = ""; }; 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "report-react-native-promise-rejection.json"; sourceTree = ""; }; 01C17AE62542ED7F00C102C9 /* KSCrashReportWriterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSCrashReportWriterTests.m; sourceTree = ""; }; @@ -1795,7 +1798,9 @@ 008968482486DA9400DC48C2 /* BugsnagDevice.m */, 0089685A2486DA9500DC48C2 /* BugsnagDeviceWithState.m */, 008968512486DA9400DC48C2 /* BugsnagError.m */, + 0134524A256BCF7C0088C548 /* BugsnagError+Private.h */, 008968462486DA9300DC48C2 /* BugsnagEvent.m */, + 0195FC3B256BC81400DE6646 /* BugsnagEvent+Private.h */, 0089684E2486DA9400DC48C2 /* BugsnagHandledState.h */, 008968522486DA9400DC48C2 /* BugsnagHandledState.m */, 008968622486DA9500DC48C2 /* BugsnagNotifier.h */, @@ -1811,6 +1816,7 @@ 008968552486DA9400DC48C2 /* BugsnagStateEvent.h */, 008968582486DA9500DC48C2 /* BugsnagStateEvent.m */, 008968612486DA9500DC48C2 /* BugsnagThread.m */, + 0134524B256BD00A0088C548 /* BugsnagThread+Private.h */, 0089685F2486DA9500DC48C2 /* BugsnagUser.m */, ); path = Payload; diff --git a/Bugsnag/Bugsnag.m b/Bugsnag/Bugsnag.m index 6d69c3d07..4c6c07d21 100644 --- a/Bugsnag/Bugsnag.m +++ b/Bugsnag/Bugsnag.m @@ -52,10 +52,6 @@ @interface NSDictionary (BSGKSMerge) - (NSDictionary *)BSG_mergedInto:(NSDictionary *)dest; @end -@interface BugsnagEvent () -@property(readwrite) NSUInteger depth; -@end - @interface BugsnagClient () - (void)startListeningForStateChangeNotification:(NSString *_Nonnull)notificationName; - (void)addBreadcrumbWithBlock:(void (^_Nonnull)(BugsnagBreadcrumb *_Nonnull))block; diff --git a/Bugsnag/BugsnagErrorReportSink.m b/Bugsnag/BugsnagErrorReportSink.m index 5d4567592..a2d4d0cea 100644 --- a/Bugsnag/BugsnagErrorReportSink.m +++ b/Bugsnag/BugsnagErrorReportSink.m @@ -25,14 +25,16 @@ // #import "BugsnagErrorReportSink.h" + +#import "BSG_KSSystemInfo.h" #import "Bugsnag.h" -#import "BugsnagLogger.h" -#import "BugsnagCollections.h" #import "BugsnagClient.h" #import "BugsnagClientInternal.h" +#import "BugsnagCollections.h" +#import "BugsnagEvent+Private.h" #import "BugsnagKeys.h" +#import "BugsnagLogger.h" #import "BugsnagNotifier.h" -#import "BSG_KSSystemInfo.h" #import "Private.h" // This is private in Bugsnag, but really we want package private so define @@ -45,13 +47,6 @@ @interface BugsnagNotifier () - (NSDictionary *)toDict; @end -@interface BugsnagEvent () -- (NSDictionary *_Nonnull)toJson; -- (BOOL)shouldBeSent; -- (instancetype _Nonnull)initWithKSReport:(NSDictionary *_Nonnull)report; -@property NSSet *redactedKeys; -@end - @interface BugsnagConfiguration () @property(nonatomic, readwrite, strong) NSMutableArray *onSendBlocks; - (NSDictionary *_Nonnull)errorApiHeaders; diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index 573cbdae8..2fc44f720 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -28,35 +28,38 @@ #import "BugsnagClient.h" -#import "BugsnagBreadcrumbs.h" -#import "BugsnagClientInternal.h" #import "BSGConnectivity.h" -#import "Bugsnag.h" -#import "Private.h" -#import "BugsnagCrashSentry.h" -#import "BugsnagHandledState.h" -#import "BugsnagLogger.h" -#import "BugsnagKeys.h" -#import "BugsnagSessionTracker.h" -#import "BugsnagSessionTrackingApiClient.h" -#import "BugsnagPluginClient.h" -#import "BugsnagSystemState.h" -#import "BSG_RFC3339DateTool.h" +#import "BSGJSONSerialization.h" +#import "BSGSerialization.h" +#import "BSG_KSCrash.h" #import "BSG_KSCrashC.h" -#import "BSG_KSCrashType.h" +#import "BSG_KSCrashReport.h" #import "BSG_KSCrashState.h" -#import "BSG_KSSystemInfo.h" +#import "BSG_KSCrashType.h" #import "BSG_KSMach.h" -#import "BSGSerialization.h" +#import "BSG_KSSystemInfo.h" +#import "BSG_RFC3339DateTool.h" +#import "Bugsnag.h" #import "Bugsnag.h" +#import "BugsnagBreadcrumbs.h" +#import "BugsnagClientInternal.h" +#import "BugsnagCollections.h" +#import "BugsnagCrashSentry.h" +#import "BugsnagError+Private.h" #import "BugsnagErrorTypes.h" -#import "BugsnagNotifier.h" +#import "BugsnagEvent+Private.h" +#import "BugsnagHandledState.h" +#import "BugsnagKeys.h" +#import "BugsnagLogger.h" #import "BugsnagMetadataInternal.h" +#import "BugsnagNotifier.h" +#import "BugsnagPluginClient.h" +#import "BugsnagSessionTracker.h" +#import "BugsnagSessionTrackingApiClient.h" #import "BugsnagStateEvent.h" -#import "BugsnagCollections.h" -#import "BSG_KSCrashReport.h" -#import "BSG_KSCrash.h" -#import "BSGJSONSerialization.h" +#import "BugsnagSystemState.h" +#import "BugsnagThread+Private.h" +#import "Private.h" #if BSG_PLATFORM_IOS || BSG_PLATFORM_TVOS #define BSGOOMAvailable 1 @@ -121,10 +124,6 @@ @interface BugsnagSession () @property NSUInteger handledCount; @end -@interface BugsnagThread () -+ (NSMutableArray *)serializeThreads:(NSArray *)threads; -@end - @interface BugsnagAppWithState () + (BugsnagAppWithState *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config @@ -326,31 +325,6 @@ @interface BugsnagConfiguration () - (BOOL)shouldRecordBreadcrumbType:(BSGBreadcrumbType)type; @end -@interface BugsnagEvent () -@property(readonly, copy, nonnull) NSDictionary *overrides; -@property(readwrite) NSUInteger depth; -@property(readonly, nonnull) BugsnagHandledState *handledState; -@property (nonatomic, strong) BugsnagMetadata *metadata; -- (void)setOverrideProperty:(NSString *)key value:(id)value; -- (NSDictionary *)toJson; -- (instancetype)initWithApp:(BugsnagAppWithState *)app - device:(BugsnagDeviceWithState *)device - handledState:(BugsnagHandledState *)handledState - user:(BugsnagUser *)user - metadata:(BugsnagMetadata *)metadata - breadcrumbs:(NSArray *)breadcrumbs - errors:(NSArray *)errors - threads:(NSArray *)threads - session:(BugsnagSession *)session; -@end - -@interface BugsnagError () -- (instancetype)initWithErrorClass:(NSString *)errorClass - errorMessage:(NSString *)errorMessage - errorType:(BSGErrorType)errorType - stacktrace:(NSArray *)stacktrace; -@end - @interface BugsnagSessionTracker () @property(nonatomic) NSString *codeBundleId; @end diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m index 980078386..c12f34f22 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m @@ -36,7 +36,7 @@ //#define BSG_KSLogger_LocalLevel TRACE #import "BSG_KSLogger.h" -#import "BugsnagThread.h" +#import "BugsnagThread+Private.h" #import "BSGJSONSerialization.h" #import "BSGSerialization.h" #import "Bugsnag.h" @@ -74,13 +74,6 @@ #pragma mark - Globals - // ============================================================================ -@interface BugsnagThread () -+ (NSMutableArray *)threadsFromArray:(NSArray *)threads - binaryImages:(NSArray *)binaryImages - depth:(NSUInteger)depth - errorType:(NSString *)errorType; -@end - @interface BSG_KSCrash () @property(nonatomic, readwrite, retain) NSString *bundleName; diff --git a/Bugsnag/Payload/BugsnagError+Private.h b/Bugsnag/Payload/BugsnagError+Private.h new file mode 100644 index 000000000..c358f4643 --- /dev/null +++ b/Bugsnag/Payload/BugsnagError+Private.h @@ -0,0 +1,34 @@ +// +// BugsnagError+Private.h +// Bugsnag +// +// Created by Nick Dowell on 23/11/2020. +// Copyright © 2020 Bugsnag Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class BugsnagThread; + +@interface BugsnagError () + +- (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(nullable BugsnagThread *)thread; + +- (instancetype)initWithErrorClass:(NSString *)errorClass + errorMessage:(NSString *)errorMessage + errorType:(BSGErrorType)errorType + stacktrace:(nullable NSArray *)stacktrace; + ++ (BugsnagError *)errorFromJson:(NSDictionary *)json; + +- (NSDictionary *)toDictionary; + +@end + +BSGErrorType BSGParseErrorType(NSString *errorType); + +NSString *BSGSerializeErrorType(BSGErrorType errorType); + +NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Payload/BugsnagEvent+Private.h b/Bugsnag/Payload/BugsnagEvent+Private.h new file mode 100644 index 000000000..ef58e37e7 --- /dev/null +++ b/Bugsnag/Payload/BugsnagEvent+Private.h @@ -0,0 +1,70 @@ +// +// BugsnagEvent+Private.h +// Bugsnag +// +// Created by Nick Dowell on 23/11/2020. +// Copyright © 2020 Bugsnag Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface BugsnagEvent () + +@property (copy, nonatomic) NSString *codeBundleId; + +/// User-provided exception metadata. +@property (readwrite, copy, nullable, nonatomic) NSDictionary *customException; + +/// Number of frames to discard at the top of the generated stacktrace. Stacktraces from raised exceptions are unaffected. +@property NSUInteger depth; + +/// A unique hash identifying this device for the application or vendor. +@property (readwrite, copy, nullable, nonatomic) NSString *deviceAppHash; + +/// The release stages used to notify at the time this report is captured. +@property (readwrite, copy, nullable) NSArray *enabledReleaseStages; + +/// Raw error data added to metadata. +@property (readwrite, copy, nullable) NSDictionary *error; + +/// The event state (whether the error is handled/unhandled.) +@property (readonly) BugsnagHandledState *handledState; + +@property (strong, nonatomic) BugsnagMetadata *metadata; + +/// Property overrides. +@property (readonly, copy) NSDictionary *overrides; + +@property NSSet *redactedKeys; + +/// The release stage of the application +@property (readwrite, copy, nullable) NSString *releaseStage; + +@property (strong, nullable, nonatomic) BugsnagSession *session; + +- (instancetype)initWithApp:(nullable BugsnagAppWithState *)app + device:(nullable BugsnagDeviceWithState *)device + handledState:(BugsnagHandledState *)handledState + user:(nullable BugsnagUser *)user + metadata:(nullable BugsnagMetadata *)metadata + breadcrumbs:(NSArray *)breadcrumbs + errors:(NSArray *)errors + threads:(NSArray *)threads + session:(nullable BugsnagSession *)session; + +- (instancetype)initWithKSReport:(NSDictionary *)KSReport; + +- (instancetype)initWithUserData:(NSDictionary *)event; + +/// Whether this report should be sent, based on release stage information cached at crash time and within the application currently. +- (BOOL)shouldBeSent; + +- (void)setOverrideProperty:(NSString *)key value:(id)value; + +- (NSDictionary *)toJson; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Payload/BugsnagEvent.m b/Bugsnag/Payload/BugsnagEvent.m index a8c3485e6..bd08ec388 100644 --- a/Bugsnag/Payload/BugsnagEvent.m +++ b/Bugsnag/Payload/BugsnagEvent.m @@ -14,22 +14,25 @@ #endif #import + #import "BSGSerialization.h" +#import "BSG_KSCrashReportFields.h" +#import "BSG_RFC3339DateTool.h" #import "Bugsnag.h" #import "BugsnagBreadcrumbs.h" #import "BugsnagCollections.h" +#import "BugsnagError+Private.h" +#import "BugsnagEvent+Private.h" #import "BugsnagHandledState.h" -#import "BugsnagLogger.h" #import "BugsnagKeys.h" +#import "BugsnagLogger.h" #import "BugsnagSession.h" -#import "Private.h" -#import "BSG_RFC3339DateTool.h" -#import "BugsnagStacktrace.h" -#import "BugsnagThread.h" -#import "RegisterErrorData.h" #import "BugsnagSessionInternal.h" +#import "BugsnagStacktrace.h" +#import "BugsnagThread+Private.h" #import "BugsnagUser.h" -#import "BSG_KSCrashReportFields.h" +#import "Private.h" +#import "RegisterErrorData.h" static NSString *const DEFAULT_EXCEPTION_TYPE = @"cocoa"; @@ -81,46 +84,14 @@ - (NSDictionary *)toDictionary; - (instancetype)deepCopy; @end -@interface BugsnagThread () -@property BugsnagStacktrace *trace; -- (NSDictionary *)toDictionary; - -- (instancetype)initWithThread:(NSDictionary *)thread - binaryImages:(NSArray *)binaryImages; - -+ (NSMutableArray *)threadsFromArray:(NSArray *)threads - binaryImages:(NSArray *)binaryImages - depth:(NSUInteger)depth - errorType:(NSString *)errorType; - -+ (NSMutableArray *)serializeThreads:(NSArray *)threads; -@end - @interface BugsnagStacktrace () - (NSArray *)toArray; @end -@interface BugsnagThread () -- (instancetype)initWithThread:(NSDictionary *)thread - binaryImages:(NSArray *)binaryImages; - -+ (NSMutableArray *)threadsFromArray:(NSArray *)threads - binaryImages:(NSArray *)binaryImages - depth:(NSUInteger)depth - errorType:(NSString *)errorType; -+ (instancetype)threadFromJson:(NSDictionary *)json; -@end - @interface BugsnagStacktrace () - (NSArray *)toArray; @end -@interface BugsnagError () -- (NSDictionary *)toDictionary; -- (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(BugsnagThread *)thread; -+ (BugsnagError *)errorFromJson:(NSDictionary *)json; -@end - // MARK: - KSCrashReport parsing NSString *_Nonnull BSGParseErrorClass(NSDictionary *error, NSString *errorType); NSString *BSGParseErrorMessage(NSDictionary *report, NSDictionary *error, NSString *errorType); @@ -233,66 +204,6 @@ - (instancetype)initWithDictionary:(NSDictionary *)dict; - (instancetype)initWithUserId:(NSString *)userId name:(NSString *)name emailAddress:(NSString *)emailAddress; @end -@interface BugsnagEvent () - -/** - * A unique hash identifying this device for the application or vendor - */ -@property(nonatomic, readwrite, copy, nullable) NSString *deviceAppHash; -/** - * User-provided exception metadata - */ -@property(nonatomic, readwrite, copy, nullable) NSDictionary *customException; -@property(nonatomic, strong) BugsnagSession *session; - -/** - * The event state (whether the error is handled/unhandled) - */ -@property(readonly, nonnull) BugsnagHandledState *handledState; - -- (NSDictionary *_Nonnull)toJson; - -/** - * Whether this report should be sent, based on release stage information - * cached at crash time and within the application currently - * - * @return YES if the report should be sent - */ -- (BOOL)shouldBeSent; - -/** - * The release stages used to notify at the time this report is captured - */ -@property(readwrite, copy, nullable) NSArray *enabledReleaseStages; - -/** - * Property overrides - */ -@property(readonly, copy, nonnull) NSDictionary *overrides; - -/** - * Number of frames to discard at the top of the generated stacktrace. - * Stacktraces from raised exceptions are unaffected. - */ -@property(readwrite) NSUInteger depth; - -@property (nonatomic, strong) BugsnagMetadata *metadata; - -/** - * Raw error data added to metadata - */ -@property(readwrite, copy, nullable) NSDictionary *error; - -/** - * The release stage of the application - */ -@property(readwrite, copy, nullable) NSString *releaseStage; - -@property NSSet *redactedKeys; - -@property(nonatomic) NSString *codeBundleId; -@end - @implementation BugsnagEvent /** diff --git a/Bugsnag/Payload/BugsnagThread+Private.h b/Bugsnag/Payload/BugsnagThread+Private.h new file mode 100644 index 000000000..1caf2459d --- /dev/null +++ b/Bugsnag/Payload/BugsnagThread+Private.h @@ -0,0 +1,40 @@ +// +// BugsnagThread+Private.h +// Bugsnag +// +// Created by Nick Dowell on 23/11/2020. +// Copyright © 2020 Bugsnag Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface BugsnagThread () + +@property BugsnagStacktrace *trace; + +- (instancetype)initWithThread:(NSDictionary *)thread binaryImages:(NSArray *)binaryImages; + ++ (instancetype)threadFromJson:(NSDictionary *)json; + ++ (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread + depth:(NSUInteger)depth + errorType:(nullable NSString *)errorType; + ++ (NSMutableArray *)serializeThreads:(NSArray *)threads; + ++ (NSMutableArray *)threadsFromArray:(NSArray *)threads + binaryImages:(NSArray *)binaryImages + depth:(NSUInteger)depth + errorType:(nullable NSString *)errorType; + +- (NSDictionary *)toDictionary; + +@end + +BSGThreadType BSGParseThreadType(NSString *type); + +NSString *BSGSerializeThreadType(BSGThreadType type); + +NS_ASSUME_NONNULL_END diff --git a/Tests/BugsnagErrorReportSinkTests.m b/Tests/BugsnagErrorReportSinkTests.m index bae93a027..155768d90 100644 --- a/Tests/BugsnagErrorReportSinkTests.m +++ b/Tests/BugsnagErrorReportSinkTests.m @@ -14,6 +14,7 @@ #import "Bugsnag.h" #import "BugsnagHandledState.h" #import "BugsnagErrorReportSink.h" +#import "BugsnagEvent+Private.h" #import "BugsnagTestConstants.h" @interface BugsnagErrorReportSinkTests : XCTestCase @@ -29,20 +30,6 @@ @interface BugsnagClient () - (void)start; @end -@interface BugsnagEvent () -- (instancetype)initWithKSReport:(NSDictionary *)report; - -- (instancetype)initWithApp:(BugsnagAppWithState *)app - device:(BugsnagDeviceWithState *)device - handledState:(BugsnagHandledState *)handledState - user:(BugsnagUser *)user - metadata:(BugsnagMetadata *)metadata - breadcrumbs:(NSArray *)breadcrumbs - errors:(NSArray *)errors - threads:(NSArray *)threads - session:(BugsnagSession *)session; -@end - @interface BugsnagErrorReportSink () - (NSDictionary *)prepareEventPayload:(BugsnagEvent *)event; @end diff --git a/Tests/BugsnagErrorTest.m b/Tests/BugsnagErrorTest.m index 4de311819..2edcf0602 100644 --- a/Tests/BugsnagErrorTest.m +++ b/Tests/BugsnagErrorTest.m @@ -9,27 +9,14 @@ #import #import "BugsnagKeys.h" -#import "BugsnagError.h" +#import "BugsnagError+Private.h" #import "BugsnagStackframe.h" -#import "BugsnagThread.h" +#import "BugsnagThread+Private.h" NSString *_Nonnull BSGParseErrorClass(NSDictionary *error, NSString *errorType); NSString *BSGParseErrorMessage(NSDictionary *report, NSDictionary *error, NSString *errorType); -@interface BugsnagError () -- (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(BugsnagThread *)thread; - -- (NSDictionary *)toDictionary; -@end - -@interface BugsnagThread () -+ (NSMutableArray *)threadsFromArray:(NSArray *)threads - binaryImages:(NSArray *)binaryImages - depth:(NSUInteger)depth - errorType:(NSString *)errorType; -@end - @interface BugsnagErrorTest : XCTestCase @property NSDictionary *event; @end diff --git a/Tests/BugsnagEventFromKSCrashReportTest.m b/Tests/BugsnagEventFromKSCrashReportTest.m index 90b258a96..43548939e 100644 --- a/Tests/BugsnagEventFromKSCrashReportTest.m +++ b/Tests/BugsnagEventFromKSCrashReportTest.m @@ -7,7 +7,9 @@ // @import XCTest; -#import "Bugsnag.h" + +#import +#import "BugsnagEvent+Private.h" @interface Bugsnag () + (BugsnagConfiguration *)configuration; @@ -17,14 +19,6 @@ @interface BugsnagEventFromKSCrashReportTest : XCTestCase @property BugsnagEvent *event; @end -@interface BugsnagEvent () -- (NSDictionary *_Nonnull)toJson; -- (BOOL)shouldBeSent; -- (instancetype)initWithKSReport:(NSDictionary *)event; -@property(readwrite, copy, nullable) NSArray *enabledReleaseStages; -@property(readwrite) NSUInteger depth; -@end - @implementation BugsnagEventFromKSCrashReportTest - (void)setUp { diff --git a/Tests/BugsnagEventPersistLoadTest.m b/Tests/BugsnagEventPersistLoadTest.m index 83bcd9000..1c4d5adb8 100644 --- a/Tests/BugsnagEventPersistLoadTest.m +++ b/Tests/BugsnagEventPersistLoadTest.m @@ -7,7 +7,8 @@ // #import -#import "BugsnagEvent.h" + +#import "BugsnagEvent+Private.h" #import "BugsnagAppWithState.h" #import "BugsnagUser.h" #import "BugsnagDeviceWithState.h" @@ -16,18 +17,10 @@ #import "BugsnagBreadcrumb.h" #import "BugsnagHandledState.h" #import "Bugsnag.h" - #import "BugsnagError.h" #import "BugsnagStackframe.h" #import "BugsnagThread.h" -@interface BugsnagEvent () -- (instancetype)initWithKSReport:(NSDictionary *)report; -- (NSDictionary *)toJson; -@property(readonly, nonnull) BugsnagHandledState *handledState; -@property BugsnagSession *session; -@end - @interface BugsnagDeviceWithState () @property (nonatomic, readonly) NSDateFormatter *formatter; @end diff --git a/Tests/BugsnagEventTests.m b/Tests/BugsnagEventTests.m index f552a3f76..325b22576 100644 --- a/Tests/BugsnagEventTests.m +++ b/Tests/BugsnagEventTests.m @@ -11,6 +11,7 @@ #import "BSG_RFC3339DateTool.h" #import "Bugsnag.h" +#import "BugsnagEvent+Private.h" #import "BugsnagHandledState.h" #import "BugsnagSession.h" #import "BugsnagSessionInternal.h" @@ -32,27 +33,6 @@ @interface Bugsnag () + (BugsnagConfiguration *)configuration; @end -@interface BugsnagEvent () -- (NSDictionary *_Nonnull)toJson; -- (BOOL)shouldBeSent; -- (instancetype)initWithUserData:(NSDictionary *)event; -- (instancetype)initWithKSReport:(NSDictionary *)report; -- (instancetype)initWithApp:(BugsnagAppWithState *)app - device:(BugsnagDeviceWithState *)device - handledState:(BugsnagHandledState *)handledState - user:(BugsnagUser *)user - metadata:(BugsnagMetadata *)metadata - breadcrumbs:(NSArray *)breadcrumbs - errors:(NSArray *)errors - threads:(NSArray *)threads - session:(BugsnagSession *)session; -@property (nonatomic, strong) BugsnagMetadata *metadata; -@property(readwrite) NSUInteger depth; -@property NSString *releaseStage; -@property NSArray *enabledReleaseStages; -@property BugsnagSession *session; -@end - @interface BugsnagMetadata () - (NSDictionary *_Nonnull)toDictionary; @end diff --git a/Tests/BugsnagMetadataRedactionTest.m b/Tests/BugsnagMetadataRedactionTest.m index 95648206c..98ab7a37a 100644 --- a/Tests/BugsnagMetadataRedactionTest.m +++ b/Tests/BugsnagMetadataRedactionTest.m @@ -7,13 +7,8 @@ // #import -#import "BugsnagEvent.h" -@interface BugsnagEvent () -- (NSDictionary *)toJson; -- (instancetype)initWithKSReport:(NSDictionary *)report; -@property NSSet *redactedKeys; -@end +#import "BugsnagEvent+Private.h" @interface BugsnagMetadataRedactionTest : XCTestCase diff --git a/Tests/BugsnagOnCrashTest.m b/Tests/BugsnagOnCrashTest.m index 57fc17377..7dd48d8bb 100644 --- a/Tests/BugsnagOnCrashTest.m +++ b/Tests/BugsnagOnCrashTest.m @@ -9,10 +9,7 @@ #import #import - -@interface BugsnagEvent () -- (instancetype)initWithKSReport:(NSDictionary *)report; -@end +#import "BugsnagEvent+Private.h" @interface BugsnagOnCrashTest : XCTestCase diff --git a/Tests/BugsnagTests.m b/Tests/BugsnagTests.m index ab3e42f82..44fb90701 100644 --- a/Tests/BugsnagTests.m +++ b/Tests/BugsnagTests.m @@ -7,11 +7,13 @@ // // Unit tests of global Bugsnag behaviour +#import + #import "Bugsnag.h" #import "BugsnagClient.h" +#import "BugsnagEvent+Private.h" #import "BugsnagTestConstants.h" #import "BugsnagNotifier.h" -#import // MARK: - BugsnagTests @@ -32,10 +34,6 @@ - (void)start; @property BugsnagConfiguration *configuration; @end -@interface BugsnagEvent () -@property (nonatomic, strong) BugsnagMetadata *metadata; -@end - @interface BugsnagTests : XCTestCase @end diff --git a/Tests/BugsnagThreadSerializationTest.m b/Tests/BugsnagThreadSerializationTest.m index 72c1a1b95..94bc76bd3 100644 --- a/Tests/BugsnagThreadSerializationTest.m +++ b/Tests/BugsnagThreadSerializationTest.m @@ -3,19 +3,13 @@ // Copyright (c) 2018 Bugsnag. All rights reserved. // -#import #import -#import "BugsnagEvent.h" +#import "BugsnagEvent+Private.h" @interface BugsnagThreadSerializationTest : XCTestCase @end -@interface BugsnagEvent () -- (NSDictionary *)toJson; -- (instancetype)initWithKSReport:(NSDictionary *)report; -@end - @implementation BugsnagThreadSerializationTest - (void)testEmptyThreads { diff --git a/Tests/BugsnagThreadTest.m b/Tests/BugsnagThreadTest.m index ca3cd055d..236ba94d8 100644 --- a/Tests/BugsnagThreadTest.m +++ b/Tests/BugsnagThreadTest.m @@ -9,17 +9,7 @@ #import #import "BugsnagStackframe.h" -#import "BugsnagThread.h" - -@interface BugsnagThread () -- (NSDictionary *)toDictionary; - -- (instancetype)initWithThread:(NSDictionary *)thread binaryImages:(NSArray *)binaryImages; - -+ (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread - depth:(NSUInteger)depth - errorType:(NSString *)errorType; -@end +#import "BugsnagThread+Private.h" @interface BugsnagThreadTest : XCTestCase @property NSArray *binaryImages; diff --git a/Tests/BugsnagUserTest.m b/Tests/BugsnagUserTest.m index 17ce07c88..92879c518 100644 --- a/Tests/BugsnagUserTest.m +++ b/Tests/BugsnagUserTest.m @@ -9,11 +9,7 @@ #import #import "BugsnagUser.h" -#import "BugsnagEvent.h" - -@interface BugsnagEvent () -- (instancetype)initWithKSReport:(NSDictionary *)report; -@end +#import "BugsnagEvent+Private.h" @interface BugsnagUser () - (instancetype)initWithDictionary:(NSDictionary *)dict; @@ -21,10 +17,6 @@ - (instancetype)initWithUserId:(NSString *)userId name:(NSString *)name emailAdd - (NSDictionary *)toJson; @end -@interface BugsnagEvent () -- (instancetype)initWithKSReport:(NSDictionary *)report; -@end - @interface BugsnagUserTest : XCTestCase @end diff --git a/Tests/EventApiValidationTest.m b/Tests/EventApiValidationTest.m index bd2ba1541..164093a9a 100644 --- a/Tests/EventApiValidationTest.m +++ b/Tests/EventApiValidationTest.m @@ -7,11 +7,9 @@ // #import -#import -@interface BugsnagEvent () -- (instancetype)initWithKSReport:(NSDictionary *)event; -@end +#import +#import "BugsnagEvent+Private.h" /** * Validates that the Event API interface handles any invalid input gracefully. From 0d2b13831119de87f3425a12f73099049734c27f Mon Sep 17 00:00:00 2001 From: Karl Stenerud Date: Tue, 24 Nov 2020 16:57:51 +0100 Subject: [PATCH 10/11] We know the state file won't be present on first launch, so don't report an error if it's not found --- Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m index 410f6ff21..2c74c4429 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashState.m @@ -180,7 +180,9 @@ bool bsg_kscrashstate_i_loadState(BSG_KSCrash_State *const context, NSError *error = nil; NSData *data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:path] options:0 error:&error]; if (error != nil) { - BSG_KSLOG_ERROR(@"%s: Could not load file: %@", path, error); + if (!(error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError)) { + BSG_KSLOG_ERROR(@"%s: Could not load file: %@", path, error); + } return false; } id objectContext = [BSG_KSJSONCodec decode:data options:0 error:&error]; From 11c2c539be895c6abced92a0a9fe4aeb218a3ce1 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Wed, 25 Nov 2020 14:25:18 +0000 Subject: [PATCH 11/11] Release 6.2.6 --- Bugsnag.podspec.json | 4 ++-- Bugsnag/Payload/BugsnagNotifier.m | 2 +- CHANGELOG.md | 2 +- Framework/Info.plist | 2 +- Tests/Info.plist | 2 +- VERSION | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Bugsnag.podspec.json b/Bugsnag.podspec.json index 54121f2cd..038923d91 100644 --- a/Bugsnag.podspec.json +++ b/Bugsnag.podspec.json @@ -1,6 +1,6 @@ { "name": "Bugsnag", - "version": "6.2.5", + "version": "6.2.6", "summary": "The Bugsnag crash reporting framework for Apple platforms.", "homepage": "https://bugsnag.com", "license": "MIT", @@ -9,7 +9,7 @@ }, "source": { "git": "https://github.com/bugsnag/bugsnag-cocoa.git", - "tag": "v6.2.5" + "tag": "v6.2.6" }, "frameworks": [ "Foundation", diff --git a/Bugsnag/Payload/BugsnagNotifier.m b/Bugsnag/Payload/BugsnagNotifier.m index 5300f336d..0069b9a20 100644 --- a/Bugsnag/Payload/BugsnagNotifier.m +++ b/Bugsnag/Payload/BugsnagNotifier.m @@ -23,7 +23,7 @@ - (instancetype)init { #else self.name = @"Bugsnag Objective-C"; #endif - self.version = @"6.2.5"; + self.version = @"6.2.6"; self.url = @"https://github.com/bugsnag/bugsnag-cocoa"; self.dependencies = [NSMutableArray new]; } diff --git a/CHANGELOG.md b/CHANGELOG.md index bdeb14b1a..bd708e439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Changelog ========= -## TBD +## 6.2.6 (2020-11-25) ### Bug fixes diff --git a/Framework/Info.plist b/Framework/Info.plist index 25d57e29c..c057269ce 100644 --- a/Framework/Info.plist +++ b/Framework/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.2.5 + 6.2.6 CFBundleVersion 1 diff --git a/Tests/Info.plist b/Tests/Info.plist index 493ef72f0..ff41daf62 100644 --- a/Tests/Info.plist +++ b/Tests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 6.2.5 + 6.2.6 CFBundleVersion 1 diff --git a/VERSION b/VERSION index a6534bb33..09d22fa51 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.2.5 +6.2.6