From 9083917a4eac58e8efd008ddd3ac5b3a0b83047d Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 21 Jul 2022 08:33:37 +0100 Subject: [PATCH 01/24] Use `device.id` as fallback `user.id` for events & sessions (#1442) --- Bugsnag/BugsnagSessionTracker.m | 11 ++++---- Bugsnag/Client/BugsnagClient.m | 6 ++-- Bugsnag/Configuration/BugsnagConfiguration.m | 13 ++------- Bugsnag/Payload/BugsnagEvent.m | 2 +- Bugsnag/Payload/BugsnagSession.m | 2 +- Bugsnag/Payload/BugsnagUser+Private.h | 5 +++- Bugsnag/Payload/BugsnagUser.m | 23 +++++++++------ Bugsnag/include/Bugsnag/Bugsnag.h | 2 ++ .../include/Bugsnag/BugsnagConfiguration.h | 2 ++ CHANGELOG.md | 8 ++++++ Tests/BugsnagTests/BugsnagSessionTest.m | 2 +- Tests/BugsnagTests/BugsnagUserTest.m | 2 +- .../ios/iOSTestApp.xcodeproj/project.pbxproj | 4 +++ .../macOSTestApp.xcodeproj/project.pbxproj | 6 +++- .../UnhandledMachExceptionScenario.m | 1 + .../shared/scenarios/UserNilScenario.swift | 28 +++++++++++++++++++ features/unhandled_mach_exception.feature | 1 + features/user.feature | 21 ++++++++++++-- 18 files changed, 104 insertions(+), 35 deletions(-) create mode 100644 features/fixtures/shared/scenarios/UserNilScenario.swift diff --git a/Bugsnag/BugsnagSessionTracker.m b/Bugsnag/BugsnagSessionTracker.m index 5e1f1ce69..6d2c7a8ec 100644 --- a/Bugsnag/BugsnagSessionTracker.m +++ b/Bugsnag/BugsnagSessionTracker.m @@ -8,7 +8,11 @@ #import "BugsnagSessionTracker.h" +#import "BSGAppKit.h" +#import "BSGDefines.h" #import "BSGSessionUploader.h" +#import "BSGUIKit.h" +#import "BSGWatchKit.h" #import "BSG_KSSystemInfo.h" #import "BugsnagApp+Private.h" #import "BugsnagClient+Private.h" @@ -17,10 +21,7 @@ #import "BugsnagDevice+Private.h" #import "BugsnagLogger.h" #import "BugsnagSession+Private.h" -#import "BSGDefines.h" -#import "BSGAppKit.h" -#import "BSGWatchKit.h" -#import "BSGUIKit.h" +#import "BugsnagUser+Private.h" /** Number of seconds in background required to make a new session @@ -174,7 +175,7 @@ - (void)startNewSession { BugsnagSession *newSession = [[BugsnagSession alloc] initWithId:[[NSUUID UUID] UUIDString] startedAt:[NSDate date] - user:self.client.user + user:[self.client.user withId] app:app device:device]; diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index 77e5bff64..de8f2f219 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -682,7 +682,7 @@ - (void)notifyErrorOrException:(id)errorOrException block:(BugsnagOnErrorBlock)b BugsnagEvent *event = [[BugsnagEvent alloc] initWithApp:[self generateAppWithState:systemInfo] device:[self generateDeviceWithState:systemInfo] handledState:handledState - user:self.user + user:[self.user withId] metadata:metadata breadcrumbs:self.breadcrumbs.breadcrumbs ?: @[] errors:@[error] @@ -725,6 +725,8 @@ - (void)notifyInternal:(BugsnagEvent *_Nonnull)event } } + event.user = [event.user withId]; + BOOL originalUnhandledValue = event.unhandled; @try { if (block != nil && !block(event)) { // skip notifying if callback false @@ -1041,7 +1043,7 @@ - (void)appHangDetectedAtDate:(NSDate *)date withThreads:(NSArray BugsnagOnSessionRef; * @param userId ID of the user * @param name Name of the user * @param email Email address of the user + * + * If user ID is nil, a Bugsnag-generated Device ID is used for the `user.id` property of events and sessions. */ - (void)setUser:(NSString *_Nullable)userId withEmail:(NSString *_Nullable)email diff --git a/CHANGELOG.md b/CHANGELOG.md index fc2f343f3..80663f2e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Changelog ========= +## TBD + +### Bug fixes + +* Set `user.id` to to `device.id` for all events and sessions if `BugsnagClient.user.id` is set to nil. + To prevent collection, set it to an empty string or update it in `OnSendError` / `OnSession`. + [#1442](https://github.com/bugsnag/bugsnag-cocoa/pull/1442) + ## 6.21.0 (2022-07-20) ### Enhancements diff --git a/Tests/BugsnagTests/BugsnagSessionTest.m b/Tests/BugsnagTests/BugsnagSessionTest.m index ab6f0f8a7..4dc991b8f 100644 --- a/Tests/BugsnagTests/BugsnagSessionTest.m +++ b/Tests/BugsnagTests/BugsnagSessionTest.m @@ -108,7 +108,7 @@ - (NSDictionary *)generateSerializedSession { - (void)testPayloadSerialization { NSDate *now = [NSDate date]; - BugsnagUser *user = [[BugsnagUser alloc] initWithUserId:@"123" name:@"Joe Bloggs" emailAddress:@"joe@example.com"]; + BugsnagUser *user = [[BugsnagUser alloc] initWithId:@"123" name:@"Joe Bloggs" emailAddress:@"joe@example.com"]; BugsnagSession *payload = [[BugsnagSession alloc] initWithId:@"test" startedAt:now user:user diff --git a/Tests/BugsnagTests/BugsnagUserTest.m b/Tests/BugsnagTests/BugsnagUserTest.m index f0b593aa7..3bf3e458a 100644 --- a/Tests/BugsnagTests/BugsnagUserTest.m +++ b/Tests/BugsnagTests/BugsnagUserTest.m @@ -32,7 +32,7 @@ - (void)testDictDeserialisation { } - (void)testPayloadSerialisation { - BugsnagUser *payload = [[BugsnagUser alloc] initWithUserId:@"test" name:@"Tom Bombadil" emailAddress:@"fake@example.com"]; + BugsnagUser *payload = [[BugsnagUser alloc] initWithId:@"test" name:@"Tom Bombadil" emailAddress:@"fake@example.com"]; NSDictionary *rootNode = [payload toJson]; XCTAssertNotNil(rootNode); XCTAssertEqual(3, [rootNode count]); diff --git a/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj b/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj index c4d3ca4c1..94be40539 100644 --- a/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj @@ -57,6 +57,7 @@ 010BAB3F2833D29A0003FF36 /* EnabledReleaseStageManualSessionScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010BAB3E2833D29A0003FF36 /* EnabledReleaseStageManualSessionScenario.swift */; }; 010BAB412833D2AA0003FF36 /* DisabledReleaseStageAutoSessionScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010BAB402833D2AA0003FF36 /* DisabledReleaseStageAutoSessionScenario.swift */; }; 010BDFB92885562D007025F9 /* ReportBackgroundAppHangScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010BDFB82885562D007025F9 /* ReportBackgroundAppHangScenario.swift */; }; + 010BDFBD28883714007025F9 /* UserNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010BDFBC28883714007025F9 /* UserNilScenario.swift */; }; 01221E55282E5538008095C3 /* MaxPersistedSessionsScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 01221E54282E5538008095C3 /* MaxPersistedSessionsScenario.m */; }; 0163BFA72583B3CF008DC28B /* DiscardClassesHandledExceptionRegexScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0163BFA62583B3CF008DC28B /* DiscardClassesHandledExceptionRegexScenario.swift */; }; 017B4134276B8D9B0054C91D /* OnSendErrorPersistenceScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 017B4133276B8D9B0054C91D /* OnSendErrorPersistenceScenario.m */; }; @@ -247,6 +248,7 @@ 010BAB3E2833D29A0003FF36 /* EnabledReleaseStageManualSessionScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnabledReleaseStageManualSessionScenario.swift; sourceTree = ""; }; 010BAB402833D2AA0003FF36 /* DisabledReleaseStageAutoSessionScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisabledReleaseStageAutoSessionScenario.swift; sourceTree = ""; }; 010BDFB82885562D007025F9 /* ReportBackgroundAppHangScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportBackgroundAppHangScenario.swift; sourceTree = ""; }; + 010BDFBC28883714007025F9 /* UserNilScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNilScenario.swift; sourceTree = ""; }; 01221E54282E5538008095C3 /* MaxPersistedSessionsScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MaxPersistedSessionsScenario.m; sourceTree = ""; }; 0163BFA62583B3CF008DC28B /* DiscardClassesHandledExceptionRegexScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscardClassesHandledExceptionRegexScenario.swift; sourceTree = ""; }; 017B4133276B8D9B0054C91D /* OnSendErrorPersistenceScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OnSendErrorPersistenceScenario.m; sourceTree = ""; }; @@ -629,6 +631,7 @@ E700EE4D247D1317008CFFB6 /* UserFromClientScenario.swift */, AA6ACD1B2773E0B3006464C4 /* UserFromConfigScenario.swift */, AA6ACD1F2773E3F0006464C4 /* UserInfoScenario.swift */, + 010BDFBC28883714007025F9 /* UserNilScenario.swift */, 010BAAF12833CE570003FF36 /* UserPersistenceDontPersistUserScenario.m */, 010BAAFE2833CE570003FF36 /* UserPersistenceNoUserScenario.m */, 010BAAF22833CE570003FF36 /* UserPersistencePersistUserClientScenario.m */, @@ -854,6 +857,7 @@ 001E5502243B8FDA0009E31D /* AutoCaptureRunScenario.m in Sources */, 0104085F258CA0A100933C60 /* DispatchCrashScenario.swift in Sources */, E700EE55247D3204008CFFB6 /* OnSendOverwriteScenario.swift in Sources */, + 010BDFBD28883714007025F9 /* UserNilScenario.swift in Sources */, CB0AE1F1287D89C90079B28E /* OnSendErrorCallbackFeatureFlagsScenario.swift in Sources */, F429538D8941382EC2C857CE /* AsyncSafeThreadScenario.m in Sources */, F42955869D33EE0E510B9651 /* ReadGarbagePointerScenario.m in Sources */, diff --git a/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj b/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj index df5c27ad9..e0faffbb7 100644 --- a/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj +++ b/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 010BAB772833D34A0003FF36 /* DiscardClassesHandledExceptionRegexScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010BAB5B2833D34A0003FF36 /* DiscardClassesHandledExceptionRegexScenario.swift */; }; 010BAB782833D34A0003FF36 /* EnabledReleaseStageManualSessionScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010BAB5C2833D34A0003FF36 /* EnabledReleaseStageManualSessionScenario.swift */; }; 010BAB792833D34A0003FF36 /* HandledErrorInvalidReleaseStageScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010BAB5D2833D34A0003FF36 /* HandledErrorInvalidReleaseStageScenario.swift */; }; + 010BDFBB28883704007025F9 /* UserNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010BDFBA28883703007025F9 /* UserNilScenario.swift */; }; 011B7A4C26CD52080071C3EB /* ThermalStateBreadcrumbScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011B7A4B26CD52080071C3EB /* ThermalStateBreadcrumbScenario.swift */; }; 0123189C275921590007EFD7 /* RecrashScenarios.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0123189B275921590007EFD7 /* RecrashScenarios.mm */; }; 015AE23A276B88300044B1AE /* OnSendErrorPersistenceScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 015AE239276B88300044B1AE /* OnSendErrorPersistenceScenario.m */; }; @@ -230,6 +231,7 @@ 010BAB5B2833D34A0003FF36 /* DiscardClassesHandledExceptionRegexScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscardClassesHandledExceptionRegexScenario.swift; sourceTree = ""; }; 010BAB5C2833D34A0003FF36 /* EnabledReleaseStageManualSessionScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnabledReleaseStageManualSessionScenario.swift; sourceTree = ""; }; 010BAB5D2833D34A0003FF36 /* HandledErrorInvalidReleaseStageScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HandledErrorInvalidReleaseStageScenario.swift; sourceTree = ""; }; + 010BDFBA28883703007025F9 /* UserNilScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNilScenario.swift; sourceTree = ""; }; 011B7A4B26CD52080071C3EB /* ThermalStateBreadcrumbScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermalStateBreadcrumbScenario.swift; sourceTree = ""; }; 0123189B275921590007EFD7 /* RecrashScenarios.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RecrashScenarios.mm; sourceTree = ""; }; 01452360254AFEA700D436AA /* macOSTestApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "macOSTestApp-Bridging-Header.h"; sourceTree = ""; }; @@ -515,10 +517,10 @@ 01F47CBA254B1B3000B184AD /* OnErrorOverwriteScenario.swift */, 01ECBCF225A7522000FC0678 /* OnErrorOverwriteUnhandledFalseScenario.swift */, 01ECBCF325A7522000FC0678 /* OnErrorOverwriteUnhandledTrueScenario.swift */, - CB0AE1F2287DBA380079B28E /* OnSendErrorCallbackFeatureFlagsScenario.swift */, 01F47C30254B1B2D00B184AD /* OnSendCallbackOrderScenario.swift */, 01F47C9B254B1B2F00B184AD /* OnSendCallbackRemovalScenario.m */, 01F47C76254B1B2E00B184AD /* OnSendErrorCallbackCrashScenario.swift */, + CB0AE1F2287DBA380079B28E /* OnSendErrorCallbackFeatureFlagsScenario.swift */, 015AE239276B88300044B1AE /* OnSendErrorPersistenceScenario.m */, 01F47C6C254B1B2E00B184AD /* OnSendOverwriteScenario.swift */, 01F47C41254B1B2D00B184AD /* OOMAutoDetectErrorsScenario.swift */, @@ -579,6 +581,7 @@ 01F47CA3254B1B3000B184AD /* UserFromClientScenario.swift */, AA6ACD192773E098006464C4 /* UserFromConfigScenario.swift */, AA6ACD1D2773E39C006464C4 /* UserInfoScenario.swift */, + 010BDFBA28883703007025F9 /* UserNilScenario.swift */, 017D9CF52833C81100B0AA87 /* UserPersistenceDontPersistUserScenario.m */, 017D9CF12833C81100B0AA87 /* UserPersistenceNoUserScenario.m */, 017D9CF02833C81100B0AA87 /* UserPersistencePersistUserClientScenario.m */, @@ -881,6 +884,7 @@ 010BAB712833D34A0003FF36 /* AppAndDeviceAttributesCallbackOverrideScenario.swift in Sources */, 010BAB6B2833D34A0003FF36 /* UnhandledErrorInvalidReleaseStageScenario.swift in Sources */, 010BAB652833D34A0003FF36 /* AppHangDisabledScenario.swift in Sources */, + 010BDFBB28883704007025F9 /* UserNilScenario.swift in Sources */, 0176C0AE254AE81B0066E0F3 /* main.m in Sources */, 01F47CDB254B1B3100B184AD /* UnhandledMachExceptionScenario.m in Sources */, 01F47D12254B1B3100B184AD /* OnSendCallbackRemovalScenario.m in Sources */, diff --git a/features/fixtures/shared/scenarios/UnhandledMachExceptionScenario.m b/features/fixtures/shared/scenarios/UnhandledMachExceptionScenario.m index ed072f763..b2d958886 100644 --- a/features/fixtures/shared/scenarios/UnhandledMachExceptionScenario.m +++ b/features/fixtures/shared/scenarios/UnhandledMachExceptionScenario.m @@ -19,6 +19,7 @@ - (void)startBugsnag { } - (void)run { + [Bugsnag setUser:nil withEmail:nil andName:nil]; void (*ptr)(void) = (void *)0xDEADBEEF; ptr(); } diff --git a/features/fixtures/shared/scenarios/UserNilScenario.swift b/features/fixtures/shared/scenarios/UserNilScenario.swift new file mode 100644 index 000000000..d66a1ba93 --- /dev/null +++ b/features/fixtures/shared/scenarios/UserNilScenario.swift @@ -0,0 +1,28 @@ +class UserNilScenario: Scenario { + + override func startBugsnag() { + self.config.autoTrackSessions = false; + super.startBugsnag() + } + + override func run() { + Bugsnag.setUser(nil, withEmail: nil, andName: nil) + + // This session should have a non-nil user.id + Bugsnag.startSession() + + Bugsnag.addOnSession { session in + session.setUser(nil, withEmail: nil, andName: nil) + return true + } + + // This session should have a nil user.id, due to the OnSession block + Bugsnag.startSession() + + Bugsnag.notifyError(NSError(domain: "ErrorWithCallback", code: 100)) { event in + // This error should have a nil user.id + event.setUser(nil, withEmail: nil, andName: nil) + return true + } + } +} diff --git a/features/unhandled_mach_exception.feature b/features/unhandled_mach_exception.feature index 1575adb08..684642b6c 100644 --- a/features/unhandled_mach_exception.feature +++ b/features/unhandled_mach_exception.feature @@ -23,6 +23,7 @@ Feature: Bugsnag captures an unhandled mach exception And the event "metaData.error.mach.subcode" equals "0xdeadbeef" And the event "severity" equals "error" And the event "unhandled" is true + And the event "user.id" is not null And the event "severityReason.type" equals "unhandledException" Scenario: Trigger a mach exception with unhandled override diff --git a/features/user.feature b/features/user.feature index 25b2d24ef..68c50fb77 100644 --- a/features/user.feature +++ b/features/user.feature @@ -16,7 +16,8 @@ Feature: Reporting User Information # User fields set as null Then the error is valid for the error reporting API And the exception "message" equals "The operation couldn’t be completed. (UserDisabled error 100.)" - And the event "user.id" is null + And the error payload field "events.0.device.id" is stored as the value "device_id" + And the error payload field "events.0.user.id" equals the stored value "device_id" And the event "user.email" is null And the event "user.name" is null And I discard the oldest error @@ -24,7 +25,7 @@ Feature: Reporting User Information # Only User email field set Then the error is valid for the error reporting API And the exception "message" equals "The operation couldn’t be completed. (UserEmail error 100.)" - And the event "user.id" is null + And the error payload field "events.0.user.id" equals the stored value "device_id" And the event "user.email" equals "user@example.com" And the event "user.name" is null And I discard the oldest error @@ -74,3 +75,19 @@ Feature: Reporting User Information And the session "user.id" equals "def" And the session "user.email" equals "sue@gmail.com" And the session "user.name" equals "Sue" + + Scenario: Setting the user ID to nil + When I run "UserNilScenario" + And I wait to receive 2 sessions + And I wait to receive an error + Then the session payload field "device.id" is stored as the value "device_id" + And the session payload field "sessions.0.user.id" equals the stored value "device_id" + And the session "user.email" is null + And the session "user.name" is null + And I discard the oldest session + And the session "user.id" is null + And the session "user.email" is null + And the session "user.name" is null + And the event "user.id" is null + And the event "user.email" is null + And the event "user.name" is null From 24eb1a4b3b040ae9de3df080887541e1ce038f68 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 21 Jul 2022 08:48:49 +0100 Subject: [PATCH 02/24] Use __unused instead of __attribute__((unused)) --- Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m | 6 +++--- Bugsnag/Client/BugsnagClient.m | 2 +- Bugsnag/Delivery/BSGConnectivity.m | 4 ++-- Bugsnag/Delivery/BSGEventUploadObjectOperation.m | 2 +- Bugsnag/Delivery/BSGEventUploadOperation.m | 4 ++-- Bugsnag/Delivery/BugsnagApiClient.m | 2 +- Bugsnag/Helpers/BSGAppHangDetector.m | 4 ++-- Bugsnag/Helpers/BSGSerialization.m | 2 +- .../Source/KSCrash/Recording/Tools/BSG_KSMach_x86_64.c | 2 +- Bugsnag/Metadata/BugsnagMetadata.m | 4 ++-- Bugsnag/Payload/BugsnagEvent.m | 4 ++-- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m b/Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m index 2a38c1c37..98bbeb44a 100644 --- a/Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m +++ b/Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m @@ -359,7 +359,7 @@ - (void)addBreadcrumbForNotification:(NSNotification *)notification { [self addBreadcrumbWithType:BSGBreadcrumbTypeState forNotificationName:notification.name]; } -- (void)addBreadcrumbForTableViewNotification:(__attribute__((unused)) NSNotification *)notification { +- (void)addBreadcrumbForTableViewNotification:(__unused NSNotification *)notification { #if BSG_HAVE_TABLE_VIEW #if TARGET_OS_IOS || TARGET_OS_TV @@ -375,7 +375,7 @@ - (void)addBreadcrumbForTableViewNotification:(__attribute__((unused)) NSNotific #endif } -- (void)addBreadcrumbForMenuItemNotification:(__attribute__((unused)) NSNotification *)notification { +- (void)addBreadcrumbForMenuItemNotification:(__unused NSNotification *)notification { #if TARGET_OS_OSX NSMenuItem *menuItem = [[notification userInfo] valueForKey:@"MenuItem"]; [self addBreadcrumbWithType:BSGBreadcrumbTypeState forNotificationName:notification.name metadata: @@ -383,7 +383,7 @@ - (void)addBreadcrumbForMenuItemNotification:(__attribute__((unused)) NSNotifica #endif } -- (void)addBreadcrumbForControlNotification:(__attribute__((unused)) NSNotification *)notification { +- (void)addBreadcrumbForControlNotification:(__unused NSNotification *)notification { #if TARGET_OS_IOS NSString *label = ((UIControl *)notification.object).accessibilityLabel; [self addBreadcrumbWithType:BSGBreadcrumbTypeUser forNotificationName:notification.name metadata: diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index de8f2f219..1a47b8c93 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -312,7 +312,7 @@ - (void)start { #endif } -- (void)appLaunchTimerFired:(__attribute__((unused)) NSTimer *)timer { +- (void)appLaunchTimerFired:(__unused NSTimer *)timer { [self markLaunchCompleted]; } diff --git a/Bugsnag/Delivery/BSGConnectivity.m b/Bugsnag/Delivery/BSGConnectivity.m index 9c3fa814e..c9c40a739 100644 --- a/Bugsnag/Delivery/BSGConnectivity.m +++ b/Bugsnag/Delivery/BSGConnectivity.m @@ -88,9 +88,9 @@ BOOL BSGConnectivityShouldReportChange(SCNetworkReachabilityFlags flags) { * Callback invoked by SCNetworkReachability, which calls an Objective-C block * that handles the connection change. */ -void BSGConnectivityCallback(__attribute__((unused)) SCNetworkReachabilityRef target, +void BSGConnectivityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, - __attribute__((unused)) void *info) + __unused void *info) { if (bsg_reachability_change_block && BSGConnectivityShouldReportChange(flags)) { BOOL connected = (flags & kSCNetworkReachabilityFlagsReachable) != 0; diff --git a/Bugsnag/Delivery/BSGEventUploadObjectOperation.m b/Bugsnag/Delivery/BSGEventUploadObjectOperation.m index fe18305d5..677ad3864 100644 --- a/Bugsnag/Delivery/BSGEventUploadObjectOperation.m +++ b/Bugsnag/Delivery/BSGEventUploadObjectOperation.m @@ -20,7 +20,7 @@ - (instancetype)initWithEvent:(BugsnagEvent *)event delegate:(id)delegate completion __block NSData *HTTPBody = [delegate.apiClient sendJSONPayload:requestPayload headers:requestHeaders toURL:notifyURL - completionHandler:^(BugsnagApiClientDeliveryStatus status, __attribute__((unused)) NSError *deliveryError) { + completionHandler:^(BugsnagApiClientDeliveryStatus status, __unused NSError *deliveryError) { switch (status) { case BugsnagApiClientDeliveryStatusDelivered: @@ -156,7 +156,7 @@ - (void)runWithDelegate:(id)delegate completion // MARK: Subclassing -- (BugsnagEvent *)loadEventAndReturnError:(__attribute__((unused)) NSError * __autoreleasing *)errorPtr { +- (BugsnagEvent *)loadEventAndReturnError:(__unused NSError * __autoreleasing *)errorPtr { // Must be implemented by all subclasses [self doesNotRecognizeSelector:_cmd]; return nil; diff --git a/Bugsnag/Delivery/BugsnagApiClient.m b/Bugsnag/Delivery/BugsnagApiClient.m index b2bb78c44..1673dcdd0 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.m +++ b/Bugsnag/Delivery/BugsnagApiClient.m @@ -64,7 +64,7 @@ - (NSData *)sendJSONPayload:(NSDictionary *)payload NSMutableURLRequest *request = [self prepareRequest:url headers:mutableHeaders]; bsg_log_debug(@"Sending %lu byte payload to %@", (unsigned long)data.length, url); - [[self.session uploadTaskWithRequest:request fromData:data completionHandler:^(__attribute__((unused)) NSData *responseData, + [[self.session uploadTaskWithRequest:request fromData:data completionHandler:^(__unused NSData *responseData, NSURLResponse *response, NSError *connectionError) { if (![response isKindOfClass:[NSHTTPURLResponse class]]) { bsg_log_debug(@"Request to %@ completed with error %@", url, error); diff --git a/Bugsnag/Helpers/BSGAppHangDetector.m b/Bugsnag/Helpers/BSGAppHangDetector.m index 94ac2a81f..4dc0842e1 100644 --- a/Bugsnag/Helpers/BSGAppHangDetector.m +++ b/Bugsnag/Helpers/BSGAppHangDetector.m @@ -72,7 +72,7 @@ - (void)startWithDelegate:(id)delegate { __block BOOL isProcessing = NO; void (^ observerBlock)(CFRunLoopObserverRef, CFRunLoopActivity) = - ^(__attribute__((unused)) CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + ^(__unused CFRunLoopObserverRef observer, CFRunLoopActivity activity) { if (activity == kCFRunLoopAfterWaiting || activity == kCFRunLoopBeforeSources) { if (isProcessing) { @@ -177,7 +177,7 @@ - (void)appHangDetected { threads = [BugsnagThread allThreads:YES callStackReturnAddresses:NSThread.callStackReturnAddresses]; // By default the calling thread is marked as "Error reported from this thread", which is not correct case for app hangs. [threads enumerateObjectsUsingBlock:^(BugsnagThread * _Nonnull thread, NSUInteger idx, - __attribute__((unused)) BOOL * _Nonnull stop) { + __unused BOOL * _Nonnull stop) { thread.errorReportingThread = idx == 0; }]; } else { diff --git a/Bugsnag/Helpers/BSGSerialization.m b/Bugsnag/Helpers/BSGSerialization.m index eadb82c14..64751c377 100644 --- a/Bugsnag/Helpers/BSGSerialization.m +++ b/Bugsnag/Helpers/BSGSerialization.m @@ -39,7 +39,7 @@ id BSGSanitizeObject(id obj) { __block NSMutableDictionary *output = [NSMutableDictionary dictionaryWithCapacity:[input count]]; [input enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, - __attribute__((unused)) BOOL *_Nonnull stop) { + __unused BOOL *_Nonnull stop) { if ([key isKindOfClass:[NSString class]]) { id cleanedObject = BSGSanitizeObject(obj); if (cleanedObject) diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach_x86_64.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach_x86_64.c index aed15c041..a2882a7ae 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach_x86_64.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach_x86_64.c @@ -62,7 +62,7 @@ uintptr_t bsg_ksmachinstructionAddress( uintptr_t bsg_ksmachlinkRegister(const BSG_STRUCT_MCONTEXT_L *const machineContext - __attribute__((unused))) { + __unused) { return 0; } diff --git a/Bugsnag/Metadata/BugsnagMetadata.m b/Bugsnag/Metadata/BugsnagMetadata.m index 750b7fe2d..c35575ca2 100644 --- a/Bugsnag/Metadata/BugsnagMetadata.m +++ b/Bugsnag/Metadata/BugsnagMetadata.m @@ -111,13 +111,13 @@ - (NSDictionary *)toDictionary { // MARK: - -- (id)copyWithZone:(__attribute__((unused)) NSZone *)zone { +- (id)copyWithZone:(__unused NSZone *)zone { return [self deepCopy]; } // MARK: - -- (instancetype)mutableCopyWithZone:(__attribute__((unused)) NSZone *)zone { +- (instancetype)mutableCopyWithZone:(__unused NSZone *)zone { @synchronized(self) { NSMutableDictionary *dict = [self.dictionary mutableCopy]; return [[BugsnagMetadata alloc] initWithDictionary:dict]; diff --git a/Bugsnag/Payload/BugsnagEvent.m b/Bugsnag/Payload/BugsnagEvent.m index 2ac022685..76b219580 100644 --- a/Bugsnag/Payload/BugsnagEvent.m +++ b/Bugsnag/Payload/BugsnagEvent.m @@ -350,7 +350,7 @@ - (instancetype)initWithKSCrashReport:(NSDictionary *)event { handledState.unhandledOverridden = isUnhandledOverridden; } - [[self parseOnCrashData:event] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, __attribute__((unused)) BOOL *stop) { + [[self parseOnCrashData:event] enumerateKeysAndObjectsUsingBlock:^(id key, id obj, __unused BOOL *stop) { if ([key isKindOfClass:[NSString class]] && [obj isKindOfClass:[NSDictionary class]]) { [metadata addMetadata:obj toSection:key]; @@ -565,7 +565,7 @@ - (NSDictionary *)toJsonWithRedactedKeys:(NSSet *)redactedKeys { event[BSGKeyExceptions] = ({ NSMutableArray *array = [NSMutableArray array]; - [self.errors enumerateObjectsUsingBlock:^(BugsnagError *error, NSUInteger idx, __attribute__((unused)) BOOL *stop) { + [self.errors enumerateObjectsUsingBlock:^(BugsnagError *error, NSUInteger idx, __unused BOOL *stop) { if (self.customException != nil && idx == 0) { [array addObject:(NSDictionary * _Nonnull)self.customException]; } else { From 6d1b53896343807b14fb5b0c9f486f49abdc94cf Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 21 Jul 2022 08:59:11 +0100 Subject: [PATCH 03/24] Remove unnecessary NS_DESIGNATED_INITIALIZER definition --- Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h b/Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h index b1e3c1db1..ed4e50093 100644 --- a/Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h +++ b/Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h @@ -25,14 +25,6 @@ // #import -#ifndef NS_DESIGNATED_INITIALIZER -#if __has_attribute(objc_designated_initializer) -#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) -#else -#define NS_DESIGNATED_INITIALIZER -#endif -#endif - /** * Types of breadcrumbs */ From f297a1f182c592ffaf32de1b4966921eca6334f1 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 21 Jul 2022 09:21:23 +0100 Subject: [PATCH 04/24] Remove -[BugsnagMetadata deepCopy] --- Bugsnag/Client/BugsnagClient.m | 6 +++--- Bugsnag/Metadata/BugsnagMetadata+Private.h | 4 +--- Bugsnag/Metadata/BugsnagMetadata.m | 17 ++--------------- Tests/BugsnagTests/BugsnagMetadataTests.m | 6 +++++- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index 1a47b8c93..208a6125c 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -210,7 +210,7 @@ - (instancetype)initWithConfiguration:(BugsnagConfiguration *)configuration { self.breadcrumbs = [[BugsnagBreadcrumbs alloc] initWithConfiguration:self.configuration]; // Start with a copy of the configuration metadata - self.metadata = [[_configuration metadata] deepCopy]; + self.metadata = [[_configuration metadata] copy]; } return self; } @@ -604,7 +604,7 @@ - (void)notify:(NSException *)exception block:(BugsnagOnErrorBlock)block { - (void)notifyErrorOrException:(id)errorOrException block:(BugsnagOnErrorBlock)block { NSDictionary *systemInfo = [BSG_KSSystemInfo systemInfo]; - BugsnagMetadata *metadata = [self.metadata deepCopy]; + BugsnagMetadata *metadata = [self.metadata copy]; NSArray *callStack = nil; NSString *context = self.context; @@ -1034,7 +1034,7 @@ - (void)appHangDetectedAtDate:(NSDate *)date withThreads:(NSArray *breadcrumbs = [self.breadcrumbs breadcrumbsBeforeDate:date]; - BugsnagMetadata *metadata = [self.metadata deepCopy]; + BugsnagMetadata *metadata = [self.metadata copy]; [metadata addMetadata:BSGAppMetadataFromRunContext(bsg_runContext) toSection:BSGKeyApp]; [metadata addMetadata:BSGDeviceMetadataFromRunContext(bsg_runContext) toSection:BSGKeyDevice]; diff --git a/Bugsnag/Metadata/BugsnagMetadata+Private.h b/Bugsnag/Metadata/BugsnagMetadata+Private.h index 9bc1d5063..e39163fc3 100644 --- a/Bugsnag/Metadata/BugsnagMetadata+Private.h +++ b/Bugsnag/Metadata/BugsnagMetadata+Private.h @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^ BSGMetadataObserver)(BugsnagMetadata *); -@interface BugsnagMetadata () +@interface BugsnagMetadata () #pragma mark Properties @@ -24,8 +24,6 @@ typedef void (^ BSGMetadataObserver)(BugsnagMetadata *); - (NSDictionary *)toDictionary; -- (instancetype)deepCopy; - @end NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Metadata/BugsnagMetadata.m b/Bugsnag/Metadata/BugsnagMetadata.m index c35575ca2..092fc2856 100644 --- a/Bugsnag/Metadata/BugsnagMetadata.m +++ b/Bugsnag/Metadata/BugsnagMetadata.m @@ -111,16 +111,9 @@ - (NSDictionary *)toDictionary { // MARK: - -- (id)copyWithZone:(__unused NSZone *)zone { - return [self deepCopy]; -} - -// MARK: - - -- (instancetype)mutableCopyWithZone:(__unused NSZone *)zone { +- (id)copyWithZone:(NSZone *)zone { @synchronized(self) { - NSMutableDictionary *dict = [self.dictionary mutableCopy]; - return [[BugsnagMetadata alloc] initWithDictionary:dict]; + return [[BugsnagMetadata allocWithZone:zone] initWithDictionary:self.dictionary]; } } @@ -138,12 +131,6 @@ - (NSMutableDictionary *)getMetadata:(NSString *)sectionName } } -- (instancetype)deepCopy { - @synchronized(self) { - return [[BugsnagMetadata alloc] initWithDictionary:self.dictionary]; - } -} - // MARK: - /** diff --git a/Tests/BugsnagTests/BugsnagMetadataTests.m b/Tests/BugsnagTests/BugsnagMetadataTests.m index b2a3ecf46..5b39c6a27 100644 --- a/Tests/BugsnagTests/BugsnagMetadataTests.m +++ b/Tests/BugsnagTests/BugsnagMetadataTests.m @@ -205,11 +205,15 @@ - (void)testDeepCopyWithZone { BugsnagMetadata *metadata = [BugsnagMetadata new]; [metadata addMetadata:@"myKey" withKey:@"myValue" toSection:@"section1"]; - BugsnagMetadata *clone = [metadata deepCopy]; + BugsnagMetadata *clone = [metadata copy]; XCTAssertNotEqual(metadata, clone); // Until/unless it's decided otherwise the copy is a shallow one. XCTAssertEqualObjects([metadata getMetadataFromSection:@"section1"], [clone getMetadataFromSection:@"section1"]); + + [metadata clearMetadataFromSection:@"section1" withKey:@"myValue"]; + XCTAssertEqualObjects([metadata getMetadataFromSection:@"section1"], @{}); + XCTAssertEqualObjects([clone getMetadataFromSection:@"section1"], @{@"myValue":@"myKey"}); } -(void)testClearMetadataInSectionWithKey { From be2df101ccbd794302d5c87d1f3f3860f42a6ebd Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Mon, 25 Jul 2022 08:07:46 +0100 Subject: [PATCH 05/24] Send queued sessions when connection regained (#1445) --- Bugsnag/BugsnagSessionTracker.h | 4 ++++ Bugsnag/BugsnagSessionTracker.m | 2 -- Bugsnag/Client/BugsnagClient.m | 1 + Bugsnag/Delivery/BSGSessionUploader.h | 3 +++ CHANGELOG.md | 3 +++ 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Bugsnag/BugsnagSessionTracker.h b/Bugsnag/BugsnagSessionTracker.h index d26549a55..0f5ea3c18 100644 --- a/Bugsnag/BugsnagSessionTracker.h +++ b/Bugsnag/BugsnagSessionTracker.h @@ -11,6 +11,8 @@ #import #import +#import "BSGSessionUploader.h" + NS_ASSUME_NONNULL_BEGIN @interface BugsnagSessionTracker : NSObject @@ -82,6 +84,8 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, readonly, nonatomic) BugsnagSession *runningSession; +@property (strong, nonatomic) BSGSessionUploader *sessionUploader; + - (void)addRuntimeVersionInfo:(NSString *)info withKey:(NSString *)key; diff --git a/Bugsnag/BugsnagSessionTracker.m b/Bugsnag/BugsnagSessionTracker.m index 6d2c7a8ec..f0fbbd237 100644 --- a/Bugsnag/BugsnagSessionTracker.m +++ b/Bugsnag/BugsnagSessionTracker.m @@ -10,7 +10,6 @@ #import "BSGAppKit.h" #import "BSGDefines.h" -#import "BSGSessionUploader.h" #import "BSGUIKit.h" #import "BSGWatchKit.h" #import "BSG_KSSystemInfo.h" @@ -31,7 +30,6 @@ @interface BugsnagSessionTracker () @property (strong, nonatomic) BugsnagConfiguration *config; @property (weak, nonatomic) BugsnagClient *client; -@property (strong, nonatomic) BSGSessionUploader *sessionUploader; @property (strong, nonatomic) NSDate *backgroundStartTime; @property (nonatomic) NSMutableDictionary *extraRuntimeInfo; @end diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index 208a6125c..ea69fa4fb 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -462,6 +462,7 @@ - (void)setupConnectivityListener { __strong typeof(weakSelf) strongSelf = weakSelf; if (connected) { [strongSelf.eventUploader uploadStoredEvents]; + [strongSelf.sessionTracker.sessionUploader processStoredSessions]; } [strongSelf addAutoBreadcrumbOfType:BSGBreadcrumbTypeState diff --git a/Bugsnag/Delivery/BSGSessionUploader.h b/Bugsnag/Delivery/BSGSessionUploader.h index e7e21da52..d8c3ec2e2 100644 --- a/Bugsnag/Delivery/BSGSessionUploader.h +++ b/Bugsnag/Delivery/BSGSessionUploader.h @@ -17,6 +17,9 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithConfig:(BugsnagConfiguration *)configuration notifier:(BugsnagNotifier *)notifier; +/// Scans previously persisted sessions and either discards or attempts upload. +- (void)processStoredSessions; + - (void)uploadSession:(BugsnagSession *)session; @property (copy, nonatomic) NSString *codeBundleId; diff --git a/CHANGELOG.md b/CHANGELOG.md index 80663f2e4..1d0032b2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Changelog ### Bug fixes +* Attempt to send sessions stored on disk when connection regained. + [#1445](https://github.com/bugsnag/bugsnag-cocoa/pull/1445) + * Set `user.id` to to `device.id` for all events and sessions if `BugsnagClient.user.id` is set to nil. To prevent collection, set it to an empty string or update it in `OnSendError` / `OnSession`. [#1442](https://github.com/bugsnag/bugsnag-cocoa/pull/1442) From 32c24fb14550b90234c688eec0c01610dea108b5 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Mon, 25 Jul 2022 16:39:33 +0100 Subject: [PATCH 06/24] Make `stackframesWithCallStackReturnAddresses:` public (#1446) --- Bugsnag/Payload/BugsnagStackframe+Private.h | 2 -- Bugsnag/include/Bugsnag/BugsnagStackframe.h | 14 ++++++++++++-- CHANGELOG.md | 5 +++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Bugsnag/Payload/BugsnagStackframe+Private.h b/Bugsnag/Payload/BugsnagStackframe+Private.h index e4c71a12d..b1142634d 100644 --- a/Bugsnag/Payload/BugsnagStackframe+Private.h +++ b/Bugsnag/Payload/BugsnagStackframe+Private.h @@ -14,8 +14,6 @@ NS_ASSUME_NONNULL_BEGIN + (NSArray *)stackframesWithBacktrace:(uintptr_t *)backtrace length:(NSUInteger)length; -+ (NSArray *)stackframesWithCallStackReturnAddresses:(NSArray *)callStackReturnAddresses; - /// Constructs a stackframe object from a stackframe dictionary and list of images captured by KSCrash. + (nullable instancetype)frameFromDict:(NSDictionary *)dict withImages:(NSArray *> *)binaryImages; diff --git a/Bugsnag/include/Bugsnag/BugsnagStackframe.h b/Bugsnag/include/Bugsnag/BugsnagStackframe.h index 882396b43..63d5b429f 100644 --- a/Bugsnag/include/Bugsnag/BugsnagStackframe.h +++ b/Bugsnag/include/Bugsnag/BugsnagStackframe.h @@ -70,9 +70,19 @@ FOUNDATION_EXPORT BugsnagStackframeType const BugsnagStackframeTypeCocoa; @property (copy, nullable, nonatomic) BugsnagStackframeType type; /** - * Returns an array of stackframe objects representing the provided call stack strings. + * Creates an array of stackframe objects representing the provided call stack. * - * The call stack strings should follow the format used by `[NSThread callStackSymbols]` and `backtrace_symbols()`. + * @param callStackReturnAddresses An array containing the call stack return addresses, as returned by + * `NSThread.callStackReturnAddresses` or `NSException.callStackReturnAddresses`. + */ ++ (NSArray *)stackframesWithCallStackReturnAddresses:(NSArray *)callStackReturnAddresses; + +/** + * Creates an array of stackframe objects representing the provided call stack. + * + * @param callStackSymbols An array containing the call stack symbols, as returned by `NSThread.callStackSymbols`. + * Each element should be in a format determined by the `backtrace_symbols()` function. + */ + (nullable NSArray *)stackframesWithCallStackSymbols:(NSArray *)callStackSymbols; diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0032b2e..829ee6a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Changelog ## TBD +### Enhancements + +* Add `+[BugsnagStackframe stackframesWithCallStackReturnAddresses:]` to public headers. + [#1446](https://github.com/bugsnag/bugsnag-cocoa/pull/1446) + ### Bug fixes * Attempt to send sessions stored on disk when connection regained. From 5cee5de1eaf0de2e8508e280df99fdcb1ba7d1ba Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 26 Jul 2022 09:21:07 +0100 Subject: [PATCH 07/24] Hide symbols by default GCC_SYMBOLS_PRIVATE_EXTERN sets -fvisibility=hidden unless ENABLE_TESTABILITY is enabled; making private symbols available to unit tests. --- Bugsnag.podspec.json | 3 ++ Bugsnag.xcodeproj/project.pbxproj | 12 ++++++ Bugsnag/Helpers/BSGDefines.h | 2 - Bugsnag/Helpers/BSGInternalErrorReporter.h | 2 +- Bugsnag/Helpers/BSGRunContext.h | 4 +- Bugsnag/Helpers/BSGUtils.h | 4 +- .../KSCrash/Recording/Tools/BSG_KSLogger.h | 3 ++ Bugsnag/Payload/BugsnagApp+Private.h | 2 +- .../Payload/BugsnagDeviceWithState+Private.h | 2 +- Bugsnag/Payload/BugsnagSession+Private.h | 8 ++-- Bugsnag/include/Bugsnag/Bugsnag.h | 8 ++-- Bugsnag/include/Bugsnag/BugsnagApp.h | 3 ++ Bugsnag/include/Bugsnag/BugsnagAppWithState.h | 2 + Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h | 3 ++ Bugsnag/include/Bugsnag/BugsnagClient.h | 8 ++-- .../include/Bugsnag/BugsnagConfiguration.h | 15 +++---- Bugsnag/include/Bugsnag/BugsnagDefines.h | 41 +++++++++++++++++++ Bugsnag/include/Bugsnag/BugsnagDevice.h | 3 ++ .../include/Bugsnag/BugsnagDeviceWithState.h | 2 + .../Bugsnag/BugsnagEndpointConfiguration.h | 3 ++ Bugsnag/include/Bugsnag/BugsnagError.h | 3 ++ Bugsnag/include/Bugsnag/BugsnagErrorTypes.h | 3 ++ Bugsnag/include/Bugsnag/BugsnagEvent.h | 2 + Bugsnag/include/Bugsnag/BugsnagFeatureFlag.h | 3 ++ Bugsnag/include/Bugsnag/BugsnagLastRunInfo.h | 3 ++ Bugsnag/include/Bugsnag/BugsnagMetadata.h | 2 + Bugsnag/include/Bugsnag/BugsnagSession.h | 2 + Bugsnag/include/Bugsnag/BugsnagStackframe.h | 5 ++- Bugsnag/include/Bugsnag/BugsnagThread.h | 3 ++ Bugsnag/include/Bugsnag/BugsnagUser.h | 3 ++ Package.swift | 1 + Tests/BugsnagTests/Tests-Bridging-Header.h | 3 +- 32 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 Bugsnag/include/Bugsnag/BugsnagDefines.h diff --git a/Bugsnag.podspec.json b/Bugsnag.podspec.json index 8e81602d6..6b5f14261 100644 --- a/Bugsnag.podspec.json +++ b/Bugsnag.podspec.json @@ -38,6 +38,9 @@ "Security" ] }, + "compiler_flags": [ + "-fvisibility=hidden" + ], "libraries": [ "c++", "z" ], diff --git a/Bugsnag.xcodeproj/project.pbxproj b/Bugsnag.xcodeproj/project.pbxproj index 82413aa6b..28984bfe3 100644 --- a/Bugsnag.xcodeproj/project.pbxproj +++ b/Bugsnag.xcodeproj/project.pbxproj @@ -699,6 +699,10 @@ 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 */; }; + 01C41A28288FD3EA00BAE31A /* BugsnagDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 01C41A27288FD3EA00BAE31A /* BugsnagDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 01C41A29288FD3EB00BAE31A /* BugsnagDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 01C41A27288FD3EA00BAE31A /* BugsnagDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 01C41A2A288FD3EB00BAE31A /* BugsnagDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 01C41A27288FD3EA00BAE31A /* BugsnagDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 01C41A2B288FD3EB00BAE31A /* BugsnagDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 01C41A27288FD3EA00BAE31A /* BugsnagDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; 01CB95BF278F0C830077744A /* BSG_KSFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 01CB95BD278F0C830077744A /* BSG_KSFile.h */; }; 01CB95C0278F0C830077744A /* BSG_KSFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 01CB95BD278F0C830077744A /* BSG_KSFile.h */; }; 01CB95C1278F0C830077744A /* BSG_KSFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 01CB95BD278F0C830077744A /* BSG_KSFile.h */; }; @@ -1614,6 +1618,7 @@ 01C17AE62542ED7F00C102C9 /* KSCrashReportWriterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSCrashReportWriterTests.m; sourceTree = ""; }; 01C2769B2601F44D006901EA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 01C2769C2601F455006901EA /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; + 01C41A27288FD3EA00BAE31A /* BugsnagDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BugsnagDefines.h; sourceTree = ""; }; 01CB95BD278F0C830077744A /* BSG_KSFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSG_KSFile.h; sourceTree = ""; }; 01CB95BE278F0C830077744A /* BSG_KSFile.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = BSG_KSFile.c; sourceTree = ""; }; 01CCAEE825D414D60057268D /* BugsnagLastRunInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagLastRunInfo.h; sourceTree = ""; }; @@ -2281,6 +2286,7 @@ 3A700A8524A63A8E0068CD1B /* BugsnagBreadcrumb.h */, 3A700A8924A63A8E0068CD1B /* BugsnagClient.h */, 3A700A8D24A63A8E0068CD1B /* BugsnagConfiguration.h */, + 01C41A27288FD3EA00BAE31A /* BugsnagDefines.h */, 3A700A8F24A63A8E0068CD1B /* BugsnagDevice.h */, 3A700A9224A63A8E0068CD1B /* BugsnagDeviceWithState.h */, 3A700A8424A63A8E0068CD1B /* BugsnagEndpointConfiguration.h */, @@ -2374,6 +2380,7 @@ 008969DE2486DAD100DC48C2 /* BSG_KSCrashC.h in Headers */, 0089699F2486DAD100DC48C2 /* BSG_KSJSONCodecObjC.h in Headers */, 0089697E2486DAD100DC48C2 /* BSG_KSArchSpecific.h in Headers */, + 01C41A28288FD3EA00BAE31A /* BugsnagDefines.h in Headers */, CB3744942845FA9500A3955E /* BSG_KSCrashStringConversion.h in Headers */, 0154E20228070AEA009044E4 /* BSGRunContext.h in Headers */, 008969BA2486DAD100DC48C2 /* BSG_KSJSONCodec.h in Headers */, @@ -2476,6 +2483,7 @@ 008969DF2486DAD100DC48C2 /* BSG_KSCrashC.h in Headers */, 008969A02486DAD100DC48C2 /* BSG_KSJSONCodecObjC.h in Headers */, 0089697F2486DAD100DC48C2 /* BSG_KSArchSpecific.h in Headers */, + 01C41A29288FD3EB00BAE31A /* BugsnagDefines.h in Headers */, CB3744952845FA9500A3955E /* BSG_KSCrashStringConversion.h in Headers */, 0154E20328070AEA009044E4 /* BSGRunContext.h in Headers */, 008969BB2486DAD100DC48C2 /* BSG_KSJSONCodec.h in Headers */, @@ -2578,6 +2586,7 @@ 008969E02486DAD100DC48C2 /* BSG_KSCrashC.h in Headers */, 008969A12486DAD100DC48C2 /* BSG_KSJSONCodecObjC.h in Headers */, 008969802486DAD100DC48C2 /* BSG_KSArchSpecific.h in Headers */, + 01C41A2A288FD3EB00BAE31A /* BugsnagDefines.h in Headers */, CB3744962845FA9500A3955E /* BSG_KSCrashStringConversion.h in Headers */, 0154E20428070AEA009044E4 /* BSGRunContext.h in Headers */, 008969BC2486DAD100DC48C2 /* BSG_KSJSONCodec.h in Headers */, @@ -2660,6 +2669,7 @@ CBBDE95B280068FD0070DCD3 /* BugsnagBreadcrumb.h in Headers */, CBBDE99B2800699C0070DCD3 /* BSG_KSCrashSentry_MachException.h in Headers */, CBBDE917280068560070DCD3 /* BugsnagSessionTracker.h in Headers */, + 01C41A2B288FD3EB00BAE31A /* BugsnagDefines.h in Headers */, CBBDE93F280068D40070DCD3 /* BSGSerialization.h in Headers */, CBBDE954280068FD0070DCD3 /* BugsnagEvent.h in Headers */, CBBDE962280068FD0070DCD3 /* BugsnagFeatureFlag.h in Headers */, @@ -3947,6 +3957,7 @@ "DEBUG=1", "$(inherited)", ); + GCC_SYMBOLS_PRIVATE_EXTERN = YES; INFOPLIST_FILE = ./Framework/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.11; @@ -3989,6 +4000,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; INFOPLIST_FILE = ./Framework/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.11; diff --git a/Bugsnag/Helpers/BSGDefines.h b/Bugsnag/Helpers/BSGDefines.h index 9094b8b6b..e5b9f7114 100644 --- a/Bugsnag/Helpers/BSGDefines.h +++ b/Bugsnag/Helpers/BSGDefines.h @@ -10,8 +10,6 @@ #include -#define BSG_PRIVATE __attribute__((visibility("hidden"))) - // Capabilities dependent upon system defines and files #define BSG_HAVE_APPKIT __has_include() #define BSG_HAVE_BATTERY ( TARGET_OS_IOS || TARGET_OS_WATCH) diff --git a/Bugsnag/Helpers/BSGInternalErrorReporter.h b/Bugsnag/Helpers/BSGInternalErrorReporter.h index 7cace9e1c..f93e3373a 100644 --- a/Bugsnag/Helpers/BSGInternalErrorReporter.h +++ b/Bugsnag/Helpers/BSGInternalErrorReporter.h @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN /// Returns a concise desription of the error including its domain, code, and debug description or localizedDescription. -BSG_PRIVATE NSString *_Nullable BSGErrorDescription(NSError *_Nullable error); +NSString *_Nullable BSGErrorDescription(NSError *_Nullable error); // MARK: - diff --git a/Bugsnag/Helpers/BSGRunContext.h b/Bugsnag/Helpers/BSGRunContext.h index 41c891f1d..496cdeb8c 100644 --- a/Bugsnag/Helpers/BSGRunContext.h +++ b/Bugsnag/Helpers/BSGRunContext.h @@ -70,9 +70,9 @@ void BSGRunContextInit(NSString *_Nonnull path); #pragma mark - -BSG_PRIVATE void BSGRunContextUpdateMemory(void); +void BSGRunContextUpdateMemory(void); -BSG_PRIVATE void BSGRunContextUpdateTimestamp(void); +void BSGRunContextUpdateTimestamp(void); #pragma mark - diff --git a/Bugsnag/Helpers/BSGUtils.h b/Bugsnag/Helpers/BSGUtils.h index 3cb135e4b..06cb3d473 100644 --- a/Bugsnag/Helpers/BSGUtils.h +++ b/Bugsnag/Helpers/BSGUtils.h @@ -16,7 +16,7 @@ __BEGIN_DECLS NS_ASSUME_NONNULL_BEGIN /// Returns a heap allocated null-terminated C string with the contents of `data`, or NULL if `data` is nil or empty. -BSG_PRIVATE char *_Nullable BSGCStringWithData(NSData *_Nullable data); +char *_Nullable BSGCStringWithData(NSData *_Nullable data); /// Changes the NSFileProtectionKey attribute of the specified file or directory from NSFileProtectionComplete to NSFileProtectionCompleteUnlessOpen. /// Has no effect if the specified file or directory does not have NSFileProtectionComplete. @@ -24,7 +24,7 @@ BSG_PRIVATE char *_Nullable BSGCStringWithData(NSData *_Nullable data); /// Files with NSFileProtectionComplete cannot be read from or written to while the device is locked or booting. /// /// Files with NSFileProtectionCompleteUnlessOpen can be created while the device is locked, but once closed, cannot be opened again until the device is unlocked. -BSG_PRIVATE BOOL BSGDisableNSFileProtectionComplete(NSString *path); +BOOL BSGDisableNSFileProtectionComplete(NSString *path); dispatch_queue_t BSGGetFileSystemQueue(void); diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSLogger.h b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSLogger.h index 4c3ab0788..6d9b076e6 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSLogger.h +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSLogger.h @@ -31,6 +31,7 @@ extern "C" { #endif +#include #include "BugsnagLogger.h" @@ -143,6 +144,7 @@ void bsg_i_kslog_logC(const char *level, const char *file, int line, const char *function, const char *fmt, ...) __printflike(5, 6); +BUGSNAG_EXTERN void bsg_i_kslog_logCBasic(const char *fmt, ...) __printflike(1, 2); #define i_KSLOG_FULL bsg_i_kslog_logC @@ -212,6 +214,7 @@ void bsg_i_kslog_logCBasic(const char *fmt, ...) __printflike(1, 2); * * @param overwrite If true, overwrite the log file. */ +BUGSNAG_EXTERN bool bsg_kslog_setLogFilename(const char *filename, bool overwrite); /** Tests if the logger would print at the specified level. diff --git a/Bugsnag/Payload/BugsnagApp+Private.h b/Bugsnag/Payload/BugsnagApp+Private.h index a10bb55ce..18c742fb8 100644 --- a/Bugsnag/Payload/BugsnagApp+Private.h +++ b/Bugsnag/Payload/BugsnagApp+Private.h @@ -32,6 +32,6 @@ NS_ASSUME_NONNULL_BEGIN NSDictionary *BSGParseAppMetadata(NSDictionary *event); -BSG_PRIVATE NSDictionary *BSGAppMetadataFromRunContext(const struct BSGRunContext *context); +NSDictionary *BSGAppMetadataFromRunContext(const struct BSGRunContext *context); NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Payload/BugsnagDeviceWithState+Private.h b/Bugsnag/Payload/BugsnagDeviceWithState+Private.h index f336aebc7..89c7cfff4 100644 --- a/Bugsnag/Payload/BugsnagDeviceWithState+Private.h +++ b/Bugsnag/Payload/BugsnagDeviceWithState+Private.h @@ -32,6 +32,6 @@ NS_ASSUME_NONNULL_BEGIN NSMutableDictionary *BSGParseDeviceMetadata(NSDictionary *event); -BSG_PRIVATE NSDictionary * BSGDeviceMetadataFromRunContext(const struct BSGRunContext *context); +NSDictionary * BSGDeviceMetadataFromRunContext(const struct BSGRunContext *context); NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Payload/BugsnagSession+Private.h b/Bugsnag/Payload/BugsnagSession+Private.h index 0cea2e121..e6fd82359 100644 --- a/Bugsnag/Payload/BugsnagSession+Private.h +++ b/Bugsnag/Payload/BugsnagSession+Private.h @@ -52,15 +52,15 @@ NSDictionary * BSGSessionToEventJson(BugsnagSession *session); BugsnagSession *_Nullable BSGSessionFromEventJson(NSDictionary *_Nullable json, BugsnagApp *app, BugsnagDevice *device, BugsnagUser *user); /// Saves the session info into bsg_runContext. -BSG_PRIVATE void BSGSessionUpdateRunContext(BugsnagSession *_Nullable session); +void BSGSessionUpdateRunContext(BugsnagSession *_Nullable session); /// Returns session information from bsg_lastRunContext. -BSG_PRIVATE BugsnagSession *_Nullable BSGSessionFromLastRunContext(BugsnagApp *app, BugsnagDevice *device, BugsnagUser *user); +BugsnagSession *_Nullable BSGSessionFromLastRunContext(BugsnagApp *app, BugsnagDevice *device, BugsnagUser *user); /// Saves current session information (from bsg_runContext) into a crash report. -BSG_PRIVATE void BSGSessionWriteCrashReport(const BSG_KSCrashReportWriter *writer); +void BSGSessionWriteCrashReport(const BSG_KSCrashReportWriter *writer); /// Returns session information from a crash report previously written to by BSGSessionWriteCrashReport or BSSerializeDataCrashHandler. -BSG_PRIVATE BugsnagSession *_Nullable BSGSessionFromCrashReport(NSDictionary *report, BugsnagApp *app, BugsnagDevice *device, BugsnagUser *user); +BugsnagSession *_Nullable BSGSessionFromCrashReport(NSDictionary *report, BugsnagApp *app, BugsnagDevice *device, BugsnagUser *user); NS_ASSUME_NONNULL_END diff --git a/Bugsnag/include/Bugsnag/Bugsnag.h b/Bugsnag/include/Bugsnag/Bugsnag.h index 25d1c4ce6..ad122d987 100644 --- a/Bugsnag/include/Bugsnag/Bugsnag.h +++ b/Bugsnag/include/Bugsnag/Bugsnag.h @@ -29,6 +29,7 @@ #import #import #import +#import #import #import #import @@ -46,6 +47,7 @@ /** * Static access to a Bugsnag Client, the easiest way to use Bugsnag in your app. */ +BUGSNAG_EXTERN @interface Bugsnag : NSObject /** @@ -103,7 +105,7 @@ /** * @return YES if Bugsnag has been started and the previous launch crashed */ -+ (BOOL)appDidCrashLastLaunch BSG_DEPRECATED_WITH_REPLACEMENT("lastRunInfo.crashed"); ++ (BOOL)appDidCrashLastLaunch BUGSNAG_DEPRECATED_WITH_REPLACEMENT("lastRunInfo.crashed"); /** * Information about the last run of the app, and whether it crashed. @@ -340,7 +342,7 @@ * Deprecated */ + (void)removeOnSessionBlock:(BugsnagOnSessionBlock _Nonnull)block - BSG_DEPRECATED_WITH_REPLACEMENT("removeOnSession:") + BUGSNAG_DEPRECATED_WITH_REPLACEMENT("removeOnSession:") NS_SWIFT_NAME(removeOnSession(block:)); // ============================================================================= @@ -370,7 +372,7 @@ * Deprecated */ + (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block - BSG_DEPRECATED_WITH_REPLACEMENT("removeOnBreadcrumb:") + BUGSNAG_DEPRECATED_WITH_REPLACEMENT("removeOnBreadcrumb:") NS_SWIFT_NAME(removeOnBreadcrumb(block:)); @end diff --git a/Bugsnag/include/Bugsnag/BugsnagApp.h b/Bugsnag/include/Bugsnag/BugsnagApp.h index 04d6dc1d2..4479b26f8 100644 --- a/Bugsnag/include/Bugsnag/BugsnagApp.h +++ b/Bugsnag/include/Bugsnag/BugsnagApp.h @@ -8,10 +8,13 @@ #import +#import + /** * Stateless information set by the notifier about your app can be found on this class. These values * can be accessed and amended if necessary. */ +BUGSNAG_EXTERN @interface BugsnagApp : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagAppWithState.h b/Bugsnag/include/Bugsnag/BugsnagAppWithState.h index 9bfaea235..2f6a231ea 100644 --- a/Bugsnag/include/Bugsnag/BugsnagAppWithState.h +++ b/Bugsnag/include/Bugsnag/BugsnagAppWithState.h @@ -9,11 +9,13 @@ #import #import +#import /** * Stateful information set by the notifier about your app can be found on this class. These values * can be accessed and amended if necessary. */ +BUGSNAG_EXTERN @interface BugsnagAppWithState : BugsnagApp /** diff --git a/Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h b/Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h index ed4e50093..fc81d4600 100644 --- a/Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h +++ b/Bugsnag/include/Bugsnag/BugsnagBreadcrumb.h @@ -25,6 +25,8 @@ // #import +#import + /** * Types of breadcrumbs */ @@ -89,6 +91,7 @@ typedef NS_OPTIONS(NSUInteger, BSGEnabledBreadcrumbType) { */ @class BugsnagBreadcrumb; +BUGSNAG_EXTERN @interface BugsnagBreadcrumb : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagClient.h b/Bugsnag/include/Bugsnag/BugsnagClient.h index 5db84b934..40ba4642c 100644 --- a/Bugsnag/include/Bugsnag/BugsnagClient.h +++ b/Bugsnag/include/Bugsnag/BugsnagClient.h @@ -27,6 +27,7 @@ #import #import +#import #import #import #import @@ -39,6 +40,7 @@ * * Use the static access provided by the Bugsnag class instead. */ +BUGSNAG_EXTERN @interface BugsnagClient : NSObject /** @@ -206,7 +208,7 @@ NS_SWIFT_NAME(leaveBreadcrumb(_:metadata:type:)); * Deprecated */ - (void)removeOnSessionBlock:(BugsnagOnSessionBlock _Nonnull )block - BSG_DEPRECATED_WITH_REPLACEMENT("removeOnSession:") + BUGSNAG_DEPRECATED_WITH_REPLACEMENT("removeOnSession:") NS_SWIFT_NAME(removeOnSession(block:)); // ============================================================================= @@ -221,7 +223,7 @@ NS_SWIFT_NAME(leaveBreadcrumb(_:metadata:type:)); /** * @return YES if Bugsnag has been started and the previous launch crashed */ -- (BOOL)appDidCrashLastLaunch BSG_DEPRECATED_WITH_REPLACEMENT("lastRunInfo.crashed"); +- (BOOL)appDidCrashLastLaunch BUGSNAG_DEPRECATED_WITH_REPLACEMENT("lastRunInfo.crashed"); /** * Information about the last run of the app, and whether it crashed. @@ -283,7 +285,7 @@ NS_SWIFT_NAME(leaveBreadcrumb(_:metadata:type:)); * Deprecated */ - (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block - BSG_DEPRECATED_WITH_REPLACEMENT("removeOnBreadcrumb:") + BUGSNAG_DEPRECATED_WITH_REPLACEMENT("removeOnBreadcrumb:") NS_SWIFT_NAME(removeOnBreadcrumb(block:)); @end diff --git a/Bugsnag/include/Bugsnag/BugsnagConfiguration.h b/Bugsnag/include/Bugsnag/BugsnagConfiguration.h index 65926c4ed..60b921451 100644 --- a/Bugsnag/include/Bugsnag/BugsnagConfiguration.h +++ b/Bugsnag/include/Bugsnag/BugsnagConfiguration.h @@ -28,17 +28,13 @@ #import #import +#import #import #import #import #import #import -/** - * Annotates methods and properties that will be removed in the next major release of Bugsnag. - */ -#define BSG_DEPRECATED_WITH_REPLACEMENT(REPLACEMENT) __attribute__((deprecated("", REPLACEMENT))) - @class BugsnagUser; @class BugsnagEndpointConfiguration; @class BugsnagErrorTypes; @@ -92,7 +88,7 @@ typedef NS_OPTIONS(NSUInteger, BSGTelemetryOptions) { * Setting `BugsnagConfiguration.appHangThresholdMillis` to this value disables the reporting of * app hangs that ended before the app was terminated. */ -extern const NSUInteger BugsnagAppHangThresholdFatalOnly API_UNAVAILABLE(watchos); +BUGSNAG_EXTERN const NSUInteger BugsnagAppHangThresholdFatalOnly API_UNAVAILABLE(watchos); /** * A configuration block for modifying an error report @@ -149,6 +145,7 @@ typedef id BugsnagOnSessionRef; /** * Contains user-provided configuration, including API key and endpoints. */ +BUGSNAG_EXTERN @interface BugsnagConfiguration : NSObject /** @@ -408,7 +405,7 @@ typedef id BugsnagOnSessionRef; * Deprecated */ - (void)removeOnSessionBlock:(BugsnagOnSessionBlock)block - BSG_DEPRECATED_WITH_REPLACEMENT("removeOnSession:") + BUGSNAG_DEPRECATED_WITH_REPLACEMENT("removeOnSession:") NS_SWIFT_NAME(removeOnSession(block:)); // ============================================================================= @@ -438,7 +435,7 @@ typedef id BugsnagOnSessionRef; * Deprecated */ - (void)removeOnSendErrorBlock:(BugsnagOnSendErrorBlock)block - BSG_DEPRECATED_WITH_REPLACEMENT("removeOnSendError:") + BUGSNAG_DEPRECATED_WITH_REPLACEMENT("removeOnSendError:") NS_SWIFT_NAME(removeOnSendError(block:)); // ============================================================================= @@ -468,7 +465,7 @@ typedef id BugsnagOnSessionRef; * Deprecated */ - (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock)block - BSG_DEPRECATED_WITH_REPLACEMENT("removeOnBreadcrumb:") + BUGSNAG_DEPRECATED_WITH_REPLACEMENT("removeOnBreadcrumb:") NS_SWIFT_NAME(removeOnBreadcrumb(block:)); // ============================================================================= diff --git a/Bugsnag/include/Bugsnag/BugsnagDefines.h b/Bugsnag/include/Bugsnag/BugsnagDefines.h new file mode 100644 index 000000000..bb0de023e --- /dev/null +++ b/Bugsnag/include/Bugsnag/BugsnagDefines.h @@ -0,0 +1,41 @@ +// +// BugsnagDefines.h +// Bugsnag +// +// Copyright © 2022 Bugsnag Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall remain in place +// in this source code. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#ifndef BugsnagDefines_h +#define BugsnagDefines_h + +#ifndef BUGSNAG_DEPRECATED_WITH_REPLACEMENT +#define BUGSNAG_DEPRECATED_WITH_REPLACEMENT(REPLACEMENT) __attribute__((deprecated ("", REPLACEMENT))) +#endif + +#ifndef BUGSNAG_EXTERN +#ifdef __cplusplus +#define BUGSNAG_EXTERN extern "C" __attribute__((visibility ("default"))) +#else +#define BUGSNAG_EXTERN extern __attribute__((visibility ("default"))) +#endif +#endif + +#endif diff --git a/Bugsnag/include/Bugsnag/BugsnagDevice.h b/Bugsnag/include/Bugsnag/BugsnagDevice.h index a71ee2d35..1cfda3850 100644 --- a/Bugsnag/include/Bugsnag/BugsnagDevice.h +++ b/Bugsnag/include/Bugsnag/BugsnagDevice.h @@ -8,10 +8,13 @@ #import +#import + /** * Stateless information set by the notifier about the device on which the event occurred can be * found on this class. These values can be accessed and amended if necessary. */ +BUGSNAG_EXTERN @interface BugsnagDevice : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagDeviceWithState.h b/Bugsnag/include/Bugsnag/BugsnagDeviceWithState.h index 8fa55d52e..2e92811f4 100644 --- a/Bugsnag/include/Bugsnag/BugsnagDeviceWithState.h +++ b/Bugsnag/include/Bugsnag/BugsnagDeviceWithState.h @@ -8,12 +8,14 @@ #import +#import #import /** * Stateful information set by the notifier about the device on which the event occurred can be * found on this class. These values can be accessed and amended if necessary. */ +BUGSNAG_EXTERN @interface BugsnagDeviceWithState : BugsnagDevice /** diff --git a/Bugsnag/include/Bugsnag/BugsnagEndpointConfiguration.h b/Bugsnag/include/Bugsnag/BugsnagEndpointConfiguration.h index ccc1a2a00..60b85e937 100644 --- a/Bugsnag/include/Bugsnag/BugsnagEndpointConfiguration.h +++ b/Bugsnag/include/Bugsnag/BugsnagEndpointConfiguration.h @@ -8,6 +8,8 @@ #import +#import + NS_ASSUME_NONNULL_BEGIN /** @@ -15,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN * https://notify.bugsnag.com, and sessions to https://sessions.bugsnag.com, but you can * override this if you are using Bugsnag Enterprise to point to your own Bugsnag endpoints. */ +BUGSNAG_EXTERN @interface BugsnagEndpointConfiguration : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagError.h b/Bugsnag/include/Bugsnag/BugsnagError.h index fcaa821ce..18b0642ba 100644 --- a/Bugsnag/include/Bugsnag/BugsnagError.h +++ b/Bugsnag/include/Bugsnag/BugsnagError.h @@ -8,6 +8,8 @@ #import +#import + @class BugsnagStackframe; /** @@ -22,6 +24,7 @@ typedef NS_OPTIONS(NSUInteger, BSGErrorType) { /** * An Error represents information extracted from an NSError, NSException, or other error source. */ +BUGSNAG_EXTERN @interface BugsnagError : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagErrorTypes.h b/Bugsnag/include/Bugsnag/BugsnagErrorTypes.h index 7122ce728..d04c99da5 100644 --- a/Bugsnag/include/Bugsnag/BugsnagErrorTypes.h +++ b/Bugsnag/include/Bugsnag/BugsnagErrorTypes.h @@ -8,9 +8,12 @@ #import +#import + /** * The types of error that should be reported. */ +BUGSNAG_EXTERN @interface BugsnagErrorTypes : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagEvent.h b/Bugsnag/include/Bugsnag/BugsnagEvent.h index 584e4afae..0016271c3 100644 --- a/Bugsnag/include/Bugsnag/BugsnagEvent.h +++ b/Bugsnag/include/Bugsnag/BugsnagEvent.h @@ -8,6 +8,7 @@ #import +#import #import #import @@ -34,6 +35,7 @@ typedef NS_ENUM(NSUInteger, BSGSeverity) { /** * Represents an occurrence of an error, along with information about the state of the app and device. */ +BUGSNAG_EXTERN @interface BugsnagEvent : NSObject // ----------------------------------------------------------------------------- diff --git a/Bugsnag/include/Bugsnag/BugsnagFeatureFlag.h b/Bugsnag/include/Bugsnag/BugsnagFeatureFlag.h index 8fdbc38d6..4bca6443b 100644 --- a/Bugsnag/include/Bugsnag/BugsnagFeatureFlag.h +++ b/Bugsnag/include/Bugsnag/BugsnagFeatureFlag.h @@ -8,8 +8,11 @@ #import +#import + NS_ASSUME_NONNULL_BEGIN +BUGSNAG_EXTERN @interface BugsnagFeatureFlag : NSObject + (instancetype)flagWithName:(NSString *)name; diff --git a/Bugsnag/include/Bugsnag/BugsnagLastRunInfo.h b/Bugsnag/include/Bugsnag/BugsnagLastRunInfo.h index 17c1b29ee..ea2372d84 100644 --- a/Bugsnag/include/Bugsnag/BugsnagLastRunInfo.h +++ b/Bugsnag/include/Bugsnag/BugsnagLastRunInfo.h @@ -8,11 +8,14 @@ #import +#import + NS_ASSUME_NONNULL_BEGIN /** * Contains information about the last run of the app. */ +BUGSNAG_EXTERN @interface BugsnagLastRunInfo : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagMetadata.h b/Bugsnag/include/Bugsnag/BugsnagMetadata.h index a19ab95cc..8e18e5283 100644 --- a/Bugsnag/include/Bugsnag/BugsnagMetadata.h +++ b/Bugsnag/include/Bugsnag/BugsnagMetadata.h @@ -26,11 +26,13 @@ #import +#import #import NS_ASSUME_NONNULL_BEGIN /// :nodoc: +BUGSNAG_EXTERN @interface BugsnagMetadata : NSObject - (instancetype)initWithDictionary:(NSDictionary *)dict; diff --git a/Bugsnag/include/Bugsnag/BugsnagSession.h b/Bugsnag/include/Bugsnag/BugsnagSession.h index 4365ebfab..766d5c953 100644 --- a/Bugsnag/include/Bugsnag/BugsnagSession.h +++ b/Bugsnag/include/Bugsnag/BugsnagSession.h @@ -9,6 +9,7 @@ #import #import +#import #import #import @@ -17,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Represents a session of user interaction with your app. */ +BUGSNAG_EXTERN @interface BugsnagSession : NSObject @property (copy, nonatomic) NSString *id; diff --git a/Bugsnag/include/Bugsnag/BugsnagStackframe.h b/Bugsnag/include/Bugsnag/BugsnagStackframe.h index 63d5b429f..18c057956 100644 --- a/Bugsnag/include/Bugsnag/BugsnagStackframe.h +++ b/Bugsnag/include/Bugsnag/BugsnagStackframe.h @@ -8,15 +8,18 @@ #import +#import + NS_ASSUME_NONNULL_BEGIN typedef NSString * BugsnagStackframeType NS_TYPED_ENUM; -FOUNDATION_EXPORT BugsnagStackframeType const BugsnagStackframeTypeCocoa; +BUGSNAG_EXTERN BugsnagStackframeType const BugsnagStackframeTypeCocoa; /** * Represents a single stackframe from a stacktrace. */ +BUGSNAG_EXTERN @interface BugsnagStackframe : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagThread.h b/Bugsnag/include/Bugsnag/BugsnagThread.h index 87f9205b1..14b5cb947 100644 --- a/Bugsnag/include/Bugsnag/BugsnagThread.h +++ b/Bugsnag/include/Bugsnag/BugsnagThread.h @@ -8,6 +8,8 @@ #import +#import + typedef NS_OPTIONS(NSUInteger, BSGThreadType) { BSGThreadTypeCocoa NS_SWIFT_NAME(cocoa) = 0, BSGThreadTypeReactNativeJs = 1 << 1 @@ -18,6 +20,7 @@ typedef NS_OPTIONS(NSUInteger, BSGThreadType) { /** * A representation of thread information recorded as part of a BugsnagEvent. */ +BUGSNAG_EXTERN @interface BugsnagThread : NSObject /** diff --git a/Bugsnag/include/Bugsnag/BugsnagUser.h b/Bugsnag/include/Bugsnag/BugsnagUser.h index 660ee3d36..c24561110 100644 --- a/Bugsnag/include/Bugsnag/BugsnagUser.h +++ b/Bugsnag/include/Bugsnag/BugsnagUser.h @@ -8,9 +8,12 @@ #import +#import + /** * Information about the current user of your application. */ +BUGSNAG_EXTERN @interface BugsnagUser : NSObject @property (readonly, nullable, nonatomic) NSString *id; diff --git a/Package.swift b/Package.swift index 86289ee10..1e5e0c48a 100644 --- a/Package.swift +++ b/Package.swift @@ -37,6 +37,7 @@ let package = Package( .headerSearchPath("Payload"), .headerSearchPath("Plugins"), .headerSearchPath("Storage"), + .unsafeFlags(["-fvisibility=hidden"]), ], linkerSettings: [ .linkedLibrary("z"), diff --git a/Tests/BugsnagTests/Tests-Bridging-Header.h b/Tests/BugsnagTests/Tests-Bridging-Header.h index 6097d80d0..c54081022 100644 --- a/Tests/BugsnagTests/Tests-Bridging-Header.h +++ b/Tests/BugsnagTests/Tests-Bridging-Header.h @@ -2,5 +2,6 @@ // Public headers exposed to Swift // -#import "BugsnagConfiguration.h" +#import + #import "BugsnagTestConstants.h" From eab1d722184ddb6fcad500ccfab59362141d5a71 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 26 Jul 2022 11:22:52 +0100 Subject: [PATCH 08/24] Export symbols used by bugsnag-flutter and react-native --- Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h | 6 ++++-- Bugsnag/Payload/BugsnagBreadcrumb+Private.h | 5 +++-- Bugsnag/Payload/BugsnagHandledState.h | 3 ++- Bugsnag/Payload/BugsnagNotifier.h | 2 ++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h index b1e4c8c0a..0ccfc536d 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.h @@ -24,6 +24,9 @@ // THE SOFTWARE. // +#import +#import + #define BSG_KSSystemField_AppUUID "app_uuid" #define BSG_KSSystemField_BinaryArch "binary_arch" #define BSG_KSSystemField_BundleID "CFBundleIdentifier" @@ -47,11 +50,10 @@ #define BSG_KSSystemField_Translated "proc_translated" #define BSG_KSSystemField_iOSSupportVersion "iOSSupportVersion" -#import - /** * Provides system information useful for a crash report. */ +BUGSNAG_EXTERN @interface BSG_KSSystemInfo : NSObject /** Get the system info. diff --git a/Bugsnag/Payload/BugsnagBreadcrumb+Private.h b/Bugsnag/Payload/BugsnagBreadcrumb+Private.h index e32d1de2c..99d8188d2 100644 --- a/Bugsnag/Payload/BugsnagBreadcrumb+Private.h +++ b/Bugsnag/Payload/BugsnagBreadcrumb+Private.h @@ -7,6 +7,7 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN @@ -23,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @end -FOUNDATION_EXPORT NSString *BSGBreadcrumbTypeValue(BSGBreadcrumbType type); -FOUNDATION_EXPORT BSGBreadcrumbType BSGBreadcrumbTypeFromString( NSString * _Nullable value); +BUGSNAG_EXTERN NSString * BSGBreadcrumbTypeValue(BSGBreadcrumbType type); +BUGSNAG_EXTERN BSGBreadcrumbType BSGBreadcrumbTypeFromString(NSString * _Nullable value); NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Payload/BugsnagHandledState.h b/Bugsnag/Payload/BugsnagHandledState.h index e9456895a..264573825 100644 --- a/Bugsnag/Payload/BugsnagHandledState.h +++ b/Bugsnag/Payload/BugsnagHandledState.h @@ -32,7 +32,7 @@ typedef NS_ENUM(NSUInteger, SeverityReasonType) { * @return converted severity level or BSGSeverityError if no conversion is * found */ -BSGSeverity BSGParseSeverity(NSString *severity); +BUGSNAG_EXTERN BSGSeverity BSGParseSeverity(NSString *severity); /** * Serialize a severity for JSON payloads @@ -43,6 +43,7 @@ BSGSeverity BSGParseSeverity(NSString *severity); */ NSString *BSGFormatSeverity(BSGSeverity severity); +BUGSNAG_EXTERN @interface BugsnagHandledState : NSObject @property(nonatomic) BOOL unhandled; diff --git a/Bugsnag/Payload/BugsnagNotifier.h b/Bugsnag/Payload/BugsnagNotifier.h index ad9b785f6..cfebabfca 100644 --- a/Bugsnag/Payload/BugsnagNotifier.h +++ b/Bugsnag/Payload/BugsnagNotifier.h @@ -6,10 +6,12 @@ // Copyright © 2020 Bugsnag. All rights reserved. // +#import #import NS_ASSUME_NONNULL_BEGIN +BUGSNAG_EXTERN @interface BugsnagNotifier : NSObject /// Initializes the object with details of the Cocoa notifier. From 0044a294e0180f4079081553d6066d0201bb0963 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Wed, 27 Jul 2022 15:40:37 +0100 Subject: [PATCH 09/24] Fix session order flake in UserNilScenario (#1448) --- features/steps/cocoa_steps.rb | 12 ------------ features/steps/reusable_steps.rb | 15 +++++++++++++++ features/user.feature | 12 ++++-------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/features/steps/cocoa_steps.rb b/features/steps/cocoa_steps.rb index e61b2b66e..3a40127d9 100644 --- a/features/steps/cocoa_steps.rb +++ b/features/steps/cocoa_steps.rb @@ -1,15 +1,3 @@ -def request_matches_row(body, row) - row.each do |key, expected_value| - obs_val = Maze::Helper.read_key_path(body, key) - next if ('null'.eql? expected_value) && obs_val.nil? # Both are null/nil - next if !obs_val.nil? && (expected_value.to_s.eql? obs_val.to_s) # Values match - # Match not found - return false - return false - end - # All matched - return true - true -end - Then('I wait for the fixture to process the response') do sleep 2 end diff --git a/features/steps/reusable_steps.rb b/features/steps/reusable_steps.rb index 4ec455998..1e980ae7c 100644 --- a/features/steps/reusable_steps.rb +++ b/features/steps/reusable_steps.rb @@ -125,3 +125,18 @@ contains = actual.each_cons(expected.length).to_a.include? expected Maze.check.true(contains, "Stacktrace methods #{actual} did not contain #{expected}") end + +Then('the received sessions match:') do |table| + requests = Maze::Server.sessions.remaining + match_count = 0 + + # iterate through each row in the table. exactly 1 request should match each row. + table.hashes.each do |row| + requests.each do |request| + sessions = request.dig(:body, 'sessions') + Maze.check.equal(1, sessions.length, 'Expected exactly one session per request') + match_count += 1 if Maze::Assertions::RequestSetAssertions.request_matches_row(sessions[0], row) + end + end + Maze.check.equal(requests.size, match_count, 'Unexpected number of requests matched the received payloads') +end diff --git a/features/user.feature b/features/user.feature index 68c50fb77..a657ab0a7 100644 --- a/features/user.feature +++ b/features/user.feature @@ -79,15 +79,11 @@ Feature: Reporting User Information Scenario: Setting the user ID to nil When I run "UserNilScenario" And I wait to receive 2 sessions + Then the received sessions match: + | user.id | user.email | user.name | + | @not_null | null | null | + | null | null | null | And I wait to receive an error - Then the session payload field "device.id" is stored as the value "device_id" - And the session payload field "sessions.0.user.id" equals the stored value "device_id" - And the session "user.email" is null - And the session "user.name" is null - And I discard the oldest session - And the session "user.id" is null - And the session "user.email" is null - And the session "user.name" is null And the event "user.id" is null And the event "user.email" is null And the event "user.name" is null From 0290cf605f6878ab9e701f7d7543557987639c5b Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 28 Jul 2022 11:06:16 +0100 Subject: [PATCH 10/24] Implement string truncation functions --- Bugsnag.xcodeproj/project.pbxproj | 10 +++ Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.h | 2 +- Bugsnag/Helpers/BSGSerialization.h | 21 ++++-- Bugsnag/Helpers/BSGSerialization.m | 34 +++++++++ Bugsnag/Payload/BugsnagEvent+Private.h | 4 +- Bugsnag/Payload/BugsnagEvent.m | 28 +++++++- Tests/BugsnagTests/BSGSerializationTests.m | 82 ++++++++++++++++++++++ Tests/BugsnagTests/BugsnagEventTests.m | 53 ++++++++++++++ 8 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 Tests/BugsnagTests/BSGSerializationTests.m diff --git a/Bugsnag.xcodeproj/project.pbxproj b/Bugsnag.xcodeproj/project.pbxproj index 28984bfe3..082d79853 100644 --- a/Bugsnag.xcodeproj/project.pbxproj +++ b/Bugsnag.xcodeproj/project.pbxproj @@ -723,6 +723,10 @@ 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 */; }; + 01F9FCB628929336005EDD8C /* BSGSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01F9FCB528929336005EDD8C /* BSGSerializationTests.m */; }; + 01F9FCB728929336005EDD8C /* BSGSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01F9FCB528929336005EDD8C /* BSGSerializationTests.m */; }; + 01F9FCB828929336005EDD8C /* BSGSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01F9FCB528929336005EDD8C /* BSGSerializationTests.m */; }; + 01F9FCB928929336005EDD8C /* BSGSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01F9FCB528929336005EDD8C /* BSGSerializationTests.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, ); }; }; @@ -1628,6 +1632,7 @@ 01DE903B26CEAF9E00455213 /* BSGUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSGUtilsTests.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 = ""; }; + 01F9FCB528929336005EDD8C /* BSGSerializationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGSerializationTests.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 = ""; }; @@ -2033,6 +2038,7 @@ 0163BF5825823D8D008DC28B /* BSGNotificationBreadcrumbsTests.m */, 008966C82486D43600DC48C2 /* BSGOutOfMemoryTests.m */, 0130DEF82880203A00E5953F /* BSGRunContextTests.m */, + 01F9FCB528929336005EDD8C /* BSGSerializationTests.m */, CB6419AA25A73E8C00613D25 /* BSGStorageMigratorTests.m */, 017DCF9A287422BB000ECB22 /* BSGTelemetryTests.m */, 01DE903B26CEAF9E00455213 /* BSGUtilsTests.m */, @@ -3236,6 +3242,7 @@ CBA2249B251E429C00B87416 /* TestSupport.m in Sources */, 008967332486D43700DC48C2 /* BugsnagClientTests.m in Sources */, 004E353F2487B3BD007FBAE4 /* BugsnagSwiftConfigurationTests.swift in Sources */, + 01F9FCB628929336005EDD8C /* BSGSerializationTests.m in Sources */, 008967542486D43700DC48C2 /* BugsnagOnCrashTest.m in Sources */, 008967152486D43700DC48C2 /* BugsnagCollectionsTests.m in Sources */, 01E8765E256684E700F4B70A /* URLSessionMock.m in Sources */, @@ -3395,6 +3402,7 @@ 0089671C2486D43700DC48C2 /* BugsnagSessionTest.m in Sources */, 008967AC2486D43700DC48C2 /* BSG_KSMachTests.m in Sources */, 0163BF5A25823D8D008DC28B /* BSGNotificationBreadcrumbsTests.m in Sources */, + 01F9FCB728929336005EDD8C /* BSGSerializationTests.m in Sources */, 01BDB1FD25DEBFB300A91FAF /* BSGEventUploadKSCrashReportOperationTests.m in Sources */, 00896A452486DBF000DC48C2 /* BugsnagConfigurationTests.m in Sources */, 008967492486D43700DC48C2 /* BugsnagUserTest.m in Sources */, @@ -3543,6 +3551,7 @@ E701FAAD2490EFD9008D842F /* EventApiValidationTest.m in Sources */, 008967472486D43700DC48C2 /* BugsnagTests.m in Sources */, 010993A6273D188B00128BBE /* BSGFeatureFlagStoreTests.m in Sources */, + 01F9FCB828929336005EDD8C /* BSGSerializationTests.m in Sources */, 008967A72486D43700DC48C2 /* KSString_Tests.m in Sources */, 0089671A2486D43700DC48C2 /* BugsnagErrorTest.m in Sources */, 008967172486D43700DC48C2 /* BugsnagCollectionsTests.m in Sources */, @@ -3843,6 +3852,7 @@ CB28F0A228294D4F003AB200 /* KSCrashReportWriterTests.m in Sources */, CB28F0D3282A4B91003AB200 /* BugsnagErrorTest.m in Sources */, CB28F0DE282A4BEE003AB200 /* BugsnagSessionTrackerStopTest.m in Sources */, + 01F9FCB928929336005EDD8C /* BSGSerializationTests.m in Sources */, CB28F0B328294DD0003AB200 /* BSGClientObserverTests.m in Sources */, CB28F0B128294D4F003AB200 /* KSCrashSentry_Tests.m in Sources */, CB28F0B228294D52003AB200 /* XCTestCase+KSCrash.m in Sources */, diff --git a/Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.h b/Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.h index 1657d4b64..3a0e8fdde 100644 --- a/Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.h +++ b/Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithConfiguration:(BugsnagConfiguration *)config; /** - * The breadcrumbs stored in memory. + * Returns an array of new objects representing the breadcrumbs stored in memory. */ @property (readonly, nonatomic) NSArray *breadcrumbs; diff --git a/Bugsnag/Helpers/BSGSerialization.h b/Bugsnag/Helpers/BSGSerialization.h index 70047c173..b06eb1314 100644 --- a/Bugsnag/Helpers/BSGSerialization.h +++ b/Bugsnag/Helpers/BSGSerialization.h @@ -1,8 +1,7 @@ -#ifndef BugsnagJSONSerializable_h -#define BugsnagJSONSerializable_h - #import +NS_ASSUME_NONNULL_BEGIN + /** Removes any values which would be rejected by NSJSONSerialization for documented reasons @@ -10,7 +9,7 @@ @param input an array @return a new array */ -NSArray *_Nonnull BSGSanitizeArray(NSArray *_Nonnull input); +NSArray * BSGSanitizeArray(NSArray *input); /** Removes any values which would be rejected by NSJSONSerialization for @@ -19,7 +18,7 @@ NSArray *_Nonnull BSGSanitizeArray(NSArray *_Nonnull input); @param input a dictionary @return a new dictionary */ -NSDictionary *_Nonnull BSGSanitizeDict(NSDictionary *_Nonnull input); +NSDictionary * BSGSanitizeDict(NSDictionary *input); /** Checks whether the base type would be accepted by the serialization process @@ -37,4 +36,14 @@ BOOL BSGIsSanitizedType(id _Nullable obj); */ id _Nullable BSGSanitizeObject(id _Nullable obj); -#endif +typedef struct _BSGTruncateContext { + NSUInteger maxLength; + NSUInteger strings; + NSUInteger length; +} BSGTruncateContext; + +NSString * BSGTruncateString(BSGTruncateContext *context, NSString *string); + +id BSGTruncateStrings(BSGTruncateContext *context, id object); + +NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Helpers/BSGSerialization.m b/Bugsnag/Helpers/BSGSerialization.m index 64751c377..4525a154a 100644 --- a/Bugsnag/Helpers/BSGSerialization.m +++ b/Bugsnag/Helpers/BSGSerialization.m @@ -58,3 +58,37 @@ id BSGSanitizeObject(id obj) { } return output; } + +NSString * BSGTruncateString(BSGTruncateContext *context, NSString *string) { + const NSUInteger inputLength = string.length; + if (inputLength <= context->maxLength) return string; + // Prevent chopping in the middle of a composed character sequence + NSRange range = [string rangeOfComposedCharacterSequenceAtIndex:context->maxLength]; + NSString *output = [string substringToIndex:range.location]; + NSUInteger count = inputLength - range.location; + context->strings++; + context->length += count; + return [output stringByAppendingFormat:@"\n***%lu CHARS TRUNCATED***", (unsigned long)count]; +} + +id BSGTruncateStrings(BSGTruncateContext *context, id object) { + if ([object isKindOfClass:[NSString class]]) { + return BSGTruncateString(context, object); + } + if ([object isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary *output = [NSMutableDictionary dictionaryWithCapacity:((NSDictionary *)object).count]; + for (NSString *key in (NSDictionary *)object) { + id value = ((NSDictionary *)object)[key]; + output[key] = BSGTruncateStrings(context, value); + } + return output; + } + if ([object isKindOfClass:[NSArray class]]) { + NSMutableArray *output = [NSMutableArray arrayWithCapacity:((NSArray *)object).count]; + for (id element in (NSArray *)object) { + [output addObject:BSGTruncateStrings(context, element)]; + } + return output; + } + return object; +} diff --git a/Bugsnag/Payload/BugsnagEvent+Private.h b/Bugsnag/Payload/BugsnagEvent+Private.h index c822eac74..c7f3c0095 100644 --- a/Bugsnag/Payload/BugsnagEvent+Private.h +++ b/Bugsnag/Payload/BugsnagEvent+Private.h @@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN /// An array of string representations of BSGErrorType describing the types of stackframe / stacktrace in this error. @property (readonly, nonatomic) NSArray *stacktraceTypes; -/// Usage telemetry info, from BSGTelemetryCreateUsage() +/// Usage telemetry info, from BSGTelemetryCreateUsage(), or nil if BSGTelemetryUsage is not enabled. @property (readwrite, nullable, nonatomic) NSDictionary *usage; @property (readwrite, nonnull, nonatomic) BugsnagUser *user; @@ -73,6 +73,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSDictionary *)toJsonWithRedactedKeys:(nullable NSSet *)redactedKeys; +- (void)truncateStrings:(NSUInteger)maxLength; + - (void)notifyUnhandledOverridden; @end diff --git a/Bugsnag/Payload/BugsnagEvent.m b/Bugsnag/Payload/BugsnagEvent.m index 76b219580..46559fe83 100644 --- a/Bugsnag/Payload/BugsnagEvent.m +++ b/Bugsnag/Payload/BugsnagEvent.m @@ -8,6 +8,7 @@ #import "BugsnagEvent+Private.h" +#import "BSGDefines.h" #import "BSGFeatureFlagStore.h" #import "BSGKeys.h" #import "BSGSerialization.h" @@ -31,7 +32,6 @@ #import "BugsnagStacktrace.h" #import "BugsnagThread+Private.h" #import "BugsnagUser+Private.h" -#import "BSGDefines.h" static NSString * const RedactedMetadataValue = @"[REDACTED]"; @@ -690,6 +690,32 @@ - (void)symbolicateIfNeeded { } } +- (void)truncateStrings:(NSUInteger)maxLength { + BSGTruncateContext context = { + .maxLength = maxLength + }; + + for (BugsnagBreadcrumb *breadcrumb in self.breadcrumbs) { + breadcrumb.message = BSGTruncateString(&context, breadcrumb.message); + breadcrumb.metadata = BSGTruncateStrings(&context, breadcrumb.metadata); + } + + BugsnagMetadata *metadata = self.metadata; + if (metadata) { + self.metadata = [[BugsnagMetadata alloc] initWithDictionary: + BSGTruncateStrings(&context, metadata.dictionary)]; + } + + NSDictionary *usage = self.usage; + if (usage) { + self.usage = BSGDictMerge(@{ + @"system": @{ + @"stringCharsTruncated": @(context.length), + @"stringsTruncated": @(context.strings)} + }, usage); + } +} + - (BOOL)unhandled { return self.handledState.unhandled; } diff --git a/Tests/BugsnagTests/BSGSerializationTests.m b/Tests/BugsnagTests/BSGSerializationTests.m new file mode 100644 index 000000000..3f23d0149 --- /dev/null +++ b/Tests/BugsnagTests/BSGSerializationTests.m @@ -0,0 +1,82 @@ +// +// BSGSerializationTests.m +// Bugsnag +// +// Created by Nick Dowell on 28/07/2022. +// Copyright © 2022 Bugsnag Inc. All rights reserved. +// + +#import + +#import "BSGSerialization.h" + +@interface BSGSerializationTests : XCTestCase + +@end + +@implementation BSGSerializationTests + +- (void)testTruncateString { + BSGTruncateContext context = {0}; + + context.maxLength = NSUIntegerMax; + XCTAssertEqualObjects(BSGTruncateString(&context, @"Hello, world!"), @"Hello, world!"); + XCTAssertEqual(context.strings, 0); + XCTAssertEqual(context.length, 0); + + context.maxLength = 5; + XCTAssertEqualObjects(BSGTruncateString(&context, @"Hello, world!"), @"Hello" + "\n***8 CHARS TRUNCATED***"); + XCTAssertEqual(context.strings, 1); + XCTAssertEqual(context.length, 8); + + // Verify that emoji (composed character sequences) are not partially truncated + // Note when adding tests that older OSes like iOS 9 don't understand more recently + // added emoji like 🏴󠁧󠁢󠁥󠁮󠁧󠁿 and 👩🏾‍🚀 and therefore won't be able to avoid slicing them. + + context.maxLength = 10; + XCTAssertEqualObjects(BSGTruncateString(&context, @"Emoji: 👍🏾"), @"Emoji: " + "\n***4 CHARS TRUNCATED***"); + XCTAssertEqual(context.strings, 2); + XCTAssertEqual(context.length, 12); +} + +- (void)testTruncateStringsWithString { + BSGTruncateContext context = (BSGTruncateContext){.maxLength = 3}; + XCTAssertEqualObjects(BSGTruncateStrings(&context, @"foo bar"), @"foo" + "\n***4 CHARS TRUNCATED***"); + XCTAssertEqual(context.strings, 1); + XCTAssertEqual(context.length, 4); +} + +- (void)testTruncateStringsWithArray { + BSGTruncateContext context = (BSGTruncateContext){.maxLength = 3}; + XCTAssertEqualObjects(BSGTruncateStrings(&context, @[@"foo bar"]), + @[@"foo" + "\n***4 CHARS TRUNCATED***"]); + XCTAssertEqual(context.strings, 1); + XCTAssertEqual(context.length, 4); +} + +- (void)testTruncateStringsWithObject { + BSGTruncateContext context = (BSGTruncateContext){.maxLength = 3}; + XCTAssertEqualObjects(BSGTruncateStrings(&context, @{@"name": @"foo bar"}), + @{@"name": @"foo" + "\n***4 CHARS TRUNCATED***"}); + XCTAssertEqual(context.strings, 1); + XCTAssertEqual(context.length, 4); +} + +- (void)testTruncateStringsWithNestedObjects { + BSGTruncateContext context = (BSGTruncateContext){.maxLength = 3}; + XCTAssertEqualObjects(BSGTruncateStrings(&context, (@{@"one": @{@"key": @"foo bar"}, + @"two": @{@"foo": @"Baa, Baa, Black Sheep"}})), + (@{@"one": @{@"key": @"foo" + "\n***4 CHARS TRUNCATED***"}, + @"two": @{@"foo": @"Baa" + "\n***18 CHARS TRUNCATED***"}})); + XCTAssertEqual(context.strings, 2); + XCTAssertEqual(context.length, 22); +} + +@end diff --git a/Tests/BugsnagTests/BugsnagEventTests.m b/Tests/BugsnagTests/BugsnagEventTests.m index 0e337604d..e163f74a1 100644 --- a/Tests/BugsnagTests/BugsnagEventTests.m +++ b/Tests/BugsnagTests/BugsnagEventTests.m @@ -321,6 +321,58 @@ - (void)testJsonToEventToJson { } } +- (void)testTruncateStrings { + BugsnagEvent *event = [BugsnagEvent new]; + + BugsnagBreadcrumb * (^ MakeBreadcrumb)() = ^(NSString *message) { + BugsnagBreadcrumb *breadcrumb = [BugsnagBreadcrumb new]; + breadcrumb.message = message; + breadcrumb.metadata = @{@"string": message}; + return breadcrumb; + }; + + event.breadcrumbs = @[ + MakeBreadcrumb(@"Lorem ipsum dolor si" + "t amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."), + + MakeBreadcrumb(@"Lorem ipsum is place" + "holder text commonly used in the graphic, print, and publishing industries for previewing layouts and visual mockups."), + + MakeBreadcrumb(@"20 characters string")]; + + event.metadata = [[BugsnagMetadata alloc] initWithDictionary:@{}]; + [event addMetadata:@"From its medieval or" + "igins to the digital era, learn everything there is to know about the ubiquitous lorem ipsum passage." + withKey:@"name" toSection:@"test"]; + + event.usage = @{}; // Enable gathering telemetry + + [event truncateStrings:20]; + + XCTAssertEqualObjects([event.usage valueForKeyPath:@"system.stringsTruncated"], @5); + + XCTAssertEqualObjects([event.usage valueForKeyPath:@"system.stringCharsTruncated"], @(103 + 103 + 117 + 117 + 101)); + + XCTAssertEqualObjects(event.breadcrumbs[0].message, @"Lorem ipsum dolor si" + "\n***103 CHARS TRUNCATED***"); + + XCTAssertEqualObjects(event.breadcrumbs[0].metadata[@"string"], @"Lorem ipsum dolor si" + "\n***103 CHARS TRUNCATED***"); + + XCTAssertEqualObjects(event.breadcrumbs[1].message, @"Lorem ipsum is place" + "\n***117 CHARS TRUNCATED***"); + + XCTAssertEqualObjects(event.breadcrumbs[1].metadata[@"string"], @"Lorem ipsum is place" + "\n***117 CHARS TRUNCATED***"); + + XCTAssertEqualObjects(event.breadcrumbs[2].message, @"20 characters string"); + + XCTAssertEqualObjects(event.breadcrumbs[2].metadata[@"string"], @"20 characters string"); + + XCTAssertEqualObjects([event getMetadataFromSection:@"test" withKey:@"name"], @"From its medieval or" + "\n***101 CHARS TRUNCATED***"); +} + // MARK: - Feature flags interface - (void)testFeatureFlags { @@ -679,4 +731,5 @@ - (void)testRuntimeVersionsUnhandled { }; XCTAssertEqualObjects(expected, event.device.runtimeVersions); } + @end From 89834ce7ecc617056235160a68ddfd4ab6a163f3 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 28 Jul 2022 11:50:22 +0100 Subject: [PATCH 11/24] Clean up BSGSanitize* functions --- Bugsnag/Helpers/BSGSerialization.h | 23 ++----------- Bugsnag/Helpers/BSGSerialization.m | 37 ++++++-------------- Bugsnag/Metadata/BugsnagMetadata.m | 40 +--------------------- Tests/BugsnagTests/BSGSerializationTests.m | 15 ++++++++ 4 files changed, 30 insertions(+), 85 deletions(-) diff --git a/Bugsnag/Helpers/BSGSerialization.h b/Bugsnag/Helpers/BSGSerialization.h index b06eb1314..0f3c85d0f 100644 --- a/Bugsnag/Helpers/BSGSerialization.h +++ b/Bugsnag/Helpers/BSGSerialization.h @@ -4,35 +4,18 @@ NS_ASSUME_NONNULL_BEGIN /** Removes any values which would be rejected by NSJSONSerialization for - documented reasons - - @param input an array - @return a new array - */ -NSArray * BSGSanitizeArray(NSArray *input); - -/** - Removes any values which would be rejected by NSJSONSerialization for - documented reasons + documented reasons or is NSNull @param input a dictionary @return a new dictionary */ -NSDictionary * BSGSanitizeDict(NSDictionary *input); - -/** - Checks whether the base type would be accepted by the serialization process - - @param obj any object or nil - @return YES if the object is an Array, Dictionary, String, Number, or NSNull - */ -BOOL BSGIsSanitizedType(id _Nullable obj); +NSMutableDictionary * BSGSanitizeDict(NSDictionary *input); /** Cleans the object, including nested dictionary and array values @param obj any object or nil - @return a new object for serialization or nil if the obj was incompatible + @return a new object for serialization or nil if the obj was incompatible or NSNull */ id _Nullable BSGSanitizeObject(id _Nullable obj); diff --git a/Bugsnag/Helpers/BSGSerialization.m b/Bugsnag/Helpers/BSGSerialization.m index 4525a154a..135611ef1 100644 --- a/Bugsnag/Helpers/BSGSerialization.m +++ b/Bugsnag/Helpers/BSGSerialization.m @@ -1,41 +1,26 @@ #import "BSGSerialization.h" -#import "BugsnagLogger.h" + #import "BSGJSONSerialization.h" +#import "BugsnagLogger.h" -BOOL BSGIsSanitizedType(id obj) { - static dispatch_once_t onceToken; - static NSArray *allowedTypes = nil; - dispatch_once(&onceToken, ^{ - allowedTypes = @[ - [NSArray class], [NSDictionary class], [NSNull class], - [NSNumber class], [NSString class] - ]; - }); - - for (Class klass in allowedTypes) { - if ([obj isKindOfClass:klass]) - return YES; - } - return NO; -} +static NSArray * BSGSanitizeArray(NSArray *input); id BSGSanitizeObject(id obj) { - if ([obj isKindOfClass:[NSNumber class]]) { - NSNumber *number = obj; - if (![number isEqualToNumber:[NSDecimalNumber notANumber]] && - !isinf([number doubleValue])) - return obj; - } else if ([obj isKindOfClass:[NSArray class]]) { + if ([obj isKindOfClass:[NSArray class]]) { return BSGSanitizeArray(obj); } else if ([obj isKindOfClass:[NSDictionary class]]) { return BSGSanitizeDict(obj); - } else if (BSGIsSanitizedType(obj)) { + } else if ([obj isKindOfClass:[NSString class]]) { + return obj; + } else if ([obj isKindOfClass:[NSNumber class]] + && ![obj isEqualToNumber:[NSDecimalNumber notANumber]] + && !isinf([obj doubleValue])) { return obj; } return nil; } -NSDictionary *_Nonnull BSGSanitizeDict(NSDictionary *input) { +NSMutableDictionary * BSGSanitizeDict(NSDictionary *input) { __block NSMutableDictionary *output = [NSMutableDictionary dictionaryWithCapacity:[input count]]; [input enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, @@ -49,7 +34,7 @@ id BSGSanitizeObject(id obj) { return output; } -NSArray *BSGSanitizeArray(NSArray *input) { +static NSArray * BSGSanitizeArray(NSArray *input) { NSMutableArray *output = [NSMutableArray arrayWithCapacity:[input count]]; for (id obj in input) { id cleanedObject = BSGSanitizeObject(obj); diff --git a/Bugsnag/Metadata/BugsnagMetadata.m b/Bugsnag/Metadata/BugsnagMetadata.m index 092fc2856..88b56f2a6 100644 --- a/Bugsnag/Metadata/BugsnagMetadata.m +++ b/Bugsnag/Metadata/BugsnagMetadata.m @@ -56,7 +56,7 @@ - (instancetype)initWithDictionary:(NSDictionary *)dict { if ((self = [super init])) { // Ensure that the instantiating dictionary is mutable. // Saves checks later. - _dictionary = [self sanitizeDictionary:dict]; + _dictionary = BSGSanitizeDict(dict); self.stateEventBlocks = [NSMutableArray new]; } if (self.observer) { @@ -65,44 +65,6 @@ - (instancetype)initWithDictionary:(NSDictionary *)dict { return self; } -/** - * Sanitizes the given dictionary to prevent [NSNull null] values from being added - * to the metadata when deserializing a payload. - * - * @param dictionary the input dictionary - * @return a sanitized dictionary - */ -- (NSMutableDictionary *)sanitizeDictionary:(NSDictionary *)dictionary { - NSMutableDictionary *input = [dictionary mutableCopy]; - - for (NSString *key in [input allKeys]) { - id obj = input[key]; - - if (obj == [NSNull null]) { - [input removeObjectForKey:key]; - } else if ([obj isKindOfClass:[NSDictionary class]]) { - input[key] = [self sanitizeDictionary:obj]; - } else if ([obj isKindOfClass:[NSArray class]]) { - input[key] = [self sanitizeArray:obj]; - } - } - return input; -} - -- (NSMutableArray *)sanitizeArray:(NSArray *)obj { - NSMutableArray *ary = [obj mutableCopy]; - [ary removeObject:[NSNull null]]; - - for (NSUInteger k = 0; k < [ary count]; ++k) { - if ([ary[k] isKindOfClass:[NSDictionary class]]) { - ary[k] = [self sanitizeDictionary:ary[k]]; - } else if ([ary[k] isKindOfClass:[NSArray class]]) { - ary[k] = [self sanitizeArray:ary[k]]; - } - } - return ary; -} - - (NSDictionary *)toDictionary { @synchronized (self) { return [self.dictionary mutableCopy]; diff --git a/Tests/BugsnagTests/BSGSerializationTests.m b/Tests/BugsnagTests/BSGSerializationTests.m index 3f23d0149..c36621781 100644 --- a/Tests/BugsnagTests/BSGSerializationTests.m +++ b/Tests/BugsnagTests/BSGSerializationTests.m @@ -16,6 +16,21 @@ @interface BSGSerializationTests : XCTestCase @implementation BSGSerializationTests +- (void)testSanitizeObject { + XCTAssertEqualObjects(BSGSanitizeObject(@""), @""); + XCTAssertEqualObjects(BSGSanitizeObject(@42), @42); + XCTAssertEqualObjects(BSGSanitizeObject(@[@42]), @[@42]); + XCTAssertEqualObjects(BSGSanitizeObject(@[self]), @[]); + XCTAssertEqualObjects(BSGSanitizeObject(@{@"a": @"b"}), @{@"a": @"b"}); + XCTAssertEqualObjects(BSGSanitizeObject(@{@"self": self}), @{}); + XCTAssertNil(BSGSanitizeObject(@(INFINITY))); + XCTAssertNil(BSGSanitizeObject(@(NAN))); + XCTAssertNil(BSGSanitizeObject([NSDate date])); + XCTAssertNil(BSGSanitizeObject([NSDecimalNumber notANumber])); + XCTAssertNil(BSGSanitizeObject([NSNull null])); + XCTAssertNil(BSGSanitizeObject(self)); +} + - (void)testTruncateString { BSGTruncateContext context = {0}; From 063ef29a502c505a1e39e9ea53d7e438843fadd7 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 28 Jul 2022 12:04:43 +0100 Subject: [PATCH 12/24] Add maxStringValueLength configuration property --- Bugsnag/Configuration/BugsnagConfiguration.m | 2 ++ Bugsnag/include/Bugsnag/BugsnagConfiguration.h | 9 +++++++++ Tests/BugsnagTests/BugsnagConfigurationTests.m | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/Bugsnag/Configuration/BugsnagConfiguration.m b/Bugsnag/Configuration/BugsnagConfiguration.m index 5f4c93b8c..a69d3e8a2 100644 --- a/Bugsnag/Configuration/BugsnagConfiguration.m +++ b/Bugsnag/Configuration/BugsnagConfiguration.m @@ -94,6 +94,7 @@ - (nonnull id)copyWithZone:(nullable NSZone *)zone { [copy setSendLaunchCrashesSynchronously:self.sendLaunchCrashesSynchronously]; [copy setMaxPersistedEvents:self.maxPersistedEvents]; [copy setMaxPersistedSessions:self.maxPersistedSessions]; + [copy setMaxStringValueLength:self.maxStringValueLength]; [copy setMaxBreadcrumbs:self.maxBreadcrumbs]; [copy setNotifier:self.notifier]; [copy setFeatureFlagStore:self.featureFlagStore]; @@ -189,6 +190,7 @@ - (instancetype)initWithApiKey:(NSString *)apiKey { _maxBreadcrumbs = 50; _maxPersistedEvents = 32; _maxPersistedSessions = 128; + _maxStringValueLength = 10000; _autoTrackSessions = YES; #if BSG_HAVE_MACH_THREADS _sendThreads = BSGThreadSendPolicyAlways; diff --git a/Bugsnag/include/Bugsnag/BugsnagConfiguration.h b/Bugsnag/include/Bugsnag/BugsnagConfiguration.h index 60b921451..7bdf59201 100644 --- a/Bugsnag/include/Bugsnag/BugsnagConfiguration.h +++ b/Bugsnag/include/Bugsnag/BugsnagConfiguration.h @@ -334,6 +334,15 @@ BUGSNAG_EXTERN */ @property (nonatomic) NSUInteger maxBreadcrumbs; +/** + * The maximum length of breadcrumb messages and metadata string values. + * + * Values longer than this will be truncated prior to sending, after running any OnSendError blocks. + * + * The default value is 10000. + */ +@property (nonatomic) NSUInteger maxStringValueLength; + /** * Whether User information should be persisted to disk between application runs. * Defaults to True. diff --git a/Tests/BugsnagTests/BugsnagConfigurationTests.m b/Tests/BugsnagTests/BugsnagConfigurationTests.m index ed9c31937..2f7108f02 100644 --- a/Tests/BugsnagTests/BugsnagConfigurationTests.m +++ b/Tests/BugsnagTests/BugsnagConfigurationTests.m @@ -650,6 +650,7 @@ - (void)testDefaultConfigurationValues { XCTAssertEqualObjects(@"https://notify.bugsnag.com", config.endpoints.notify); XCTAssertEqualObjects(@"https://sessions.bugsnag.com", config.endpoints.sessions); XCTAssertEqual(50, config.maxBreadcrumbs); + XCTAssertEqual(config.maxStringValueLength, 10000); XCTAssertTrue(config.persistUser); XCTAssertEqual(1, [config.redactedKeys count]); XCTAssertEqualObjects(@"password", [config.redactedKeys allObjects][0]); @@ -872,6 +873,7 @@ - (void)testNSCopying { #if !TARGET_OS_WATCH [config setSendThreads:BSGThreadSendPolicyUnhandledOnly]; #endif + [config setMaxStringValueLength:100]; [config addPlugin:(id)[NSNull null]]; BugsnagOnSendErrorBlock onSendBlock1 = ^BOOL(BugsnagEvent * _Nonnull event) { return true; }; @@ -928,6 +930,8 @@ - (void)testNSCopying { // Plugins XCTAssert([clone.plugins containsObject:[NSNull null]]); XCTAssertNoThrow([clone.plugins removeObject:[NSNull null]]); + + XCTAssertEqual(clone.maxStringValueLength, 100); } - (void)testMetadataMutability { From 4597436740eb4aea24771997353ee02feb83493b Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 28 Jul 2022 14:34:43 +0100 Subject: [PATCH 13/24] Truncate breadcrumb and event metadata strings --- Bugsnag/Delivery/BSGEventUploadOperation.m | 4 ++++ CHANGELOG.md | 3 +++ features/barebone_tests.feature | 3 +++ .../shared/scenarios/BareboneTestHandledScenario.swift | 10 ++++++++++ 4 files changed, 20 insertions(+) diff --git a/Bugsnag/Delivery/BSGEventUploadOperation.m b/Bugsnag/Delivery/BSGEventUploadOperation.m index 33b047ca4..3c9e4b002 100644 --- a/Bugsnag/Delivery/BSGEventUploadOperation.m +++ b/Bugsnag/Delivery/BSGEventUploadOperation.m @@ -95,9 +95,13 @@ - (void)runWithDelegate:(id)delegate completion NSDictionary *eventPayload; @try { + [event truncateStrings:configuration.maxStringValueLength]; eventPayload = [event toJsonWithRedactedKeys:configuration.redactedKeys]; } @catch (NSException *exception) { bsg_log_err(@"Discarding event %@ because an exception was thrown by -toJsonWithRedactedKeys: %@", self.name, exception); + [BSGInternalErrorReporter.sharedInstance reportException:exception diagnostics:nil groupingHash: + [NSString stringWithFormat:@"BSGEventUploadOperation -[runWithDelegate:completionHandler:] %@ %@", + exception.name, exception.reason]]; [self deleteEvent]; completionHandler(); return; diff --git a/CHANGELOG.md b/CHANGELOG.md index 829ee6a1c..5aaebdade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Changelog ### Enhancements +* Truncate breadcrumb and metadata strings that are longer than `configuration.maxStringValueLength`. + [#1449](https://github.com/bugsnag/bugsnag-cocoa/pull/1449) + * Add `+[BugsnagStackframe stackframesWithCallStackReturnAddresses:]` to public headers. [#1446](https://github.com/bugsnag/bugsnag-cocoa/pull/1446) diff --git a/features/barebone_tests.feature b/features/barebone_tests.feature index ddb4dda96..d041456e1 100644 --- a/features/barebone_tests.feature +++ b/features/barebone_tests.feature @@ -63,6 +63,7 @@ Feature: Barebone tests And the event "metaData.Exception.info" equals "Some error specific information" And the event "metaData.Flags.Testing" is true And the event "metaData.Other.password" equals "[REDACTED]" + And the event "metaData.Other.shouldBeTruncated" matches "\n\*\*\*345 CHARS TRUNCATED\*\*\*" And the event "metaData.error.nsexception.name" equals "NSRangeException" And the event "metaData.error.nsexception.userInfo.date" equals "2001-01-01 00:00:00 +0000" And the event "metaData.error.nsexception.userInfo.NSUnderlyingError" matches "Error Domain=ErrorDomain Code=0" @@ -88,6 +89,8 @@ Feature: Barebone tests | ios | true | | macos | @null | | watchos | @null | + And the event "usage.system.stringCharsTruncated" equals 345 + And the event "usage.system.stringsTruncated" equals 1 And the event "user.email" equals "foobar@example.com" And the event "user.id" equals "foobar" And the event "user.name" equals "Foo Bar" diff --git a/features/fixtures/shared/scenarios/BareboneTestHandledScenario.swift b/features/fixtures/shared/scenarios/BareboneTestHandledScenario.swift index b6a06cc58..763a83822 100644 --- a/features/fixtures/shared/scenarios/BareboneTestHandledScenario.swift +++ b/features/fixtures/shared/scenarios/BareboneTestHandledScenario.swift @@ -42,6 +42,7 @@ class BareboneTestHandledScenario: Scenario { config.addMetadata(["Testing": true], section: "Flags") config.addMetadata(["password": "123456"], section: "Other") config.launchDurationMillis = 0 + config.maxStringValueLength = 100 #if !os(watchOS) config.sendThreads = .unhandledOnly #endif @@ -78,6 +79,15 @@ class BareboneTestHandledScenario: Scenario { self.afterSendErrorBlock = self.afterSendError + Bugsnag.addMetadata(""" + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \ + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \ + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \ + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu \ + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in \ + culpa qui officia deserunt mollit anim id est laborum. + """, key: "shouldBeTruncated", section: "Other") + Bugsnag.notify(NSException(name: .rangeException, reason: "-[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]", userInfo: ["date": Date(timeIntervalSinceReferenceDate: 0), From 90ccc930a2c419f6055d6b8189329aaafe078eb4 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Thu, 28 Jul 2022 14:57:46 +0100 Subject: [PATCH 14/24] Fix oversized E2E scenarios --- .../fixtures/shared/scenarios/OversizedCrashReportScenario.swift | 1 + .../shared/scenarios/OversizedHandledErrorScenario.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/features/fixtures/shared/scenarios/OversizedCrashReportScenario.swift b/features/fixtures/shared/scenarios/OversizedCrashReportScenario.swift index a1478eaf1..57dfd6e78 100644 --- a/features/fixtures/shared/scenarios/OversizedCrashReportScenario.swift +++ b/features/fixtures/shared/scenarios/OversizedCrashReportScenario.swift @@ -3,6 +3,7 @@ class OversizedCrashReportScenario: Scenario { override func startBugsnag() { config.autoTrackSessions = false config.enabledErrorTypes.ooms = false + config.maxStringValueLength = UInt.max config.addOnSendError { var data = Data(count: 1024 * 1024) _ = data.withUnsafeMutableBytes { diff --git a/features/fixtures/shared/scenarios/OversizedHandledErrorScenario.swift b/features/fixtures/shared/scenarios/OversizedHandledErrorScenario.swift index efd783a24..414e6251c 100644 --- a/features/fixtures/shared/scenarios/OversizedHandledErrorScenario.swift +++ b/features/fixtures/shared/scenarios/OversizedHandledErrorScenario.swift @@ -3,6 +3,7 @@ class OversizedHandledErrorScenario: Scenario { override func startBugsnag() { config.autoTrackSessions = false config.enabledErrorTypes.ooms = false + config.maxStringValueLength = UInt.max config.addOnSendError { var data = Data(count: 1024 * 1024) _ = data.withUnsafeMutableBytes { From 1326b071d500e3edd2a8449b2ad3e4f90b14cce4 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Mon, 8 Aug 2022 10:21:39 +0100 Subject: [PATCH 15/24] Consolidate "Bugsnag-Sent-At" header --- .../BugsnagConfiguration+Private.h | 2 -- Bugsnag/Configuration/BugsnagConfiguration.m | 8 ------ Bugsnag/Delivery/BSGEventUploadOperation.m | 2 -- Bugsnag/Delivery/BSGSessionUploader.m | 3 +- Bugsnag/Delivery/BugsnagApiClient.m | 28 +++++++------------ .../BugsnagTests/BugsnagConfigurationTests.m | 8 ------ 6 files changed, 11 insertions(+), 40 deletions(-) diff --git a/Bugsnag/Configuration/BugsnagConfiguration+Private.h b/Bugsnag/Configuration/BugsnagConfiguration+Private.h index e17c863c6..4a0e7b83d 100644 --- a/Bugsnag/Configuration/BugsnagConfiguration+Private.h +++ b/Bugsnag/Configuration/BugsnagConfiguration+Private.h @@ -46,8 +46,6 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, nonatomic) BOOL shouldSendReports; -@property (readonly, nonatomic) NSDictionary *sessionApiHeaders; - @property (readonly, nullable, nonatomic) NSURL *sessionURL; @property (readwrite, retain, nonnull, nonatomic) BugsnagUser *user; diff --git a/Bugsnag/Configuration/BugsnagConfiguration.m b/Bugsnag/Configuration/BugsnagConfiguration.m index a69d3e8a2..f74d47f4e 100644 --- a/Bugsnag/Configuration/BugsnagConfiguration.m +++ b/Bugsnag/Configuration/BugsnagConfiguration.m @@ -28,7 +28,6 @@ #import "BSGConfigurationBuilder.h" #import "BSGKeys.h" -#import "BSG_RFC3339DateTool.h" #import "BugsnagApiClient.h" #import "BugsnagEndpointConfiguration.h" #import "BugsnagErrorTypes.h" @@ -361,13 +360,6 @@ - (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock)block { // MARK: - // ============================================================================= -- (NSDictionary *)sessionApiHeaders { - return @{BugsnagHTTPHeaderNameApiKey: self.apiKey ?: @"", - BugsnagHTTPHeaderNamePayloadVersion: @"1.0", - BugsnagHTTPHeaderNameSentAt: [BSG_RFC3339DateTool stringFromDate:[NSDate date]] - }; -} - - (void)setEndpoints:(BugsnagEndpointConfiguration *)endpoints { if ([self isValidURLString:endpoints.notify]) { _endpoints.notify = [endpoints.notify copy]; diff --git a/Bugsnag/Delivery/BSGEventUploadOperation.m b/Bugsnag/Delivery/BSGEventUploadOperation.m index 3c9e4b002..e1d8cba7f 100644 --- a/Bugsnag/Delivery/BSGEventUploadOperation.m +++ b/Bugsnag/Delivery/BSGEventUploadOperation.m @@ -11,7 +11,6 @@ #import "BSGFileLocations.h" #import "BSGInternalErrorReporter.h" #import "BSGKeys.h" -#import "BSG_RFC3339DateTool.h" #import "BugsnagAppWithState+Private.h" #import "BugsnagConfiguration+Private.h" #import "BugsnagError+Private.h" @@ -123,7 +122,6 @@ - (void)runWithDelegate:(id)delegate completion NSMutableDictionary *requestHeaders = [NSMutableDictionary dictionary]; requestHeaders[BugsnagHTTPHeaderNameApiKey] = apiKey; requestHeaders[BugsnagHTTPHeaderNamePayloadVersion] = EventPayloadVersion; - requestHeaders[BugsnagHTTPHeaderNameSentAt] = [BSG_RFC3339DateTool stringFromDate:[NSDate date]]; requestHeaders[BugsnagHTTPHeaderNameStacktraceTypes] = [event.stacktraceTypes componentsJoinedByString:@","]; NSURL *notifyURL = configuration.notifyURL; diff --git a/Bugsnag/Delivery/BSGSessionUploader.m b/Bugsnag/Delivery/BSGSessionUploader.m index 14893bfc1..b54375799 100644 --- a/Bugsnag/Delivery/BSGSessionUploader.m +++ b/Bugsnag/Delivery/BSGSessionUploader.m @@ -152,8 +152,7 @@ - (void)sendSession:(BugsnagSession *)session completionHandler:(nonnull void (^ NSDictionary *headers = @{ BugsnagHTTPHeaderNameApiKey: apiKey, - BugsnagHTTPHeaderNamePayloadVersion: @"1.0", - BugsnagHTTPHeaderNameSentAt: [BSG_RFC3339DateTool stringFromDate:[NSDate date]] ?: [NSNull null] + BugsnagHTTPHeaderNamePayloadVersion: @"1.0" }; NSDictionary *payload = @{ diff --git a/Bugsnag/Delivery/BugsnagApiClient.m b/Bugsnag/Delivery/BugsnagApiClient.m index 1673dcdd0..6e166ea27 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.m +++ b/Bugsnag/Delivery/BugsnagApiClient.m @@ -7,6 +7,7 @@ #import "BSGJSONSerialization.h" #import "BSGKeys.h" +#import "BSG_RFC3339DateTool.h" #import "Bugsnag.h" #import "BugsnagConfiguration.h" #import "BugsnagLogger.h" @@ -58,10 +59,16 @@ - (NSData *)sendJSONPayload:(NSDictionary *)payload return nil; } - NSMutableDictionary *mutableHeaders = [headers mutableCopy]; - mutableHeaders[BugsnagHTTPHeaderNameIntegrity] = [NSString stringWithFormat:@"sha1 %@", [BugsnagApiClient SHA1HashStringWithData:data]]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15]; + request.HTTPMethod = @"POST"; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + [request setValue:[NSString stringWithFormat:@"sha1 %@", [BugsnagApiClient SHA1HashStringWithData:data]] forHTTPHeaderField:BugsnagHTTPHeaderNameIntegrity]; + [request setValue:[BSG_RFC3339DateTool stringFromDate:[NSDate date]] forHTTPHeaderField:BugsnagHTTPHeaderNameSentAt]; + + for (BugsnagHTTPHeaderName name in headers) { + [request setValue:headers[name] forHTTPHeaderField:name]; + } - NSMutableURLRequest *request = [self prepareRequest:url headers:mutableHeaders]; bsg_log_debug(@"Sending %lu byte payload to %@", (unsigned long)data.length, url); [[self.session uploadTaskWithRequest:request fromData:data completionHandler:^(__unused NSData *responseData, @@ -105,21 +112,6 @@ - (NSData *)sendJSONPayload:(NSDictionary *)payload return data; } -- (NSMutableURLRequest *)prepareRequest:(NSURL *)url - headers:(NSDictionary *)headers { - NSMutableURLRequest *request = [NSMutableURLRequest - requestWithURL:url - cachePolicy:NSURLRequestReloadIgnoringLocalCacheData - timeoutInterval:15]; - request.HTTPMethod = @"POST"; - [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - - for (NSString *key in [headers allKeys]) { - [request setValue:headers[key] forHTTPHeaderField:key]; - } - return request; -} - + (NSString *)SHA1HashStringWithData:(NSData *)data { if (!data) { return nil; diff --git a/Tests/BugsnagTests/BugsnagConfigurationTests.m b/Tests/BugsnagTests/BugsnagConfigurationTests.m index 2f7108f02..81ac1f8a6 100644 --- a/Tests/BugsnagTests/BugsnagConfigurationTests.m +++ b/Tests/BugsnagTests/BugsnagConfigurationTests.m @@ -38,14 +38,6 @@ - (void)testDefaultSessionConfig { XCTAssertTrue([config autoTrackSessions]); } -- (void)testSessionApiHeaders { - BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; - NSDictionary *headers = [config sessionApiHeaders]; - XCTAssertEqualObjects(config.apiKey, headers[@"Bugsnag-Api-Key"]); - XCTAssertNotNil(headers[@"Bugsnag-Sent-At"]); - XCTAssertNotNil(headers[@"Bugsnag-Payload-Version"]); -} - - (void)testSessionEndpoints { BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; From 3d74b486f30464141143f5810d278df9967e769b Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Mon, 8 Aug 2022 10:36:27 +0100 Subject: [PATCH 16/24] Pass NSData to BugsnagApiClient --- Bugsnag/Delivery/BSGEventUploadOperation.m | 14 ++++++++--- Bugsnag/Delivery/BSGSessionUploader.m | 9 +++++++- Bugsnag/Delivery/BugsnagApiClient.h | 8 +++---- Bugsnag/Delivery/BugsnagApiClient.m | 27 ++++++++-------------- Tests/BugsnagTests/BugsnagApiClientTest.m | 10 ++------ 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Bugsnag/Delivery/BSGEventUploadOperation.m b/Bugsnag/Delivery/BSGEventUploadOperation.m index e1d8cba7f..608bd95e9 100644 --- a/Bugsnag/Delivery/BSGEventUploadOperation.m +++ b/Bugsnag/Delivery/BSGEventUploadOperation.m @@ -10,6 +10,7 @@ #import "BSGFileLocations.h" #import "BSGInternalErrorReporter.h" +#import "BSGJSONSerialization.h" #import "BSGKeys.h" #import "BugsnagAppWithState+Private.h" #import "BugsnagConfiguration+Private.h" @@ -131,8 +132,15 @@ - (void)runWithDelegate:(id)delegate completion return; } - __block NSData *HTTPBody = - [delegate.apiClient sendJSONPayload:requestPayload headers:requestHeaders toURL:notifyURL + NSData *data = BSGJSONDataFromDictionary(requestPayload, NULL); + if (!data) { + bsg_log_debug(@"Encoding failed; will discard event %@", self.name); + [self deleteEvent]; + completionHandler(); + return; + } + + [delegate.apiClient postJSONData:data headers:requestHeaders toURL:notifyURL completionHandler:^(BugsnagApiClientDeliveryStatus status, __unused NSError *deliveryError) { switch (status) { @@ -143,7 +151,7 @@ - (void)runWithDelegate:(id)delegate completion case BugsnagApiClientDeliveryStatusFailed: bsg_log_debug(@"Upload failed retryably for event %@", self.name); - [self prepareForRetry:originalPayload ?: eventPayload HTTPBodySize:HTTPBody.length]; + [self prepareForRetry:originalPayload ?: eventPayload HTTPBodySize:data.length]; break; case BugsnagApiClientDeliveryStatusUndeliverable: diff --git a/Bugsnag/Delivery/BSGSessionUploader.m b/Bugsnag/Delivery/BSGSessionUploader.m index b54375799..20d342cd5 100644 --- a/Bugsnag/Delivery/BSGSessionUploader.m +++ b/Bugsnag/Delivery/BSGSessionUploader.m @@ -166,7 +166,14 @@ - (void)sendSession:(BugsnagSession *)session completionHandler:(nonnull void (^ }] }; - [self.apiClient sendJSONPayload:payload headers:headers toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError *error) { + NSData *data = BSGJSONDataFromDictionary(payload, NULL); + if (!data) { + bsg_log_err(@"Failed to encode session %@", session.id); + completionHandler(BugsnagApiClientDeliveryStatusUndeliverable); + return; + } + + [self.apiClient postJSONData:data headers:headers toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError *error) { switch (status) { case BugsnagApiClientDeliveryStatusDelivered: bsg_log_info(@"Sent session %@", session.id); diff --git a/Bugsnag/Delivery/BugsnagApiClient.h b/Bugsnag/Delivery/BugsnagApiClient.h index a285c7662..1b7461c26 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.h +++ b/Bugsnag/Delivery/BugsnagApiClient.h @@ -28,10 +28,10 @@ typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { - (instancetype)initWithSession:(nullable NSURLSession *)session; -- (nullable NSData *)sendJSONPayload:(NSDictionary *)payload - headers:(NSDictionary *)headers - toURL:(NSURL *)url - completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error))completionHandler; +- (void)postJSONData:(NSData *)data + headers:(NSDictionary *)headers + toURL:(NSURL *)url + completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error))completionHandler; + (NSString *)SHA1HashStringWithData:(NSData *)data; diff --git a/Bugsnag/Delivery/BugsnagApiClient.m b/Bugsnag/Delivery/BugsnagApiClient.m index 6e166ea27..5dccb4ce7 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.m +++ b/Bugsnag/Delivery/BugsnagApiClient.m @@ -46,18 +46,10 @@ - (instancetype)initWithSession:(nullable NSURLSession *)session { #pragma mark - Delivery -- (NSData *)sendJSONPayload:(NSDictionary *)payload - headers:(NSDictionary *)headers - toURL:(NSURL *)url - completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error))completionHandler { - - NSError *error = nil; - NSData *data = BSGJSONDataFromDictionary(payload, &error); - if (!data) { - bsg_log_err(@"Error: Could not encode JSON payload passed to %s", __PRETTY_FUNCTION__); - completionHandler(BugsnagApiClientDeliveryStatusUndeliverable, error); - return nil; - } +- (void)postJSONData:(NSData *)data + headers:(NSDictionary *)headers + toURL:(NSURL *)url + completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error))completionHandler { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15]; request.HTTPMethod = @"POST"; @@ -72,10 +64,10 @@ - (NSData *)sendJSONPayload:(NSDictionary *)payload bsg_log_debug(@"Sending %lu byte payload to %@", (unsigned long)data.length, url); [[self.session uploadTaskWithRequest:request fromData:data completionHandler:^(__unused NSData *responseData, - NSURLResponse *response, NSError *connectionError) { + NSURLResponse *response, NSError *error) { if (![response isKindOfClass:[NSHTTPURLResponse class]]) { bsg_log_debug(@"Request to %@ completed with error %@", url, error); - completionHandler(BugsnagApiClientDeliveryStatusFailed, connectionError ?: + completionHandler(BugsnagApiClientDeliveryStatusFailed, error ?: [NSError errorWithDomain:@"BugsnagApiClientErrorDomain" code:0 userInfo:@{ NSLocalizedDescriptionKey: @"Request failed: no response was received", NSURLErrorFailingURLErrorKey: url }]); @@ -90,7 +82,7 @@ - (NSData *)sendJSONPayload:(NSDictionary *)payload return; } - connectionError = [NSError errorWithDomain:@"BugsnagApiClientErrorDomain" code:1 userInfo:@{ + error = [NSError errorWithDomain:@"BugsnagApiClientErrorDomain" code:1 userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Request failed: unacceptable status code %ld (%@)", (long)statusCode, [NSHTTPURLResponse localizedStringForStatusCode:statusCode]], NSURLErrorFailingURLErrorKey: url }]; @@ -103,13 +95,12 @@ - (NSData *)sendJSONPayload:(NSDictionary *)payload statusCode != HTTPStatusCodeProxyAuthenticationRequired && statusCode != HTTPStatusCodeClientTimeout && statusCode != HTTPStatusCodeTooManyRequests) { - completionHandler(BugsnagApiClientDeliveryStatusUndeliverable, connectionError); + completionHandler(BugsnagApiClientDeliveryStatusUndeliverable, error); return; } - completionHandler(BugsnagApiClientDeliveryStatusFailed, connectionError); + completionHandler(BugsnagApiClientDeliveryStatusFailed, error); }] resume]; - return data; } + (NSString *)SHA1HashStringWithData:(NSData *)data { diff --git a/Tests/BugsnagTests/BugsnagApiClientTest.m b/Tests/BugsnagTests/BugsnagApiClientTest.m index d18dbb268..eca0bc2fc 100644 --- a/Tests/BugsnagTests/BugsnagApiClientTest.m +++ b/Tests/BugsnagTests/BugsnagApiClientTest.m @@ -18,12 +18,6 @@ @interface BugsnagApiClientTest : XCTestCase @implementation BugsnagApiClientTest -- (void)testBadJSON { - BugsnagApiClient *client = [[BugsnagApiClient alloc] initWithSession:nil]; - 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]; @@ -34,7 +28,7 @@ - (void)testHTTPStatusCodes { 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) { + [client postJSONData:[NSData data] headers:@{} toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { XCTAssertEqual(status, expectedDeliveryStatus); expectError ? XCTAssertNotNil(error) : XCTAssertNil(error); [expectation fulfill]; @@ -70,7 +64,7 @@ - (void)testNotConnectedToInternetError { [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) { + [client postJSONData:[NSData data] headers:@{} toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { XCTAssertEqual(status, BugsnagApiClientDeliveryStatusFailed); XCTAssertNotNil(error); XCTAssertEqualObjects(error.domain, NSURLErrorDomain); From b9e2d94770982ca0afa43c73a9a06fcdabbf0608 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Mon, 8 Aug 2022 11:16:42 +0100 Subject: [PATCH 17/24] Remove BugsnagApiClient class --- Bugsnag/Delivery/BSGEventUploadOperation.h | 2 -- Bugsnag/Delivery/BSGEventUploadOperation.m | 6 ++-- Bugsnag/Delivery/BSGEventUploader.m | 2 -- Bugsnag/Delivery/BSGSessionUploader.m | 6 ++-- Bugsnag/Delivery/BugsnagApiClient.h | 17 ++++------- Bugsnag/Delivery/BugsnagApiClient.m | 35 ++++++---------------- Bugsnag/Helpers/BSGInternalErrorReporter.m | 2 +- Tests/BugsnagTests/BugsnagApiClientTest.m | 22 +++++++------- 8 files changed, 30 insertions(+), 62 deletions(-) diff --git a/Bugsnag/Delivery/BSGEventUploadOperation.h b/Bugsnag/Delivery/BSGEventUploadOperation.h index 6bc439eb9..45e0b15e0 100644 --- a/Bugsnag/Delivery/BSGEventUploadOperation.h +++ b/Bugsnag/Delivery/BSGEventUploadOperation.h @@ -54,8 +54,6 @@ static const NSUInteger MaxPersistedSize = 1000000; @protocol BSGEventUploadOperationDelegate -@property (readonly, nonatomic) BugsnagApiClient *apiClient; - @property (readonly, nonatomic) BugsnagConfiguration *configuration; @property (readonly, nonatomic) BugsnagNotifier *notifier; diff --git a/Bugsnag/Delivery/BSGEventUploadOperation.m b/Bugsnag/Delivery/BSGEventUploadOperation.m index 608bd95e9..8d3fa0e35 100644 --- a/Bugsnag/Delivery/BSGEventUploadOperation.m +++ b/Bugsnag/Delivery/BSGEventUploadOperation.m @@ -140,9 +140,7 @@ - (void)runWithDelegate:(id)delegate completion return; } - [delegate.apiClient postJSONData:data headers:requestHeaders toURL:notifyURL - completionHandler:^(BugsnagApiClientDeliveryStatus status, __unused NSError *deliveryError) { - + BSGPostJSONData(configuration.session, data, requestHeaders, notifyURL, ^(BugsnagApiClientDeliveryStatus status, __unused NSError *deliveryError) { switch (status) { case BugsnagApiClientDeliveryStatusDelivered: bsg_log_debug(@"Uploaded event %@", self.name); @@ -161,7 +159,7 @@ - (void)runWithDelegate:(id)delegate completion } completionHandler(); - }]; + }); } // MARK: Subclassing diff --git a/Bugsnag/Delivery/BSGEventUploader.m b/Bugsnag/Delivery/BSGEventUploader.m index bc78d20e4..1976b1ad7 100644 --- a/Bugsnag/Delivery/BSGEventUploader.m +++ b/Bugsnag/Delivery/BSGEventUploader.m @@ -40,13 +40,11 @@ @interface BSGEventUploader () @implementation BSGEventUploader -@synthesize apiClient = _apiClient; @synthesize configuration = _configuration; @synthesize notifier = _notifier; - (instancetype)initWithConfiguration:(BugsnagConfiguration *)configuration notifier:(BugsnagNotifier *)notifier { if ((self = [super init])) { - _apiClient = [[BugsnagApiClient alloc] initWithSession:configuration.session]; _configuration = configuration; _eventsDirectory = [BSGFileLocations current].events; _kscrashReportsDirectory = [BSGFileLocations current].kscrashReports; diff --git a/Bugsnag/Delivery/BSGSessionUploader.m b/Bugsnag/Delivery/BSGSessionUploader.m index 20d342cd5..6cb3dd130 100644 --- a/Bugsnag/Delivery/BSGSessionUploader.m +++ b/Bugsnag/Delivery/BSGSessionUploader.m @@ -30,7 +30,6 @@ @interface BSGSessionUploader () @property (nonatomic) NSMutableSet *activeIds; -@property (nonatomic) BugsnagApiClient *apiClient; @property(nonatomic) BugsnagConfiguration *config; @end @@ -40,7 +39,6 @@ @implementation BSGSessionUploader - (instancetype)initWithConfig:(BugsnagConfiguration *)config notifier:(BugsnagNotifier *)notifier { if ((self = [super init])) { _activeIds = [NSMutableSet new]; - _apiClient = [[BugsnagApiClient alloc] initWithSession:config.session]; _config = config; _notifier = notifier; } @@ -173,7 +171,7 @@ - (void)sendSession:(BugsnagSession *)session completionHandler:(nonnull void (^ return; } - [self.apiClient postJSONData:data headers:headers toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError *error) { + BSGPostJSONData(self.config.session, data, headers, url, ^(BugsnagApiClientDeliveryStatus status, NSError *error) { switch (status) { case BugsnagApiClientDeliveryStatusDelivered: bsg_log_info(@"Sent session %@", session.id); @@ -186,7 +184,7 @@ - (void)sendSession:(BugsnagSession *)session completionHandler:(nonnull void (^ break; } completionHandler(status); - }]; + }); } @end diff --git a/Bugsnag/Delivery/BugsnagApiClient.h b/Bugsnag/Delivery/BugsnagApiClient.h index 1b7461c26..0213eb762 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.h +++ b/Bugsnag/Delivery/BugsnagApiClient.h @@ -24,17 +24,12 @@ typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { BugsnagApiClientDeliveryStatusUndeliverable, }; -@interface BugsnagApiClient : NSObject +void BSGPostJSONData(NSURLSession *URLSession, + NSData *data, + NSDictionary *headers, + NSURL *url, + void (^ completionHandler)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error)); -- (instancetype)initWithSession:(nullable NSURLSession *)session; - -- (void)postJSONData:(NSData *)data - headers:(NSDictionary *)headers - toURL:(NSURL *)url - completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error))completionHandler; - -+ (NSString *)SHA1HashStringWithData:(NSData *)data; - -@end +NSString *_Nullable BSGIntegrityHeaderValue(NSData *_Nullable data); NS_ASSUME_NONNULL_END diff --git a/Bugsnag/Delivery/BugsnagApiClient.m b/Bugsnag/Delivery/BugsnagApiClient.m index 5dccb4ce7..998ad84de 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.m +++ b/Bugsnag/Delivery/BugsnagApiClient.m @@ -31,30 +31,16 @@ typedef NS_ENUM(NSInteger, HTTPStatusCode) { HTTPStatusCodeTooManyRequests = 429, }; -@interface BugsnagApiClient() -@property (nonatomic, strong) NSURLSession *session; -@end - -@implementation BugsnagApiClient - -- (instancetype)initWithSession:(nullable NSURLSession *)session { - if ((self = [super init])) { - _session = session ?: [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; - } - return self; -} - -#pragma mark - Delivery - -- (void)postJSONData:(NSData *)data - headers:(NSDictionary *)headers - toURL:(NSURL *)url - completionHandler:(void (^)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error))completionHandler { +void BSGPostJSONData(NSURLSession *URLSession, + NSData *data, + NSDictionary *headers, + NSURL *url, + void (^ completionHandler)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error)) { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15]; request.HTTPMethod = @"POST"; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - [request setValue:[NSString stringWithFormat:@"sha1 %@", [BugsnagApiClient SHA1HashStringWithData:data]] forHTTPHeaderField:BugsnagHTTPHeaderNameIntegrity]; + [request setValue:BSGIntegrityHeaderValue(data) forHTTPHeaderField:BugsnagHTTPHeaderNameIntegrity]; [request setValue:[BSG_RFC3339DateTool stringFromDate:[NSDate date]] forHTTPHeaderField:BugsnagHTTPHeaderNameSentAt]; for (BugsnagHTTPHeaderName name in headers) { @@ -63,8 +49,7 @@ - (void)postJSONData:(NSData *)data bsg_log_debug(@"Sending %lu byte payload to %@", (unsigned long)data.length, url); - [[self.session uploadTaskWithRequest:request fromData:data completionHandler:^(__unused NSData *responseData, - NSURLResponse *response, NSError *error) { + [[URLSession uploadTaskWithRequest:request fromData:data completionHandler:^(__unused NSData *responseData, NSURLResponse *response, NSError *error) { if (![response isKindOfClass:[NSHTTPURLResponse class]]) { bsg_log_debug(@"Request to %@ completed with error %@", url, error); completionHandler(BugsnagApiClientDeliveryStatusFailed, error ?: @@ -103,17 +88,15 @@ - (void)postJSONData:(NSData *)data }] resume]; } -+ (NSString *)SHA1HashStringWithData:(NSData *)data { +NSString * BSGIntegrityHeaderValue(NSData *data) { if (!data) { return nil; } unsigned char md[CC_SHA1_DIGEST_LENGTH]; CC_SHA1(data.bytes, (CC_LONG)data.length, md); - return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + return [NSString stringWithFormat:@"sha1 %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", md[0], md[1], md[2], md[3], md[4], md[5], md[6], md[7], md[8], md[9], md[10], md[11], md[12], md[13], md[14], md[15], md[16], md[17], md[18], md[19]]; } - -@end diff --git a/Bugsnag/Helpers/BSGInternalErrorReporter.m b/Bugsnag/Helpers/BSGInternalErrorReporter.m index 342ba419d..f0bf2df89 100644 --- a/Bugsnag/Helpers/BSGInternalErrorReporter.m +++ b/Bugsnag/Helpers/BSGInternalErrorReporter.m @@ -258,7 +258,7 @@ - (NSURLRequest *)requestForEvent:(nonnull BugsnagEvent *)event error:(NSError * NSMutableDictionary *headers = [NSMutableDictionary dictionary]; headers[@"Content-Type"] = @"application/json"; - headers[BugsnagHTTPHeaderNameIntegrity] = [NSString stringWithFormat:@"sha1 %@", [BugsnagApiClient SHA1HashStringWithData:data]]; + headers[BugsnagHTTPHeaderNameIntegrity] = BSGIntegrityHeaderValue(data); headers[BugsnagHTTPHeaderNameInternalError] = @"bugsnag-cocoa"; headers[BugsnagHTTPHeaderNamePayloadVersion] = EventPayloadVersion; headers[BugsnagHTTPHeaderNameSentAt] = [BSG_RFC3339DateTool stringFromDate:[NSDate date]]; diff --git a/Tests/BugsnagTests/BugsnagApiClientTest.m b/Tests/BugsnagTests/BugsnagApiClientTest.m index eca0bc2fc..9a4e910dd 100644 --- a/Tests/BugsnagTests/BugsnagApiClientTest.m +++ b/Tests/BugsnagTests/BugsnagApiClientTest.m @@ -20,19 +20,18 @@ @implementation BugsnagApiClientTest - (void)testHTTPStatusCodes { NSURL *url = [NSURL URLWithString:@"https://example.com"]; - URLSessionMock *session = [[URLSessionMock alloc] init]; - BugsnagApiClient *client = [[BugsnagApiClient alloc] initWithSession:(id)session]; + id URLSession = [[URLSessionMock alloc] init]; 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 postJSONData:[NSData data] headers:@{} toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { + [URLSession mockData:[NSData data] response:response error:nil]; + BSGPostJSONData(URLSession, [NSData data], @{}, url, ^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { XCTAssertEqual(status, expectedDeliveryStatus); expectError ? XCTAssertNotNil(error) : XCTAssertNil(error); [expectation fulfill]; - }]; + }); }; test(200, BugsnagApiClientDeliveryStatusDelivered, NO); @@ -57,19 +56,18 @@ - (void)testHTTPStatusCodes { - (void)testNotConnectedToInternetError { NSURL *url = [NSURL URLWithString:@"https://example.com"]; - URLSessionMock *session = [[URLSessionMock alloc] init]; - BugsnagApiClient *client = [[BugsnagApiClient alloc] initWithSession:(id)session]; + id URLSession = [[URLSessionMock alloc] init]; XCTestExpectation *expectation = [self expectationWithDescription:@"completionHandler should be called"]; - [session mockData:nil response:nil error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorNotConnectedToInternet userInfo:@{ + [URLSession mockData:nil response:nil error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorNotConnectedToInternet userInfo:@{ NSURLErrorFailingURLErrorKey: url, }]]; - [client postJSONData:[NSData data] headers:@{} toURL:url completionHandler:^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { + BSGPostJSONData(URLSession, [NSData data], @{}, url, ^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { XCTAssertEqual(status, BugsnagApiClientDeliveryStatusFailed); XCTAssertNotNil(error); XCTAssertEqualObjects(error.domain, NSURLErrorDomain); [expectation fulfill]; - }]; + }); [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -77,9 +75,9 @@ - (void)testNotConnectedToInternetError { - (void)testSHA1HashStringWithData { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - XCTAssertNil([BugsnagApiClient SHA1HashStringWithData:nil]); + XCTAssertNil(BSGIntegrityHeaderValue(nil)); #pragma clang diagnostic pop - XCTAssertEqualObjects([BugsnagApiClient SHA1HashStringWithData:[@"{\"foo\":\"bar\"}" dataUsingEncoding:NSUTF8StringEncoding]], @"a5e744d0164540d33b1d7ea616c28f2fa97e754a"); + XCTAssertEqualObjects(BSGIntegrityHeaderValue([@"{\"foo\":\"bar\"}" dataUsingEncoding:NSUTF8StringEncoding]), @"sha1 a5e744d0164540d33b1d7ea616c28f2fa97e754a"); } @end From 39477aa15629340523b8a092dce52eea18716518 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Mon, 8 Aug 2022 11:17:56 +0100 Subject: [PATCH 18/24] BugsnagApiClientDeliveryStatus -> BSGDeliveryStatus --- Bugsnag/Delivery/BSGEventUploadOperation.m | 8 ++--- Bugsnag/Delivery/BSGSessionUploader.m | 28 +++++++++--------- Bugsnag/Delivery/BugsnagApiClient.h | 10 +++---- Bugsnag/Delivery/BugsnagApiClient.m | 10 +++---- Tests/BugsnagTests/BugsnagApiClientTest.m | 34 +++++++++++----------- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/Bugsnag/Delivery/BSGEventUploadOperation.m b/Bugsnag/Delivery/BSGEventUploadOperation.m index 8d3fa0e35..7ff7bee91 100644 --- a/Bugsnag/Delivery/BSGEventUploadOperation.m +++ b/Bugsnag/Delivery/BSGEventUploadOperation.m @@ -140,19 +140,19 @@ - (void)runWithDelegate:(id)delegate completion return; } - BSGPostJSONData(configuration.session, data, requestHeaders, notifyURL, ^(BugsnagApiClientDeliveryStatus status, __unused NSError *deliveryError) { + BSGPostJSONData(configuration.session, data, requestHeaders, notifyURL, ^(BSGDeliveryStatus status, __unused NSError *deliveryError) { switch (status) { - case BugsnagApiClientDeliveryStatusDelivered: + case BSGDeliveryStatusDelivered: bsg_log_debug(@"Uploaded event %@", self.name); [self deleteEvent]; break; - case BugsnagApiClientDeliveryStatusFailed: + case BSGDeliveryStatusFailed: bsg_log_debug(@"Upload failed retryably for event %@", self.name); [self prepareForRetry:originalPayload ?: eventPayload HTTPBodySize:data.length]; break; - case BugsnagApiClientDeliveryStatusUndeliverable: + case BSGDeliveryStatusUndeliverable: bsg_log_debug(@"Upload failed; will discard event %@", self.name); [self deleteEvent]; break; diff --git a/Bugsnag/Delivery/BSGSessionUploader.m b/Bugsnag/Delivery/BSGSessionUploader.m index 6cb3dd130..6af243593 100644 --- a/Bugsnag/Delivery/BSGSessionUploader.m +++ b/Bugsnag/Delivery/BSGSessionUploader.m @@ -46,17 +46,17 @@ - (instancetype)initWithConfig:(BugsnagConfiguration *)config notifier:(BugsnagN } - (void)uploadSession:(BugsnagSession *)session { - [self sendSession:session completionHandler:^(BugsnagApiClientDeliveryStatus status) { + [self sendSession:session completionHandler:^(BSGDeliveryStatus status) { switch (status) { - case BugsnagApiClientDeliveryStatusDelivered: + case BSGDeliveryStatusDelivered: [self processStoredSessions]; break; - case BugsnagApiClientDeliveryStatusFailed: + case BSGDeliveryStatusFailed: [self storeSession:session]; // Retry later break; - case BugsnagApiClientDeliveryStatusUndeliverable: + case BSGDeliveryStatusUndeliverable: break; } }]; @@ -106,8 +106,8 @@ - (void)processStoredSessions { [self.activeIds addObject:file]; } - [self sendSession:session completionHandler:^(BugsnagApiClientDeliveryStatus status) { - if (status != BugsnagApiClientDeliveryStatusFailed) { + [self sendSession:session completionHandler:^(BSGDeliveryStatus status) { + if (status != BSGDeliveryStatusFailed) { [fileManager removeItemAtPath:file error:nil]; } @synchronized (self.activeIds) { @@ -133,18 +133,18 @@ - (void)pruneFiles { // // https://bugsnagsessiontrackingapi.docs.apiary.io/#reference/0/session/report-a-session-starting // -- (void)sendSession:(BugsnagSession *)session completionHandler:(nonnull void (^)(BugsnagApiClientDeliveryStatus status))completionHandler { +- (void)sendSession:(BugsnagSession *)session completionHandler:(nonnull void (^)(BSGDeliveryStatus status))completionHandler { NSString *apiKey = [self.config.apiKey copy]; if (!apiKey) { bsg_log_err(@"Cannot send session because no apiKey is configured."); - completionHandler(BugsnagApiClientDeliveryStatusUndeliverable); + completionHandler(BSGDeliveryStatusUndeliverable); return; } NSURL *url = self.config.sessionURL; if (!url) { bsg_log_err(@"Cannot send session because no endpoint is configured."); - completionHandler(BugsnagApiClientDeliveryStatusUndeliverable); + completionHandler(BSGDeliveryStatusUndeliverable); return; } @@ -167,19 +167,19 @@ - (void)sendSession:(BugsnagSession *)session completionHandler:(nonnull void (^ NSData *data = BSGJSONDataFromDictionary(payload, NULL); if (!data) { bsg_log_err(@"Failed to encode session %@", session.id); - completionHandler(BugsnagApiClientDeliveryStatusUndeliverable); + completionHandler(BSGDeliveryStatusUndeliverable); return; } - BSGPostJSONData(self.config.session, data, headers, url, ^(BugsnagApiClientDeliveryStatus status, NSError *error) { + BSGPostJSONData(self.config.session, data, headers, url, ^(BSGDeliveryStatus status, NSError *error) { switch (status) { - case BugsnagApiClientDeliveryStatusDelivered: + case BSGDeliveryStatusDelivered: bsg_log_info(@"Sent session %@", session.id); break; - case BugsnagApiClientDeliveryStatusFailed: + case BSGDeliveryStatusFailed: bsg_log_warn(@"Failed to send sessions: %@", error); break; - case BugsnagApiClientDeliveryStatusUndeliverable: + case BSGDeliveryStatusUndeliverable: bsg_log_warn(@"Failed to send sessions: %@", error); break; } diff --git a/Bugsnag/Delivery/BugsnagApiClient.h b/Bugsnag/Delivery/BugsnagApiClient.h index 0213eb762..4083ee761 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.h +++ b/Bugsnag/Delivery/BugsnagApiClient.h @@ -15,20 +15,20 @@ static BugsnagHTTPHeaderName const BugsnagHTTPHeaderNamePayloadVersion = @"B static BugsnagHTTPHeaderName const BugsnagHTTPHeaderNameSentAt = @"Bugsnag-Sent-At"; static BugsnagHTTPHeaderName const BugsnagHTTPHeaderNameStacktraceTypes = @"Bugsnag-Stacktrace-Types"; -typedef NS_ENUM(NSInteger, BugsnagApiClientDeliveryStatus) { +typedef NS_ENUM(NSInteger, BSGDeliveryStatus) { /// The payload was delivered successfully and can be deleted. - BugsnagApiClientDeliveryStatusDelivered, + BSGDeliveryStatusDelivered, /// The payload was not delivered but can be retried, e.g. when there was a loss of connectivity. - BugsnagApiClientDeliveryStatusFailed, + BSGDeliveryStatusFailed, /// The payload cannot be delivered and should be deleted without attempting to retry. - BugsnagApiClientDeliveryStatusUndeliverable, + BSGDeliveryStatusUndeliverable, }; void BSGPostJSONData(NSURLSession *URLSession, NSData *data, NSDictionary *headers, NSURL *url, - void (^ completionHandler)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error)); + void (^ completionHandler)(BSGDeliveryStatus status, NSError *_Nullable error)); NSString *_Nullable BSGIntegrityHeaderValue(NSData *_Nullable data); diff --git a/Bugsnag/Delivery/BugsnagApiClient.m b/Bugsnag/Delivery/BugsnagApiClient.m index 998ad84de..2c98c1657 100644 --- a/Bugsnag/Delivery/BugsnagApiClient.m +++ b/Bugsnag/Delivery/BugsnagApiClient.m @@ -35,7 +35,7 @@ void BSGPostJSONData(NSURLSession *URLSession, NSData *data, NSDictionary *headers, NSURL *url, - void (^ completionHandler)(BugsnagApiClientDeliveryStatus status, NSError *_Nullable error)) { + void (^ completionHandler)(BSGDeliveryStatus status, NSError *_Nullable error)) { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15]; request.HTTPMethod = @"POST"; @@ -52,7 +52,7 @@ void BSGPostJSONData(NSURLSession *URLSession, [[URLSession uploadTaskWithRequest:request fromData:data completionHandler:^(__unused NSData *responseData, NSURLResponse *response, NSError *error) { if (![response isKindOfClass:[NSHTTPURLResponse class]]) { bsg_log_debug(@"Request to %@ completed with error %@", url, error); - completionHandler(BugsnagApiClientDeliveryStatusFailed, error ?: + completionHandler(BSGDeliveryStatusFailed, error ?: [NSError errorWithDomain:@"BugsnagApiClientErrorDomain" code:0 userInfo:@{ NSLocalizedDescriptionKey: @"Request failed: no response was received", NSURLErrorFailingURLErrorKey: url }]); @@ -63,7 +63,7 @@ void BSGPostJSONData(NSURLSession *URLSession, bsg_log_debug(@"Request to %@ completed with status code %ld", url, (long)statusCode); if (statusCode / 100 == 2) { - completionHandler(BugsnagApiClientDeliveryStatusDelivered, nil); + completionHandler(BSGDeliveryStatusDelivered, nil); return; } @@ -80,11 +80,11 @@ void BSGPostJSONData(NSURLSession *URLSession, statusCode != HTTPStatusCodeProxyAuthenticationRequired && statusCode != HTTPStatusCodeClientTimeout && statusCode != HTTPStatusCodeTooManyRequests) { - completionHandler(BugsnagApiClientDeliveryStatusUndeliverable, error); + completionHandler(BSGDeliveryStatusUndeliverable, error); return; } - completionHandler(BugsnagApiClientDeliveryStatusFailed, error); + completionHandler(BSGDeliveryStatusFailed, error); }] resume]; } diff --git a/Tests/BugsnagTests/BugsnagApiClientTest.m b/Tests/BugsnagTests/BugsnagApiClientTest.m index 9a4e910dd..4abfb2992 100644 --- a/Tests/BugsnagTests/BugsnagApiClientTest.m +++ b/Tests/BugsnagTests/BugsnagApiClientTest.m @@ -22,34 +22,34 @@ - (void)testHTTPStatusCodes { NSURL *url = [NSURL URLWithString:@"https://example.com"]; id URLSession = [[URLSessionMock alloc] init]; - void (^ test)(NSInteger, BugsnagApiClientDeliveryStatus, BOOL) = - ^(NSInteger statusCode, BugsnagApiClientDeliveryStatus expectedDeliveryStatus, BOOL expectError) { + void (^ test)(NSInteger, BSGDeliveryStatus, BOOL) = + ^(NSInteger statusCode, BSGDeliveryStatus expectedDeliveryStatus, BOOL expectError) { XCTestExpectation *expectation = [self expectationWithDescription:@"completionHandler should be called"]; id response = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:statusCode HTTPVersion:@"1.1" headerFields:nil]; [URLSession mockData:[NSData data] response:response error:nil]; - BSGPostJSONData(URLSession, [NSData data], @{}, url, ^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { + BSGPostJSONData(URLSession, [NSData data], @{}, url, ^(BSGDeliveryStatus status, NSError * _Nullable error) { XCTAssertEqual(status, expectedDeliveryStatus); expectError ? XCTAssertNotNil(error) : XCTAssertNil(error); [expectation fulfill]; }); }; - test(200, BugsnagApiClientDeliveryStatusDelivered, NO); + test(200, BSGDeliveryStatusDelivered, 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); + test(400, BSGDeliveryStatusUndeliverable, YES); + test(401, BSGDeliveryStatusUndeliverable, YES); + test(403, BSGDeliveryStatusUndeliverable, YES); + test(404, BSGDeliveryStatusUndeliverable, YES); + test(405, BSGDeliveryStatusUndeliverable, YES); + test(406, BSGDeliveryStatusUndeliverable, YES); // Transient failures - test(402, BugsnagApiClientDeliveryStatusFailed, YES); - test(407, BugsnagApiClientDeliveryStatusFailed, YES); - test(408, BugsnagApiClientDeliveryStatusFailed, YES); - test(429, BugsnagApiClientDeliveryStatusFailed, YES); - test(500, BugsnagApiClientDeliveryStatusFailed, YES); + test(402, BSGDeliveryStatusFailed, YES); + test(407, BSGDeliveryStatusFailed, YES); + test(408, BSGDeliveryStatusFailed, YES); + test(429, BSGDeliveryStatusFailed, YES); + test(500, BSGDeliveryStatusFailed, YES); [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -62,8 +62,8 @@ - (void)testNotConnectedToInternetError { [URLSession mockData:nil response:nil error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorNotConnectedToInternet userInfo:@{ NSURLErrorFailingURLErrorKey: url, }]]; - BSGPostJSONData(URLSession, [NSData data], @{}, url, ^(BugsnagApiClientDeliveryStatus status, NSError * _Nullable error) { - XCTAssertEqual(status, BugsnagApiClientDeliveryStatusFailed); + BSGPostJSONData(URLSession, [NSData data], @{}, url, ^(BSGDeliveryStatus status, NSError * _Nullable error) { + XCTAssertEqual(status, BSGDeliveryStatusFailed); XCTAssertNotNil(error); XCTAssertEqualObjects(error.domain, NSURLErrorDomain); [expectation fulfill]; From 3f12d4a18348094e28c40877f556d26479bca5d1 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 9 Aug 2022 08:11:46 +0100 Subject: [PATCH 19/24] Trim breadcrumbs in oversized payloads (#1451) --- Bugsnag/Delivery/BSGEventUploadOperation.m | 38 +++++++++--- Bugsnag/Payload/BugsnagEvent+Private.h | 2 + Bugsnag/Payload/BugsnagEvent.m | 36 +++++++++++ CHANGELOG.md | 3 + Tests/BugsnagTests/BugsnagEventTests.m | 61 +++++++++++++++++++ features/delivery.feature | 14 +++++ .../ios/iOSTestApp.xcodeproj/project.pbxproj | 4 ++ .../macOSTestApp.xcodeproj/project.pbxproj | 4 ++ .../OversizedBreadcrumbsScenario.swift | 16 +++++ 9 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 features/fixtures/shared/scenarios/OversizedBreadcrumbsScenario.swift diff --git a/Bugsnag/Delivery/BSGEventUploadOperation.m b/Bugsnag/Delivery/BSGEventUploadOperation.m index 7ff7bee91..988dbe02d 100644 --- a/Bugsnag/Delivery/BSGEventUploadOperation.m +++ b/Bugsnag/Delivery/BSGEventUploadOperation.m @@ -76,12 +76,12 @@ - (void)runWithDelegate:(id)delegate completion return; } - NSDictionary *_Nullable originalPayload = nil; + NSDictionary *retryPayload = nil; for (BugsnagOnSendErrorBlock block in configuration.onSendBlocks) { @try { - if (!originalPayload) { + if (!retryPayload) { // If OnSendError modifies the event and delivery fails, we need to persist the original state of the event. - originalPayload = [event toJsonWithRedactedKeys:configuration.redactedKeys]; + retryPayload = [event toJsonWithRedactedKeys:configuration.redactedKeys]; } if (!block(event)) { [self deleteEvent]; @@ -97,8 +97,11 @@ - (void)runWithDelegate:(id)delegate completion @try { [event truncateStrings:configuration.maxStringValueLength]; eventPayload = [event toJsonWithRedactedKeys:configuration.redactedKeys]; + if (!retryPayload || [retryPayload isEqualToDictionary:eventPayload]) { + retryPayload = eventPayload; + } } @catch (NSException *exception) { - bsg_log_err(@"Discarding event %@ because an exception was thrown by -toJsonWithRedactedKeys: %@", self.name, exception); + bsg_log_err(@"Discarding event %@ due to exception %@", self.name, exception); [BSGInternalErrorReporter.sharedInstance reportException:exception diagnostics:nil groupingHash: [NSString stringWithFormat:@"BSGEventUploadOperation -[runWithDelegate:completionHandler:] %@ %@", exception.name, exception.reason]]; @@ -107,11 +110,6 @@ - (void)runWithDelegate:(id)delegate completion return; } - if ([originalPayload isEqual:eventPayload]) { - // Save memory if payload has not changed - originalPayload = nil; - } - NSString *apiKey = event.apiKey ?: configuration.apiKey; NSMutableDictionary *requestPayload = [NSMutableDictionary dictionary]; @@ -140,6 +138,26 @@ - (void)runWithDelegate:(id)delegate completion return; } + if (data.length > MaxPersistedSize) { + // Trim extra bytes to make space for "removed" message and usage telemetry. + NSUInteger bytesToRemove = data.length - (MaxPersistedSize - 300); + bsg_log_debug(@"Trimming breadcrumbs; bytesToRemove = %lu", (unsigned long)bytesToRemove); + @try { + [event trimBreadcrumbs:bytesToRemove]; + eventPayload = [event toJsonWithRedactedKeys:configuration.redactedKeys]; + requestPayload[BSGKeyEvents] = @[eventPayload]; + data = BSGJSONDataFromDictionary(requestPayload, NULL); + } @catch (NSException *exception) { + bsg_log_err(@"Discarding event %@ due to exception %@", self.name, exception); + [BSGInternalErrorReporter.sharedInstance reportException:exception diagnostics:nil groupingHash: + [NSString stringWithFormat:@"BSGEventUploadOperation -[runWithDelegate:completionHandler:] %@ %@", + exception.name, exception.reason]]; + [self deleteEvent]; + completionHandler(); + return; + } + } + BSGPostJSONData(configuration.session, data, requestHeaders, notifyURL, ^(BSGDeliveryStatus status, __unused NSError *deliveryError) { switch (status) { case BSGDeliveryStatusDelivered: @@ -149,7 +167,7 @@ - (void)runWithDelegate:(id)delegate completion case BSGDeliveryStatusFailed: bsg_log_debug(@"Upload failed retryably for event %@", self.name); - [self prepareForRetry:originalPayload ?: eventPayload HTTPBodySize:data.length]; + [self prepareForRetry:retryPayload HTTPBodySize:data.length]; break; case BSGDeliveryStatusUndeliverable: diff --git a/Bugsnag/Payload/BugsnagEvent+Private.h b/Bugsnag/Payload/BugsnagEvent+Private.h index c7f3c0095..d8d004dca 100644 --- a/Bugsnag/Payload/BugsnagEvent+Private.h +++ b/Bugsnag/Payload/BugsnagEvent+Private.h @@ -73,6 +73,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSDictionary *)toJsonWithRedactedKeys:(nullable NSSet *)redactedKeys; +- (void)trimBreadcrumbs:(NSUInteger)bytesToRemove; + - (void)truncateStrings:(NSUInteger)maxLength; - (void)notifyUnhandledOverridden; diff --git a/Bugsnag/Payload/BugsnagEvent.m b/Bugsnag/Payload/BugsnagEvent.m index 46559fe83..6d7fb3286 100644 --- a/Bugsnag/Payload/BugsnagEvent.m +++ b/Bugsnag/Payload/BugsnagEvent.m @@ -10,6 +10,7 @@ #import "BSGDefines.h" #import "BSGFeatureFlagStore.h" +#import "BSGJSONSerialization.h" #import "BSGKeys.h" #import "BSGSerialization.h" #import "BSGUtils.h" @@ -690,6 +691,41 @@ - (void)symbolicateIfNeeded { } } +- (void)trimBreadcrumbs:(const NSUInteger)bytesToRemove { + NSMutableArray *breadcrumbs = [self.breadcrumbs mutableCopy]; + BugsnagBreadcrumb *lastRemovedBreadcrumb = nil; + NSUInteger bytesRemoved = 0, count = 0; + + while (bytesRemoved < bytesToRemove && breadcrumbs.count) { + lastRemovedBreadcrumb = [breadcrumbs firstObject]; + [breadcrumbs removeObjectAtIndex:0]; + + NSDictionary *dict = [lastRemovedBreadcrumb objectValue]; + NSData *data = BSGJSONDataFromDictionary(dict, NULL); + bytesRemoved += data.length; + count++; + } + + if (lastRemovedBreadcrumb) { + lastRemovedBreadcrumb.message = count < 2 ? @"Removed to reduce payload size" : + [NSString stringWithFormat:@"Removed, along with %lu older breadcrumb%s, to reduce payload size", + (unsigned long)(count - 1), count == 2 ? "" : "s"]; + lastRemovedBreadcrumb.metadata = @{}; + [breadcrumbs insertObject:lastRemovedBreadcrumb atIndex:0]; + } + + self.breadcrumbs = breadcrumbs; + + NSDictionary *usage = self.usage; + if (usage) { + self.usage = BSGDictMerge(@{ + @"system": @{ + @"breadcrumbBytesRemoved": @(bytesRemoved), + @"breadcrumbsRemoved": @(count)} + }, usage); + } +} + - (void)truncateStrings:(NSUInteger)maxLength { BSGTruncateContext context = { .maxLength = maxLength diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aaebdade..b9d90a5a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Changelog ### Enhancements +* Trim breadcrumb messages & metadata in payloads that exceed the size limit. + [#1451](https://github.com/bugsnag/bugsnag-cocoa/pull/1451) + * Truncate breadcrumb and metadata strings that are longer than `configuration.maxStringValueLength`. [#1449](https://github.com/bugsnag/bugsnag-cocoa/pull/1449) diff --git a/Tests/BugsnagTests/BugsnagEventTests.m b/Tests/BugsnagTests/BugsnagEventTests.m index e163f74a1..b4dfdc201 100644 --- a/Tests/BugsnagTests/BugsnagEventTests.m +++ b/Tests/BugsnagTests/BugsnagEventTests.m @@ -11,6 +11,7 @@ #import "BSG_RFC3339DateTool.h" #import "Bugsnag.h" +#import "BugsnagBreadcrumb+Private.h" #import "BugsnagClient+Private.h" #import "BugsnagEvent+Private.h" #import "BugsnagHandledState.h" @@ -321,6 +322,66 @@ - (void)testJsonToEventToJson { } } +- (void)testTrimBreadcrumbs { + BugsnagEvent *event = [BugsnagEvent new]; + + BugsnagBreadcrumb * (^ MakeBreadcrumb)() = ^(BSGBreadcrumbType type, NSString *message, NSDictionary *metadata) { + BugsnagBreadcrumb *breadcrumb = [BugsnagBreadcrumb new]; + breadcrumb.type = type; + breadcrumb.message = message; + breadcrumb.metadata = metadata; + return breadcrumb; + }; + + event.breadcrumbs = @[ + MakeBreadcrumb(BSGBreadcrumbTypeState, @"Test started", @{}), // 91 bytes + MakeBreadcrumb(BSGBreadcrumbTypeLog, @"Some log message", @{@"some": @"metadata"}), // 110 bytes + MakeBreadcrumb(BSGBreadcrumbTypeManual, @"The final breadcrumb", @{@"key": @"untouched"})]; + + event.usage = @{@"sentinel": @42}; // Enable gathering telemetry + + [event trimBreadcrumbs:100]; + + XCTAssertEqual(event.breadcrumbs.count, 2); + + XCTAssertEqual (event.breadcrumbs[0].type, BSGBreadcrumbTypeLog); + XCTAssertEqualObjects(event.breadcrumbs[0].message, @"Removed, along with 1 older breadcrumb, to reduce payload size"); + XCTAssertEqualObjects(event.breadcrumbs[0].metadata, @{}); + + XCTAssertEqual (event.breadcrumbs[1].type, BSGBreadcrumbTypeManual); + XCTAssertEqualObjects(event.breadcrumbs[1].message, @"The final breadcrumb"); + XCTAssertEqualObjects(event.breadcrumbs[1].metadata, @{@"key": @"untouched"}); + + XCTAssertEqualObjects(event.usage, (@{@"system": @{@"breadcrumbBytesRemoved": @(91 + 110), @"breadcrumbsRemoved": @2}, @"sentinel": @42})); +} + +- (void)testTrimSingleBreadcrumbs { + BugsnagEvent *event = [BugsnagEvent new]; + + BugsnagBreadcrumb *breadcrumb = [BugsnagBreadcrumb new]; + breadcrumb.message = @"" + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i" + "ncididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostru" + "d exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aut" + "e irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat n" + "ulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui" + " officia deserunt mollit anim id est laborum."; + breadcrumb.metadata = @{@"something": @"👍🏾🔥"}; + breadcrumb.type = BSGBreadcrumbTypeError; + event.breadcrumbs = @[breadcrumb]; + + NSUInteger byteCount = [NSJSONSerialization dataWithJSONObject:[breadcrumb objectValue] options:0 error:NULL].length; + + event.usage = @{}; // Enable gathering telemetry + + [event trimBreadcrumbs:100]; + + XCTAssertEqual (event.breadcrumbs[0].type, BSGBreadcrumbTypeError); + XCTAssertEqualObjects(event.breadcrumbs[0].message, @"Removed to reduce payload size"); + XCTAssertEqualObjects(event.breadcrumbs[0].metadata, @{}); + XCTAssertEqualObjects(event.usage, (@{@"system": @{@"breadcrumbBytesRemoved": @(byteCount), @"breadcrumbsRemoved": @1}})); +} + - (void)testTruncateStrings { BugsnagEvent *event = [BugsnagEvent new]; diff --git a/features/delivery.feature b/features/delivery.feature index 89f53ec80..d23409ea0 100644 --- a/features/delivery.feature +++ b/features/delivery.feature @@ -142,3 +142,17 @@ Feature: Delivery of errors Then the session "user.id" equals "3" And I discard the oldest session And the session "user.id" equals "2" + + Scenario: Breadcrumbs should be trimmed if payload is oversized + When I run "OversizedBreadcrumbsScenario" + And I wait to receive an error + Then the event "breadcrumbs" is an array with 10 elements + And the error "Content-Length" header matches the regex "^9\d{5}$" + And the event "breadcrumbs.0.metaData.a" is null + And the event "breadcrumbs.0.name" equals "Removed, along with 16 older breadcrumbs, to reduce payload size" + And the event "breadcrumbs.9.metaData.a" is not null + And the event "breadcrumbs.9.name" equals "Breadcrumb 25" + And the event "usage.system.breadcrumbBytesRemoved" equals 1602740 + And the event "usage.system.breadcrumbsRemoved" equals 17 + And the event "usage.system.stringCharsTruncated" is not null + And the event "usage.system.stringsTruncated" is not null diff --git a/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj b/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj index 94be40539..5ae5030bd 100644 --- a/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 01221E55282E5538008095C3 /* MaxPersistedSessionsScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 01221E54282E5538008095C3 /* MaxPersistedSessionsScenario.m */; }; 0163BFA72583B3CF008DC28B /* DiscardClassesHandledExceptionRegexScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0163BFA62583B3CF008DC28B /* DiscardClassesHandledExceptionRegexScenario.swift */; }; 017B4134276B8D9B0054C91D /* OnSendErrorPersistenceScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 017B4133276B8D9B0054C91D /* OnSendErrorPersistenceScenario.m */; }; + 017BA42428A1558A00CB985E /* OversizedBreadcrumbsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BA42328A1558A00CB985E /* OversizedBreadcrumbsScenario.swift */; }; 017DCFA028743FB5000ECB22 /* TelemetryUsageDisabledScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DCF9F28743FB5000ECB22 /* TelemetryUsageDisabledScenario.swift */; }; 01847DD626453D4E00ADA4C7 /* InvalidCrashReportScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 01847DD526453D4E00ADA4C7 /* InvalidCrashReportScenario.m */; }; 01AF6A53258A112F00FFC803 /* BareboneTestHandledScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AF6A52258A112F00FFC803 /* BareboneTestHandledScenario.swift */; }; @@ -252,6 +253,7 @@ 01221E54282E5538008095C3 /* MaxPersistedSessionsScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MaxPersistedSessionsScenario.m; sourceTree = ""; }; 0163BFA62583B3CF008DC28B /* DiscardClassesHandledExceptionRegexScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscardClassesHandledExceptionRegexScenario.swift; sourceTree = ""; }; 017B4133276B8D9B0054C91D /* OnSendErrorPersistenceScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OnSendErrorPersistenceScenario.m; sourceTree = ""; }; + 017BA42328A1558A00CB985E /* OversizedBreadcrumbsScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OversizedBreadcrumbsScenario.swift; sourceTree = ""; }; 017DCF9F28743FB5000ECB22 /* TelemetryUsageDisabledScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryUsageDisabledScenario.swift; sourceTree = ""; }; 01847DD526453D4E00ADA4C7 /* InvalidCrashReportScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InvalidCrashReportScenario.m; sourceTree = ""; }; 01AF6A52258A112F00FFC803 /* BareboneTestHandledScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BareboneTestHandledScenario.swift; sourceTree = ""; }; @@ -579,6 +581,7 @@ 010BAAFC2833CE570003FF36 /* OOMWillTerminateScenario.m */, E700EE58247D321B008CFFB6 /* OriginalErrorNSErrorScenario.swift */, E700EE5A247D3224008CFFB6 /* OriginalErrorNSExceptionScenario.swift */, + 017BA42328A1558A00CB985E /* OversizedBreadcrumbsScenario.swift */, 01F6B75C2832757F00B75C5D /* OversizedCrashReportScenario.swift */, 01F6B75D2832757F00B75C5D /* OversizedHandledErrorScenario.swift */, F4295B041F9CC494473DD226 /* OverwriteLinkRegisterScenario.m */, @@ -745,6 +748,7 @@ 010BAB252833D0070003FF36 /* AppHangDisabledScenario.swift in Sources */, E75040A02478019D005D33BD /* AutoDetectFalseHandledScenario.swift in Sources */, 01F115C927BAAF2D00892B1E /* SIGPIPEIgnoredScenario.m in Sources */, + 017BA42428A1558A00CB985E /* OversizedBreadcrumbsScenario.swift in Sources */, 8AB8866620404DD30003E444 /* ViewController.swift in Sources */, 010BAB0E2833CE570003FF36 /* UnhandledMachExceptionOverrideScenario.m in Sources */, 8AB8866420404DD30003E444 /* AppDelegate.swift in Sources */, diff --git a/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj b/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj index e0faffbb7..f3bb18aa6 100644 --- a/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj +++ b/features/fixtures/macos/macOSTestApp.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ 01AFCFC7282C058D00D48D45 /* OldSessionScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 01AFCFC6282C058D00D48D45 /* OldSessionScenario.m */; }; 01B6BB7225D56CBF00FC4DE6 /* LastRunInfoScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B6BB7125D56CBF00FC4DE6 /* LastRunInfoScenario.swift */; }; 01B6BBA225DA774C00FC4DE6 /* SendLaunchCrashesSynchronouslyScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B6BBA125DA774C00FC4DE6 /* SendLaunchCrashesSynchronouslyScenario.swift */; }; + 01BB5D2628A1463C00A7F322 /* OversizedBreadcrumbsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BB5D2528A1463C00A7F322 /* OversizedBreadcrumbsScenario.swift */; }; 01DCB82D279868160048640A /* ConcurrentCrashesScenario.mm in Sources */ = {isa = PBXBuildFile; fileRef = 01DCB82C279868160048640A /* ConcurrentCrashesScenario.mm */; }; 01DE903A26CEAD1200455213 /* CriticalThermalStateScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DE903926CEAD1200455213 /* CriticalThermalStateScenario.swift */; }; 01E0DB0625E8E95700A740ED /* AppDurationScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E0DB0425E8E90500A740ED /* AppDurationScenario.swift */; }; @@ -269,6 +270,7 @@ 01AFCFC6282C058D00D48D45 /* OldSessionScenario.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OldSessionScenario.m; sourceTree = ""; }; 01B6BB7125D56CBF00FC4DE6 /* LastRunInfoScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastRunInfoScenario.swift; sourceTree = ""; }; 01B6BBA125DA774C00FC4DE6 /* SendLaunchCrashesSynchronouslyScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendLaunchCrashesSynchronouslyScenario.swift; sourceTree = ""; }; + 01BB5D2528A1463C00A7F322 /* OversizedBreadcrumbsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OversizedBreadcrumbsScenario.swift; sourceTree = ""; }; 01DCB82C279868160048640A /* ConcurrentCrashesScenario.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ConcurrentCrashesScenario.mm; sourceTree = ""; }; 01DE903926CEAD1200455213 /* CriticalThermalStateScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CriticalThermalStateScenario.swift; sourceTree = ""; }; 01E0DB0425E8E90500A740ED /* AppDurationScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDurationScenario.swift; sourceTree = ""; }; @@ -530,6 +532,7 @@ 01F47C97254B1B2F00B184AD /* OOMWillTerminateScenario.m */, 01F47C86254B1B2F00B184AD /* OriginalErrorNSErrorScenario.swift */, 01F47C21254B1B2C00B184AD /* OriginalErrorNSExceptionScenario.swift */, + 01BB5D2528A1463C00A7F322 /* OversizedBreadcrumbsScenario.swift */, 01F6B74B2832381300B75C5D /* OversizedCrashReportScenario.swift */, 01F6B7492832381300B75C5D /* OversizedHandledErrorScenario.swift */, 01F47CC0254B1B3000B184AD /* OverwriteLinkRegisterScenario.m */, @@ -938,6 +941,7 @@ 010BAB672833D34A0003FF36 /* HandledErrorValidReleaseStageScenario.swift in Sources */, 01F47CD1254B1B3100B184AD /* ManualContextClientScenario.swift in Sources */, 010BAB732833D34A0003FF36 /* AppAndDeviceAttributesStartWithApiKeyScenario.swift in Sources */, + 01BB5D2628A1463C00A7F322 /* OversizedBreadcrumbsScenario.swift in Sources */, 01F47CC9254B1B3100B184AD /* BuiltinTrapScenario.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/features/fixtures/shared/scenarios/OversizedBreadcrumbsScenario.swift b/features/fixtures/shared/scenarios/OversizedBreadcrumbsScenario.swift new file mode 100644 index 000000000..12878e359 --- /dev/null +++ b/features/fixtures/shared/scenarios/OversizedBreadcrumbsScenario.swift @@ -0,0 +1,16 @@ +class OversizedBreadcrumbsScenario: Scenario { + + override func run() { + + var metadata: [String: String] = [:] + for char in "abcdefghij" { + metadata["\(char)"] = String(repeating: ".", count: 10_000) + } + + for i in 1...25 { + Bugsnag.leaveBreadcrumb("Breadcrumb \(i)", metadata: metadata, type: .navigation) + } + + Bugsnag.notifyError(NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError)) + } +} From 91d723c8967955a5c9d305cc0043df645953dac9 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 9 Aug 2022 09:57:13 +0100 Subject: [PATCH 20/24] Increase maxBreadcrumbs to 100 / 500 --- Bugsnag/Configuration/BugsnagConfiguration.m | 21 +++++++++++-------- .../include/Bugsnag/BugsnagConfiguration.h | 2 +- CHANGELOG.md | 3 +++ .../BSGConfigurationBuilderTests.m | 2 +- .../BugsnagTests/BugsnagConfigurationTests.m | 11 +++++----- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Bugsnag/Configuration/BugsnagConfiguration.m b/Bugsnag/Configuration/BugsnagConfiguration.m index f74d47f4e..b8b74652b 100644 --- a/Bugsnag/Configuration/BugsnagConfiguration.m +++ b/Bugsnag/Configuration/BugsnagConfiguration.m @@ -186,7 +186,7 @@ - (instancetype)initWithApiKey:(NSString *)apiKey { _enabledBreadcrumbTypes = BSGEnabledBreadcrumbTypeAll; _launchDurationMillis = 5000; _sendLaunchCrashesSynchronously = YES; - _maxBreadcrumbs = 50; + _maxBreadcrumbs = 100; _maxPersistedEvents = 32; _maxPersistedSessions = 128; _maxStringValueLength = 10000; @@ -509,15 +509,18 @@ - (NSUInteger)maxBreadcrumbs { } } -- (void)setMaxBreadcrumbs:(NSUInteger)maxBreadcrumbs { +- (void)setMaxBreadcrumbs:(NSUInteger)newValue { + static const NSUInteger maxAllowed = 500; + if (newValue > maxAllowed) { + bsg_log_err(@"Invalid configuration value detected. " + "Option maxBreadcrumbs should be an integer between 0-%lu. " + "Supplied value is %lu", + (unsigned long)maxAllowed, + (unsigned long)newValue); + return; + } @synchronized (self) { - if (maxBreadcrumbs <= 100) { - _maxBreadcrumbs = maxBreadcrumbs; - } else { - bsg_log_err(@"Invalid configuration value detected. Option maxBreadcrumbs " - "should be an integer between 0-100. Supplied value is %lu", - (unsigned long) maxBreadcrumbs); - } + _maxBreadcrumbs = newValue; } } diff --git a/Bugsnag/include/Bugsnag/BugsnagConfiguration.h b/Bugsnag/include/Bugsnag/BugsnagConfiguration.h index 7bdf59201..1b2e7c55b 100644 --- a/Bugsnag/include/Bugsnag/BugsnagConfiguration.h +++ b/Bugsnag/include/Bugsnag/BugsnagConfiguration.h @@ -330,7 +330,7 @@ BUGSNAG_EXTERN * Sets the maximum number of breadcrumbs which will be stored. Once the threshold is reached, * the oldest breadcrumbs will be deleted. * - * By default, 25 breadcrumbs are stored: this can be amended up to a maximum of 100. + * By default, 100 breadcrumbs are stored: this can be amended up to a maximum of 500. */ @property (nonatomic) NSUInteger maxBreadcrumbs; diff --git a/CHANGELOG.md b/CHANGELOG.md index b9d90a5a2..6027f0e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Changelog ### Enhancements +* Increase default and maximum values for `configuration.maxBreadcrumbs` to 100 and 500, respectively. + [#1452](https://github.com/bugsnag/bugsnag-cocoa/pull/1452) + * Trim breadcrumb messages & metadata in payloads that exceed the size limit. [#1451](https://github.com/bugsnag/bugsnag-cocoa/pull/1451) diff --git a/Tests/BugsnagTests/BSGConfigurationBuilderTests.m b/Tests/BugsnagTests/BSGConfigurationBuilderTests.m index 07cf2f4be..2d8bebdad 100644 --- a/Tests/BugsnagTests/BSGConfigurationBuilderTests.m +++ b/Tests/BugsnagTests/BSGConfigurationBuilderTests.m @@ -56,7 +56,7 @@ - (void)testDecodeDefaultValues { XCTAssertTrue(config.autoTrackSessions); XCTAssertEqual(config.maxPersistedEvents, 32); XCTAssertEqual(config.maxPersistedSessions, 128); - XCTAssertEqual(config.maxBreadcrumbs, 50); + XCTAssertEqual(config.maxBreadcrumbs, 100); XCTAssertTrue(config.persistUser); XCTAssertEqualObjects(@[@"password"], [config.redactedKeys allObjects]); XCTAssertEqual(BSGEnabledBreadcrumbTypeAll, config.enabledBreadcrumbTypes); diff --git a/Tests/BugsnagTests/BugsnagConfigurationTests.m b/Tests/BugsnagTests/BugsnagConfigurationTests.m index 81ac1f8a6..f58d8fe2a 100644 --- a/Tests/BugsnagTests/BugsnagConfigurationTests.m +++ b/Tests/BugsnagTests/BugsnagConfigurationTests.m @@ -585,15 +585,15 @@ - (void)testMaxPersistedSessions { - (void)testMaxBreadcrumb { BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; - XCTAssertEqual(50, config.maxBreadcrumbs); + XCTAssertEqual(100, config.maxBreadcrumbs); // alter to valid value config.maxBreadcrumbs = 10; XCTAssertEqual(10, config.maxBreadcrumbs); // alter to max value - config.maxBreadcrumbs = 100; - XCTAssertEqual(100, config.maxBreadcrumbs); + config.maxBreadcrumbs = 500; + XCTAssertEqual(500, config.maxBreadcrumbs); // alter to min value config.maxBreadcrumbs = 0; @@ -603,8 +603,8 @@ - (void)testMaxBreadcrumb { config.maxBreadcrumbs = -1; XCTAssertEqual(0, config.maxBreadcrumbs); - // alter to negative value (overflows) - config.maxBreadcrumbs = 500; + // alter to too large value + config.maxBreadcrumbs = 501; XCTAssertEqual(0, config.maxBreadcrumbs); } @@ -641,7 +641,6 @@ - (void)testDefaultConfigurationValues { XCTAssertNil(config.enabledReleaseStages); XCTAssertEqualObjects(@"https://notify.bugsnag.com", config.endpoints.notify); XCTAssertEqualObjects(@"https://sessions.bugsnag.com", config.endpoints.sessions); - XCTAssertEqual(50, config.maxBreadcrumbs); XCTAssertEqual(config.maxStringValueLength, 10000); XCTAssertTrue(config.persistUser); XCTAssertEqual(1, [config.redactedKeys count]); From 871ae4efa149c3ffeeba972e21910644f543e506 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 9 Aug 2022 10:57:38 +0100 Subject: [PATCH 21/24] Delete breadcrumb files on background queue --- Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.m | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.m b/Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.m index f3889402d..a1a5d6e10 100644 --- a/Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.m +++ b/Bugsnag/Breadcrumbs/BugsnagBreadcrumbs.m @@ -206,7 +206,15 @@ - (void)removeAllBreadcrumbs { } self.nextFileNumber = 0; } - [self deleteBreadcrumbFiles]; + dispatch_async(BSGGetFileSystemQueue(), ^{ + NSError *error = nil; + NSString *directory = self.breadcrumbsPath; + NSFileManager *fileManager = [NSFileManager new]; + if (![fileManager removeItemAtPath:directory error:&error] || + ![fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error]) { + bsg_log_debug(@"%s: %@", __FUNCTION__, error); + } + }); } #pragma mark - File storage @@ -275,15 +283,6 @@ - (NSString *)pathForFileNumber:(unsigned int)fileNumber { return breadcrumbs; } -- (void)deleteBreadcrumbFiles { - [[NSFileManager defaultManager] removeItemAtPath:self.breadcrumbsPath error:NULL]; - - NSError *error = nil; - if (![[NSFileManager defaultManager] createDirectoryAtPath:self.breadcrumbsPath withIntermediateDirectories:YES attributes:nil error:&error]) { - bsg_log_err(@"Unable to create breadcrumbs directory: %@", error); - } -} - @end #pragma mark - From be72b8d7609471da0084c9063a86a1c9616c8543 Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 9 Aug 2022 15:06:42 +0100 Subject: [PATCH 22/24] Fix potential deadlock in bsg_kscrw_i_writeThread() (#1453) --- .../Source/KSCrash/Recording/BSG_KSCrashReport.c | 16 ++++++++-------- .../Source/KSCrash/Recording/Tools/BSG_KSMach.c | 4 ++-- CHANGELOG.md | 3 +++ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c index 296c1c63d..2aa2b88fd 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c @@ -756,6 +756,7 @@ void bsg_kscrw_i_writeThread(const BSG_KSCrashReportWriter *const writer, int backtraceLength = sizeof(backtraceBuffer) / sizeof(*backtraceBuffer); int skippedEntries = 0; const char* state = bsg_kscrashthread_state_name(threadRunState); + char name[MAXTHREADNAMESIZE] = {0}; BSG_STRUCT_MCONTEXT_L *machineContext = bsg_kscrw_i_getMachineContext(crash, thread, &machineContextBuffer); @@ -784,14 +785,13 @@ void bsg_kscrw_i_writeThread(const BSG_KSCrashReportWriter *const writer, writer->addBooleanElement(writer, BSG_KSCrashField_CurrentThread, isSelfThread); - // Fetching the current thread name is only safe as of libpthread-330.201.1 - // which was used in ios+tvos 12 and macos 10.14 - if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_12_0) { - if (isSelfThread) { - char buff[100]; - bsg_ksmachgetThreadName(thread, buff, sizeof(buff)); - writer->addStringElement(writer, BSG_KSCrashField_Name, buff); - } + // pthread_getname_np() acquires no locks if passed pthread_self() as + // of libpthread-330.201.1 (macOS 10.14 / iOS 12) + if (isSelfThread && + kCFCoreFoundationVersionNumber >= + kCFCoreFoundationVersionNumber_iOS_12_0 && + !pthread_getname_np(pthread_self(), name, sizeof(name))) { + writer->addStringElement(writer, BSG_KSCrashField_Name, name); } if (isCrashedThread && machineContext != NULL) { bsg_kscrw_i_writeStackOverflow(writer, BSG_KSCrashField_Stack, diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c index 8ef2d431a..c51674977 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/Tools/BSG_KSMach.c @@ -224,8 +224,8 @@ pthread_t bsg_ksmachpthreadFromMachThread(const thread_t thread) { bool bsg_ksmachgetThreadName(const thread_t thread, char *const buffer, size_t bufLength) { - // WARNING: This implementation is only async-safe for the current thread - // as of libpthread-330.201.1, and is still unsafe for other threads. + // WARNING: This implementation is not async-safe because + // pthread_from_mach_thread_np() acquires an internal lock. const pthread_t pthread = pthread_from_mach_thread_np(thread); if (pthread == NULL) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 6027f0e29..3b9793804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ Changelog ### Bug fixes +* Fix a potential deadlock when capturing the crashing thread's name. + [#1453](https://github.com/bugsnag/bugsnag-cocoa/pull/1453) + * Attempt to send sessions stored on disk when connection regained. [#1445](https://github.com/bugsnag/bugsnag-cocoa/pull/1445) From c183e69f32449d60db84119c3c2b737a66678cca Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Wed, 10 Aug 2022 09:41:47 +0100 Subject: [PATCH 23/24] Prevent multiple calls to `[BSGStorageMigratorV0V1 migrate]` (#1454) --- Bugsnag.xcodeproj/project.pbxproj | 20 +++++++++---------- Bugsnag/Bugsnag.m | 13 +++--------- ...rTests.m => BSGStorageMigratorV0V1Tests.m} | 6 +++--- 3 files changed, 16 insertions(+), 23 deletions(-) rename Tests/BugsnagTests/{BSGStorageMigratorTests.m => BSGStorageMigratorV0V1Tests.m} (98%) diff --git a/Bugsnag.xcodeproj/project.pbxproj b/Bugsnag.xcodeproj/project.pbxproj index 082d79853..c3a4006f1 100644 --- a/Bugsnag.xcodeproj/project.pbxproj +++ b/Bugsnag.xcodeproj/project.pbxproj @@ -844,7 +844,7 @@ CB28F0BC282A4817003AB200 /* report-react-native-promise-rejection.json in Resources */ = {isa = PBXBuildFile; fileRef = 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */; }; CB28F0BD282A4817003AB200 /* report.json in Resources */ = {isa = PBXBuildFile; fileRef = 008966B72486D43500DC48C2 /* report.json */; }; CB28F0BE282A4818003AB200 /* v0_files in Resources */ = {isa = PBXBuildFile; fileRef = CB6419B325A7419400613D25 /* v0_files */; }; - CB28F0BF282A4842003AB200 /* BSGStorageMigratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6419AA25A73E8C00613D25 /* BSGStorageMigratorTests.m */; }; + CB28F0BF282A4842003AB200 /* BSGStorageMigratorV0V1Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6419AA25A73E8C00613D25 /* BSGStorageMigratorV0V1Tests.m */; }; CB28F0C2282A4857003AB200 /* BSGUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01DE903B26CEAF9E00455213 /* BSGUtilsTests.m */; }; CB28F0C4282A488B003AB200 /* BugsnagApiClientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9103632502320A00E9D1E2 /* BugsnagApiClientTest.m */; }; CB28F0C5282A49D5003AB200 /* URLSessionMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 01E8765D256684E700F4B70A /* URLSessionMock.m */; }; @@ -910,7 +910,7 @@ CB4C83BF280FFB0600E7E2BD /* BSGDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = CB4C83BD280FFA4800E7E2BD /* BSGDefines.h */; }; CB4C83C0280FFB0600E7E2BD /* BSGDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = CB4C83BD280FFA4800E7E2BD /* BSGDefines.h */; }; CB4C83C1280FFB0700E7E2BD /* BSGDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = CB4C83BD280FFA4800E7E2BD /* BSGDefines.h */; }; - CB6419AB25A73E8C00613D25 /* BSGStorageMigratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6419AA25A73E8C00613D25 /* BSGStorageMigratorTests.m */; }; + CB6419AB25A73E8C00613D25 /* BSGStorageMigratorV0V1Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6419AA25A73E8C00613D25 /* BSGStorageMigratorV0V1Tests.m */; }; CB6419B425A7419400613D25 /* v0_files in Resources */ = {isa = PBXBuildFile; fileRef = CB6419B325A7419400613D25 /* v0_files */; }; CB6419B525A7419400613D25 /* v0_files in Resources */ = {isa = PBXBuildFile; fileRef = CB6419B325A7419400613D25 /* v0_files */; }; CB6419B625A7419400613D25 /* v0_files in Resources */ = {isa = PBXBuildFile; fileRef = CB6419B325A7419400613D25 /* v0_files */; }; @@ -1113,8 +1113,8 @@ CBCF77AB250142E0004AF22A /* BSGJSONSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCF77AA250142E0004AF22A /* BSGJSONSerializationTests.m */; }; CBCF77AC250142E0004AF22A /* BSGJSONSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCF77AA250142E0004AF22A /* BSGJSONSerializationTests.m */; }; CBCF77AD250142E0004AF22A /* BSGJSONSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCF77AA250142E0004AF22A /* BSGJSONSerializationTests.m */; }; - CBDD6D0725AC3EFF00A2E12B /* BSGStorageMigratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6419AA25A73E8C00613D25 /* BSGStorageMigratorTests.m */; }; - CBDD6D0F25AC3EFF00A2E12B /* BSGStorageMigratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6419AA25A73E8C00613D25 /* BSGStorageMigratorTests.m */; }; + CBDD6D0725AC3EFF00A2E12B /* BSGStorageMigratorV0V1Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6419AA25A73E8C00613D25 /* BSGStorageMigratorV0V1Tests.m */; }; + CBDD6D0F25AC3EFF00A2E12B /* BSGStorageMigratorV0V1Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6419AA25A73E8C00613D25 /* BSGStorageMigratorV0V1Tests.m */; }; CBE9062A25A34DAB0045B965 /* BSGStorageMigratorV0V1.h in Headers */ = {isa = PBXBuildFile; fileRef = CBE9062825A34DAB0045B965 /* BSGStorageMigratorV0V1.h */; }; CBE9062B25A34DAB0045B965 /* BSGStorageMigratorV0V1.h in Headers */ = {isa = PBXBuildFile; fileRef = CBE9062825A34DAB0045B965 /* BSGStorageMigratorV0V1.h */; }; CBE9062C25A34DAB0045B965 /* BSGStorageMigratorV0V1.h in Headers */ = {isa = PBXBuildFile; fileRef = CBE9062825A34DAB0045B965 /* BSGStorageMigratorV0V1.h */; }; @@ -1663,7 +1663,7 @@ CB37449B284756C100A3955E /* stb_sprintf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stb_sprintf.h; sourceTree = ""; }; CB37449F28475EA800A3955E /* BSG_KSCrashStringConversionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSG_KSCrashStringConversionTest.m; sourceTree = ""; }; CB4C83BD280FFA4800E7E2BD /* BSGDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGDefines.h; sourceTree = ""; }; - CB6419AA25A73E8C00613D25 /* BSGStorageMigratorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGStorageMigratorTests.m; sourceTree = ""; }; + CB6419AA25A73E8C00613D25 /* BSGStorageMigratorV0V1Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGStorageMigratorV0V1Tests.m; sourceTree = ""; }; CB6419B325A7419400613D25 /* v0_files */ = {isa = PBXFileReference; lastKnownFileType = folder; path = v0_files; sourceTree = ""; }; CB9103632502320A00E9D1E2 /* BugsnagApiClientTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagApiClientTest.m; sourceTree = ""; }; CBA22499251E429C00B87416 /* TestSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestSupport.h; sourceTree = ""; }; @@ -2039,7 +2039,7 @@ 008966C82486D43600DC48C2 /* BSGOutOfMemoryTests.m */, 0130DEF82880203A00E5953F /* BSGRunContextTests.m */, 01F9FCB528929336005EDD8C /* BSGSerializationTests.m */, - CB6419AA25A73E8C00613D25 /* BSGStorageMigratorTests.m */, + CB6419AA25A73E8C00613D25 /* BSGStorageMigratorV0V1Tests.m */, 017DCF9A287422BB000ECB22 /* BSGTelemetryTests.m */, 01DE903B26CEAF9E00455213 /* BSGUtilsTests.m */, CB9103632502320A00E9D1E2 /* BugsnagApiClientTest.m */, @@ -3262,7 +3262,7 @@ CB9103642502320A00E9D1E2 /* BugsnagApiClientTest.m in Sources */, 008967602486D43700DC48C2 /* BugsnagBreadcrumbsTest.m in Sources */, 01B74E9C27903326004B9765 /* BSG_KSFileTests.m in Sources */, - CB6419AB25A73E8C00613D25 /* BSGStorageMigratorTests.m in Sources */, + CB6419AB25A73E8C00613D25 /* BSGStorageMigratorV0V1Tests.m in Sources */, E701FAA72490EF77008D842F /* ClientApiValidationTest.m in Sources */, 008967362486D43700DC48C2 /* BugsnagMetadataTests.m in Sources */, 008967992486D43700DC48C2 /* FileBasedTestCase.m in Sources */, @@ -3391,7 +3391,7 @@ 0089672B2486D43700DC48C2 /* BugsnagStacktraceTest.m in Sources */, 008966F22486D43700DC48C2 /* BugsnagMetadataRedactionTest.m in Sources */, 01ABD87A2786DF9A009A5CA2 /* BSG_KSCrashReportTests.m in Sources */, - CBDD6D0725AC3EFF00A2E12B /* BSGStorageMigratorTests.m in Sources */, + CBDD6D0725AC3EFF00A2E12B /* BSGStorageMigratorV0V1Tests.m in Sources */, 008967372486D43700DC48C2 /* BugsnagMetadataTests.m in Sources */, 008967822486D43700DC48C2 /* KSSystemInfo_Tests.m in Sources */, 008967612486D43700DC48C2 /* BugsnagBreadcrumbsTest.m in Sources */, @@ -3543,7 +3543,7 @@ 008967922486D43700DC48C2 /* KSJSONCodec_Tests.m in Sources */, 008967742486D43700DC48C2 /* KSSysCtl_Tests.m in Sources */, 010F80C428645B4200D6569E /* BSGDefinesTests.m in Sources */, - CBDD6D0F25AC3EFF00A2E12B /* BSGStorageMigratorTests.m in Sources */, + CBDD6D0F25AC3EFF00A2E12B /* BSGStorageMigratorV0V1Tests.m in Sources */, 008967502486D43700DC48C2 /* BugsnagPluginTest.m in Sources */, 008967142486D43700DC48C2 /* BugsnagEventTests.m in Sources */, 0089675C2486D43700DC48C2 /* BugsnagEnabledBreadcrumbTest.m in Sources */, @@ -3868,7 +3868,7 @@ CB28F0E1282A4BEF003AB200 /* BugsnagPluginTest.m in Sources */, CB28F11F282A7C97003AB200 /* BugsnagSwiftPublicAPITests.swift in Sources */, CB28F0C7282A49EF003AB200 /* NSUserDefaultsStub.m in Sources */, - CB28F0BF282A4842003AB200 /* BSGStorageMigratorTests.m in Sources */, + CB28F0BF282A4842003AB200 /* BSGStorageMigratorV0V1Tests.m in Sources */, CB28F0AB28294D4F003AB200 /* KSLogger_Tests.m in Sources */, CB28F126282A7DB0003AB200 /* ClientApiValidationTest.m in Sources */, CB28F0A328294D4F003AB200 /* KSCrashIdentifierTests.m in Sources */, diff --git a/Bugsnag/Bugsnag.m b/Bugsnag/Bugsnag.m index c180c63c1..12e902c21 100644 --- a/Bugsnag/Bugsnag.m +++ b/Bugsnag/Bugsnag.m @@ -26,18 +26,11 @@ #import "Bugsnag.h" -#import "BSGKeys.h" -#import "BSG_KSCrash.h" +#import "BSGStorageMigratorV0V1.h" #import "Bugsnag+Private.h" #import "BugsnagBreadcrumbs.h" -#import "BugsnagLogger.h" #import "BugsnagClient+Private.h" -#import "BugsnagConfiguration+Private.h" -#import "BugsnagMetadata+Private.h" -#import "BugsnagPlugin.h" -#import "BugsnagHandledState.h" -#import "BugsnagSystemState.h" -#import "BSGStorageMigratorV0V1.h" +#import "BugsnagLogger.h" static BugsnagClient *bsg_g_bugsnag_client = NULL; @@ -56,8 +49,8 @@ + (BugsnagClient *_Nonnull)startWithApiKey:(NSString *_Nonnull)apiKey { + (BugsnagClient *_Nonnull)startWithConfiguration:(BugsnagConfiguration *_Nonnull)configuration { @synchronized(self) { - [BSGStorageMigratorV0V1 migrate]; if (bsg_g_bugsnag_client == nil) { + [BSGStorageMigratorV0V1 migrate]; bsg_g_bugsnag_client = [[BugsnagClient alloc] initWithConfiguration:configuration]; [bsg_g_bugsnag_client start]; } else { diff --git a/Tests/BugsnagTests/BSGStorageMigratorTests.m b/Tests/BugsnagTests/BSGStorageMigratorV0V1Tests.m similarity index 98% rename from Tests/BugsnagTests/BSGStorageMigratorTests.m rename to Tests/BugsnagTests/BSGStorageMigratorV0V1Tests.m index 946a132f7..1c928d61c 100644 --- a/Tests/BugsnagTests/BSGStorageMigratorTests.m +++ b/Tests/BugsnagTests/BSGStorageMigratorV0V1Tests.m @@ -1,5 +1,5 @@ // -// BSGStorageMigratorTests.m +// BSGStorageMigratorV0V1Tests.m // Bugsnag-iOSTests // // Created by Karl Stenerud on 07.01.21. @@ -12,11 +12,11 @@ #import "BugsnagConfiguration+Private.h" #import "BugsnagTestConstants.h" -@interface BSGStorageMigratorTests : XCTestCase +@interface BSGStorageMigratorV0V1Tests : XCTestCase @end -@implementation BSGStorageMigratorTests +@implementation BSGStorageMigratorV0V1Tests - (NSString *)getCachesDir { NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); From c989fa1376dab98d7a06680cf2728094ae6d0c2d Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Wed, 10 Aug 2022 10:29:05 +0100 Subject: [PATCH 24/24] Release v6.22.0 --- .jazzy.yaml | 4 ++-- Bugsnag.podspec.json | 4 ++-- Bugsnag/Payload/BugsnagNotifier.m | 2 +- BugsnagNetworkRequestPlugin.podspec.json | 6 +++--- CHANGELOG.md | 2 +- Framework/Info.plist | 2 +- Tests/BugsnagTests/Info.plist | 2 +- Tests/TestHost-iOS/Info.plist | 2 +- VERSION | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.jazzy.yaml b/.jazzy.yaml index 275f46f7d..1dd85d2e7 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -2,11 +2,11 @@ author_url: "https://www.bugsnag.com" author: "Bugsnag Inc" clean: false # avoid deleting docs/.git framework_root: "Bugsnag" -github_file_prefix: "https://github.com/bugsnag/bugsnag-cocoa/tree/v6.21.0/Bugsnag" +github_file_prefix: "https://github.com/bugsnag/bugsnag-cocoa/tree/v6.22.0/Bugsnag" github_url: "https://github.com/bugsnag/bugsnag-cocoa" hide_documentation_coverage: true module: "Bugsnag" -module_version: "6.21.0" +module_version: "6.22.0" objc: true output: "docs" readme: "README.md" diff --git a/Bugsnag.podspec.json b/Bugsnag.podspec.json index 6b5f14261..b88cc5706 100644 --- a/Bugsnag.podspec.json +++ b/Bugsnag.podspec.json @@ -1,6 +1,6 @@ { "name": "Bugsnag", - "version": "6.21.0", + "version": "6.22.0", "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.21.0" + "tag": "v6.22.0" }, "ios": { "frameworks": [ diff --git a/Bugsnag/Payload/BugsnagNotifier.m b/Bugsnag/Payload/BugsnagNotifier.m index 0772cdf08..e36626238 100644 --- a/Bugsnag/Payload/BugsnagNotifier.m +++ b/Bugsnag/Payload/BugsnagNotifier.m @@ -23,7 +23,7 @@ - (instancetype)init { #else _name = @"Bugsnag Objective-C"; #endif - _version = @"6.21.0"; + _version = @"6.22.0"; _url = @"https://github.com/bugsnag/bugsnag-cocoa"; _dependencies = @[]; } diff --git a/BugsnagNetworkRequestPlugin.podspec.json b/BugsnagNetworkRequestPlugin.podspec.json index 5230551a3..ea4bcec52 100644 --- a/BugsnagNetworkRequestPlugin.podspec.json +++ b/BugsnagNetworkRequestPlugin.podspec.json @@ -1,16 +1,16 @@ { "name": "BugsnagNetworkRequestPlugin", - "version": "6.21.0", + "version": "6.22.0", "summary": "Network request monitoring support for Bugsnag.", "homepage": "https://bugsnag.com", "license": "MIT", "authors": { "Bugsnag": "notifiers@bugsnag.com" }, - "readme": "https://raw.githubusercontent.com/bugsnag/bugsnag-cocoa/v6.21.0/BugsnagNetworkRequestPlugin/README.md", + "readme": "https://raw.githubusercontent.com/bugsnag/bugsnag-cocoa/v6.22.0/BugsnagNetworkRequestPlugin/README.md", "source": { "git": "https://github.com/bugsnag/bugsnag-cocoa.git", - "tag": "v6.21.0" + "tag": "v6.22.0" }, "dependencies": { "Bugsnag": "~> 6.13" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9793804..c90a15ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Changelog ========= -## TBD +## 6.22.0 (2022-08-10) ### Enhancements diff --git a/Framework/Info.plist b/Framework/Info.plist index 1ba8e5e0f..898aaebdd 100644 --- a/Framework/Info.plist +++ b/Framework/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.21.0 + 6.22.0 CFBundleVersion 1 diff --git a/Tests/BugsnagTests/Info.plist b/Tests/BugsnagTests/Info.plist index 279bfe3c2..1625799ca 100644 --- a/Tests/BugsnagTests/Info.plist +++ b/Tests/BugsnagTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 6.21.0 + 6.22.0 CFBundleVersion 1 diff --git a/Tests/TestHost-iOS/Info.plist b/Tests/TestHost-iOS/Info.plist index b267cca15..37c96ce64 100644 --- a/Tests/TestHost-iOS/Info.plist +++ b/Tests/TestHost-iOS/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 6.21.0 + 6.22.0 CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/VERSION b/VERSION index 7ecad1405..fe6750473 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.21.0 +6.22.0