mirror of
https://github.com/google/GoogleUtilities
synced 2026-11-09 22:25:54 +05:45
fix(environment): cache App Store environment checks (#254)
`+[GULAppEnvironmentUtil isAppStoreReceiptSandbox]` read `-[NSBundle appStoreReceiptURL]` on every call. That property makes a synchronous XPC call to the LaunchServices daemon, which can block the calling thread for seconds when the daemon is busy. `+isFromAppStore` repeated the call because it only cached `IsAppEncrypted()`. Firebase calls these from the main thread (heartbeat user agent, Messaging APNs token handling), which caused hangs and watchdog terminations. Compute both values once per process with `dispatch_once`. Their inputs (receipt type, bundle contents, Mach-O encryption info, and the Info.plist opt-out key) can't change while the app is running. The original bodies move unchanged into file-local helpers. Part of firebase/firebase-ios-sdk#16726
This commit is contained in:
1 parent
cc31f5ed46
commit
34314b8f75
3 files changed
+71
-10
No files matched your search
@@ -1,3 +1,10 @@
|
||||
# Unreleased
|
||||
- [fixed] `GULAppEnvironmentUtil` now computes `isAppStoreReceiptSandbox` and
|
||||
`isFromAppStore` once per process and caches the results. Previously every
|
||||
call read `-[NSBundle appStoreReceiptURL]`, which makes a synchronous XPC call
|
||||
to the LaunchServices daemon and could block the calling thread, often the
|
||||
main thread, for seconds. (firebase/firebase-ios-sdk#16726)
|
||||
|
||||
# 8.1.4
|
||||
- [fixed] `GULMutableDictionary` now ignores a `nil` key (and a `nil` object in
|
||||
`setObject:forKey:`) instead of throwing on its internal queue, which crashed
|
||||
|
||||
@@ -64,14 +64,7 @@ static BOOL HasEmbeddedMobileProvision(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (BOOL)isFromAppStore {
|
||||
static dispatch_once_t isEncryptedOnce;
|
||||
static BOOL isEncrypted = NO;
|
||||
|
||||
dispatch_once(&isEncryptedOnce, ^{
|
||||
isEncrypted = IsAppEncrypted();
|
||||
});
|
||||
|
||||
static BOOL IsFromAppStore(void) {
|
||||
if ([GULAppEnvironmentUtil isSimulator]) {
|
||||
return NO;
|
||||
}
|
||||
@@ -93,10 +86,10 @@ static BOOL HasEmbeddedMobileProvision(void) {
|
||||
// the iTunesMetadata.plist outside of the sandbox will be rejected by Apple.
|
||||
// If the app does not contain the embedded.mobileprovision which is stripped out by Apple when
|
||||
// the app is submitted to store, then it is highly likely that it is from Apple Store.
|
||||
return isEncrypted && !HasEmbeddedMobileProvision();
|
||||
return IsAppEncrypted() && !HasEmbeddedMobileProvision();
|
||||
}
|
||||
|
||||
+ (BOOL)isAppStoreReceiptSandbox {
|
||||
static BOOL IsAppStoreReceiptSandbox(void) {
|
||||
// Since checking the App Store's receipt URL can be memory intensive, check the option in the
|
||||
// Info.plist if developers opted out of this check.
|
||||
id enableSandboxCheck =
|
||||
@@ -111,6 +104,34 @@ static BOOL HasEmbeddedMobileProvision(void) {
|
||||
return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName];
|
||||
}
|
||||
|
||||
+ (BOOL)isFromAppStore {
|
||||
// The inputs (install source and bundle contents) can't change while the app is running, so
|
||||
// compute the result once.
|
||||
static dispatch_once_t isFromAppStoreOnce;
|
||||
static BOOL isFromAppStore = NO;
|
||||
|
||||
dispatch_once(&isFromAppStoreOnce, ^{
|
||||
isFromAppStore = IsFromAppStore();
|
||||
});
|
||||
|
||||
return isFromAppStore;
|
||||
}
|
||||
|
||||
+ (BOOL)isAppStoreReceiptSandbox {
|
||||
// `-[NSBundle appStoreReceiptURL]` makes a synchronous XPC call to the LaunchServices daemon,
|
||||
// which can block the calling thread for seconds when the daemon is busy. The receipt can't
|
||||
// change while the app is running, so compute the result once.
|
||||
// See https://github.com/firebase/firebase-ios-sdk/issues/16726.
|
||||
static dispatch_once_t isAppStoreReceiptSandboxOnce;
|
||||
static BOOL isAppStoreReceiptSandbox = NO;
|
||||
|
||||
dispatch_once(&isAppStoreReceiptSandboxOnce, ^{
|
||||
isAppStoreReceiptSandbox = IsAppStoreReceiptSandbox();
|
||||
});
|
||||
|
||||
return isAppStoreReceiptSandbox;
|
||||
}
|
||||
|
||||
+ (BOOL)isSimulator {
|
||||
#if TARGET_OS_SIMULATOR
|
||||
return YES;
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#endif
|
||||
|
||||
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
|
||||
#import "GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULSwizzler.h"
|
||||
#import "GoogleUtilities/SwizzlerTestHelpers/Public/GoogleUtilities/GULSwizzler+Unswizzle.h"
|
||||
|
||||
@interface GULAppEnvironmentUtil (Tests)
|
||||
+ (void)setProcessInfoForTest:(NSProcessInfo *)processInfo;
|
||||
@@ -159,4 +161,35 @@
|
||||
XCTAssertEqualObjects([GULAppEnvironmentUtil appleDevicePlatform], expectedPlatform);
|
||||
}
|
||||
|
||||
- (void)testAppStoreReceiptURLIsReadOnlyOnce {
|
||||
// Warm up the cached values.
|
||||
BOOL isAppStoreReceiptSandbox = [GULAppEnvironmentUtil isAppStoreReceiptSandbox];
|
||||
BOOL isFromAppStore = [GULAppEnvironmentUtil isFromAppStore];
|
||||
|
||||
__block NSInteger receiptURLReadCount = 0;
|
||||
[GULSwizzler swizzleClass:[NSBundle class]
|
||||
selector:@selector(appStoreReceiptURL)
|
||||
isClassSelector:NO
|
||||
withBlock:^NSURL *(id bundle) {
|
||||
receiptURLReadCount++;
|
||||
return nil;
|
||||
}];
|
||||
|
||||
// Sanity check that reads of the receipt URL are intercepted.
|
||||
XCTAssertNil([NSBundle mainBundle].appStoreReceiptURL);
|
||||
XCTAssertEqual(receiptURLReadCount, 1);
|
||||
receiptURLReadCount = 0;
|
||||
|
||||
// `-[NSBundle appStoreReceiptURL]` makes a synchronous XPC call that can block for seconds, so
|
||||
// later calls must return the cached values without reading it again.
|
||||
XCTAssertEqual([GULAppEnvironmentUtil isAppStoreReceiptSandbox], isAppStoreReceiptSandbox);
|
||||
XCTAssertEqual([GULAppEnvironmentUtil isFromAppStore], isFromAppStore);
|
||||
|
||||
[GULSwizzler unswizzleClass:[NSBundle class]
|
||||
selector:@selector(appStoreReceiptURL)
|
||||
isClassSelector:NO];
|
||||
|
||||
XCTAssertEqual(receiptURLReadCount, 0);
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user