Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PLAT-7702] Fix currentAppState when called before UIApplicationMain #1248

Merged
merged 1 commit into from
Dec 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Bugsnag.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@
01847DAD26441A5E00ADA4C7 /* BSGInternalErrorReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01847DAB26441A5E00ADA4C7 /* BSGInternalErrorReporterTests.m */; };
01847DAE26441A5E00ADA4C7 /* BSGInternalErrorReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01847DAB26441A5E00ADA4C7 /* BSGInternalErrorReporterTests.m */; };
0187D464255BD7B800C503D9 /* BugsnagApiClientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9103632502320A00E9D1E2 /* BugsnagApiClientTest.m */; };
01935AE2275E68E9007498B3 /* UIApplicationStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 01935AE1275E68E9007498B3 /* UIApplicationStub.m */; };
01935AE3275E68E9007498B3 /* UIApplicationStub.m in Sources */ = {isa = PBXBuildFile; fileRef = 01935AE1275E68E9007498B3 /* UIApplicationStub.m */; };
019480D42625F3EB00E833ED /* BSGAppKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 019480D32625F3EB00E833ED /* BSGAppKitTests.m */; };
01A2C542271EB9B400A27B23 /* BSG_Symbolicate.c in Sources */ = {isa = PBXBuildFile; fileRef = 01A2C540271EB9B300A27B23 /* BSG_Symbolicate.c */; };
01A2C543271EB9B400A27B23 /* BSG_Symbolicate.c in Sources */ = {isa = PBXBuildFile; fileRef = 01A2C540271EB9B300A27B23 /* BSG_Symbolicate.c */; };
Expand Down Expand Up @@ -1336,6 +1338,8 @@
01847D942644140F00ADA4C7 /* BSGInternalErrorReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSGInternalErrorReporter.h; sourceTree = "<group>"; };
01847D952644140F00ADA4C7 /* BSGInternalErrorReporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSGInternalErrorReporter.m; sourceTree = "<group>"; };
01847DAB26441A5E00ADA4C7 /* BSGInternalErrorReporterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGInternalErrorReporterTests.m; sourceTree = "<group>"; };
01935AE0275E68E9007498B3 /* UIApplicationStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIApplicationStub.h; sourceTree = "<group>"; };
01935AE1275E68E9007498B3 /* UIApplicationStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIApplicationStub.m; sourceTree = "<group>"; };
01937CF9257A7B4C00F2DE31 /* Bugsnag+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bugsnag+Private.h"; sourceTree = "<group>"; };
01937D11257A814D00F2DE31 /* BugsnagMetadata+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagMetadata+Private.h"; sourceTree = "<group>"; };
01937D2E257A83A900F2DE31 /* BugsnagApp+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagApp+Private.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1795,6 +1799,8 @@
004E35392487B375007FBAE4 /* Tests-Bridging-Header.h */,
CBA22499251E429C00B87416 /* TestSupport.h */,
CBA2249A251E429C00B87416 /* TestSupport.m */,
01935AE0275E68E9007498B3 /* UIApplicationStub.h */,
01935AE1275E68E9007498B3 /* UIApplicationStub.m */,
013D9CCF26C5262F0077F0AD /* UISceneStub.h */,
013D9CD026C5262F0077F0AD /* UISceneStub.m */,
01E8765C256684E700F4B70A /* URLSessionMock.h */,
Expand Down Expand Up @@ -2780,6 +2786,7 @@
008967782486D43700DC48C2 /* BSG_KSMachHeadersTests.m in Sources */,
0089673F2486D43700DC48C2 /* BugsnagAppTest.m in Sources */,
0089675A2486D43700DC48C2 /* BugsnagEnabledBreadcrumbTest.m in Sources */,
01935AE2275E68E9007498B3 /* UIApplicationStub.m in Sources */,
008967422486D43700DC48C2 /* BugsnagSessionTrackerStopTest.m in Sources */,
E701FAAF2490EFE8008D842F /* ConfigurationApiValidationTest.m in Sources */,
008967452486D43700DC48C2 /* BugsnagTests.m in Sources */,
Expand Down Expand Up @@ -3138,6 +3145,7 @@
01847DAE26441A5E00ADA4C7 /* BSGInternalErrorReporterTests.m in Sources */,
004E35372487AFF2007FBAE4 /* BugsnagHandledStateTest.m in Sources */,
016875C8258D003200DFFF69 /* NSUserDefaultsStub.m in Sources */,
01935AE3275E68E9007498B3 /* UIApplicationStub.m in Sources */,
01C17AE92542ED7F00C102C9 /* KSCrashReportWriterTests.m in Sources */,
00896A422486DBDD00DC48C2 /* BSGConfigurationBuilderTests.m in Sources */,
008967682486D43700DC48C2 /* BugsnagNotifierTest.m in Sources */,
Expand Down
24 changes: 17 additions & 7 deletions Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSSystemInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -453,31 +453,40 @@ + (BOOL)isRunningInAppExtension {
}

#if BSG_PLATFORM_IOS || BSG_PLATFORM_TVOS

+ (UIApplicationState)currentAppState {
// Only checked outside of app extensions since sharedApplication is
// unavailable to extension UIKit APIs
if ([self isRunningInAppExtension]) {
return UIApplicationStateActive;
}

UIApplicationState(^getState)(void) = ^() {
UIApplication * (^ getSharedApplication)(void) = ^() {
// Calling this API indirectly to avoid a compile-time check that
// [UIApplication sharedApplication] is not called from app extensions
// (which is handled above)
UIApplication *app = [UIAPPLICATION performSelector:@selector(sharedApplication)];
return [app applicationState];
return [UIAPPLICATION performSelector:@selector(sharedApplication)];
};

__block UIApplication *application = nil;
if ([[NSThread currentThread] isMainThread]) {
return getState();
application = getSharedApplication();
} else {
// [UIApplication sharedApplication] is a main thread-only API
__block UIApplicationState state;
dispatch_sync(dispatch_get_main_queue(), ^{
state = getState();
application = getSharedApplication();
});
return state;
}

// There will be no UIApplication if UIApplicationMain() has not yet been
// called. This happens if started from a SwiftUI app's init() function or
// UIKit app's main() function. Returning UIApplicationStateActive (0) would
// be higly misleading, so we must check for this condition.
if (!application) {
return UIApplicationStateBackground;
}

return application.applicationState;
}

+ (BOOL)isInForeground:(UIApplicationState)state {
Expand All @@ -494,6 +503,7 @@ + (BOOL)isInForeground:(UIApplicationState)state {
return state == UIApplicationStateInactive
|| state == UIApplicationStateActive;
}

#endif

@end
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

## TBD

### Bug fixes

* Fix `UIApplicationState` detection when started from a SwiftUI app's `init()` function.
This fixes false positive OOMs on iOS 15 for apps that have been prewarmed without transitioning to the foreground.
[#1248](https://github.com/bugsnag/bugsnag-cocoa/pull/1248)

## 6.15.0 (2021-12-01)

### Enhancements
Expand Down
2 changes: 2 additions & 0 deletions Tests/BugsnagTests/BugsnagConfigurationTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ - (void)testAddOnSessionBlock {
// Call onSession blocks
BugsnagClient *client = [[BugsnagClient alloc] initWithConfiguration:config];
[client start];
[client resumeSession];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}

Expand Down Expand Up @@ -183,6 +184,7 @@ - (void)testAddOnSessionBlockThenRemove {
// Call onSession blocks
BugsnagClient *client = [[BugsnagClient alloc] initWithConfiguration:config];
[client start];
[client resumeSession];
[self waitForExpectations:@[expectation1] timeout:1.0];

// Check it's called on new session start
Expand Down
26 changes: 26 additions & 0 deletions Tests/BugsnagTests/UIApplicationStub.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// UIApplicationStub.h
// Bugsnag
//
// Created by Nick Dowell on 06/12/2021.
// Copyright © 2021 Bugsnag Inc. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIApplicationStub : NSObject

@property (nonatomic) UIApplicationState applicationState;

@end

@interface XCTestCase (UIApplicationStub)

- (void)setUpUIApplicationStub;

@end

NS_ASSUME_NONNULL_END
38 changes: 38 additions & 0 deletions Tests/BugsnagTests/UIApplicationStub.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// UIApplicationStub.m
// Bugsnag
//
// Created by Nick Dowell on 06/12/2021.
// Copyright © 2021 Bugsnag Inc. All rights reserved.
//

#import "UIApplicationStub.h"

#import <objc/runtime.h>


@implementation UIApplicationStub

- (BOOL)isKindOfClass:(Class)aClass {
return aClass == [UIApplication class] || [super isKindOfClass:aClass];
}

@end


@implementation XCTestCase (UIApplicationStub)

- (void)setUpUIApplicationStub {
Method method = class_getClassMethod([UIApplication class], @selector(sharedApplication));
NSParameterAssert(method != NULL);

void *originalImplementation = method_setImplementation(method, imp_implementationWithBlock(^(){
return [[UIApplicationStub alloc] init];
}));

[self addTeardownBlock:^{
method_setImplementation(method, originalImplementation);
}];
}

@end
15 changes: 15 additions & 0 deletions Tests/KSCrashTests/KSCrashState_Tests.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,28 @@
#import "BSG_KSCrashState.h"
#import "BSG_KSCrashC.h"

#if TARGET_OS_TV
#import "UIApplicationStub.h"
#endif


@interface bsg_kscrashstate_Tests : FileBasedTestCase
@end


@implementation bsg_kscrashstate_Tests

#if TARGET_OS_TV // Not needed on iOS because there the tests are injected into a host app

- (void)setUp
{
[self setUpUIApplicationStub]; // These tests assume applicationState == .active

[super setUp];
}

#endif

- (void) testInitRelaunch
{
BSG_KSCrash_State context = {0};
Expand Down
8 changes: 7 additions & 1 deletion Tests/KSCrashTests/KSSystemInfo_Tests.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
#import "BSG_KSSystemInfo.h"
#import "BSG_KSSystemInfoC.h"

#if TARGET_OS_TV
#import "UIApplicationStub.h"
#endif


@interface KSSystemInfo_Tests : XCTestCase @end

Expand Down Expand Up @@ -61,7 +65,9 @@ - (void) testCopyProcessName

#if BSG_PLATFORM_TVOS || BSG_PLATFORM_IOS
- (void)testCurrentAppState {
// Should default to active as tests aren't in an app bundle
#if TARGET_OS_TV
[self setUpUIApplicationStub];
#endif
XCTAssertEqual(UIApplicationStateActive, [BSG_KSSystemInfo currentAppState]);
}

Expand Down