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

-[FIRInstanceIDTokenInfo isFreshWithIID:]: unrecognized selector sent to instance #5152

Closed
salla-andre opened this issue Mar 19, 2020 · 6 comments
Assignees

Comments

@salla-andre
Copy link

[REQUIRED] Step 1: Describe your environment

  • Xcode version: 10.1
  • Firebase SDK version: 6.20.0
  • Firebase Component: FirebaseInstanceID
  • Component version: 4.3.2 (was 4.2.6)
  • Installation method: CocoaPods

[REQUIRED] Step 2: Describe the problem

Steps to reproduce:

After updating from 6.11 to 6.20, it started to crash and reported the error:

-[FIRInstanceIDTokenInfo isFreshWithIID:]: unrecognized selector sent to instance

I did some search and find out that in version 6.12, the method isFreshWithIID: came as replacement to the previous isFresh.

I put some breakpoint at the crash point and printed the object to check if it responds to the new selector and old selector, then I found this:

(lldb) po [tokenInfo respondsToSelector:@selector(isFresh)]
YES

(lldb) po [tokenInfo respondsToSelector:@selector(isFreshWithIID)]
NO

It seems that the object archived in keychain is an old version of the object, that doesn't have the new method. Since its use the NSKeyedUnarchiver to unarchive the object, it remains the object that was saved before, crashing when trying to access the new method.

Relevant Code:

//FIRInstanceIDTokenStore.m

+ (nullable FIRInstanceIDTokenInfo *)tokenInfoFromKeychainItem:(NSData *)item {
  // Check if it is saved as an archived FIRInstanceIDTokenInfo, otherwise return nil.
  FIRInstanceIDTokenInfo *tokenInfo = nil;
  // NOTE: Passing in nil to unarchiveObjectWithData will result in an iOS error logged
  // in the console on iOS 10 and below. Avoid by checking item.data's existence.
  if (item) {
    // TODO(chliangGoogle: Use the new API and secureCoding protocol.
    @try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
      tokenInfo = [NSKeyedUnarchiver unarchiveObjectWithData:item];
#pragma clang diagnostic pop

    } @catch (NSException *exception) {
      FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenStoreExceptionUnarchivingTokenInfo,
                               @"Unable to parse token info from Keychain item; item was in an "
                               @"invalid format");
      tokenInfo = nil;
    } @finally {
    }
  }
  return tokenInfo;
}
//FIRInstanceIDTokenManager.m

#pragma mark - Invalidating Cached Tokens
- (BOOL)checkTokenRefreshPolicyWithIID:(NSString *)IID {
  // We know at least one cached token exists.
  BOOL shouldFetchDefaultToken = NO;
  NSArray<FIRInstanceIDTokenInfo *> *tokenInfos = [self.instanceIDStore cachedTokenInfos];

  NSMutableArray<FIRInstanceIDTokenInfo *> *tokenInfosToDelete =
      [NSMutableArray arrayWithCapacity:tokenInfos.count];
  for (FIRInstanceIDTokenInfo *tokenInfo in tokenInfos) {
    if ([tokenInfo isFreshWithIID:IID]) { //THIS LINE CAUSES THE CRASH
      // Token is fresh and in right format, do nothing
      continue;
    }
    if ([tokenInfo isDefaultToken]) {
      // Default token is expired, do not mark for deletion. Fetch directly from server to
      // replace the current one.
      shouldFetchDefaultToken = YES;
    } else {
      // Non-default token is expired, mark for deletion.
      [tokenInfosToDelete addObject:tokenInfo];
    }
    FIRInstanceIDLoggerDebug(
        kFIRInstanceIDMessageCodeTokenManagerInvalidateStaleToken,
        @"Invalidating cached token for %@ (%@) due to token is no longer fresh.",
        tokenInfo.authorizedEntity, tokenInfo.scope);
  }
  for (FIRInstanceIDTokenInfo *tokenInfoToDelete in tokenInfosToDelete) {
    [self.instanceIDStore removeCachedTokenWithAuthorizedEntity:tokenInfoToDelete.authorizedEntity
                                                          scope:tokenInfoToDelete.scope];
  }
  return shouldFetchDefaultToken;
}
@google-oss-bot

This comment has been minimized.

@maksymmalyhin
Copy link
Contributor

@AndreasLS The issue looks very similar to #4776 and #4711. Most likely you have two copy of Firebase in you project - one in a module defined in a dynamic framework (one of your app dependencies) and another one in you app target. Also, probably the versions of Firebase are different which triggers the crash.

Could you please follow troubleshooting steps from the issue above to identify what exactly issue you have and let us know?

@salla-andre
Copy link
Author

@maksymmalyhin I checked my dynamic frameworks, there is not any embbeded frameworks.

I just got one Target in my project. I tried to deintegrate pod and install it again. It still not working.

The versions described in Podfile.lock related to Firebase are those:

  • Firebase (6.20.0):
    • Firebase/Core (= 6.20.0)
    • Firebase/Analytics (6.20.0):
      • Firebase/Core
    • Firebase/Core (6.20.0):
      • Firebase/CoreOnly
      • FirebaseAnalytics (= 6.3.1)
    • Firebase/CoreOnly (6.20.0):
      • FirebaseCore (= 6.6.4)
    • Firebase/DynamicLinks (6.20.0):
      • Firebase/CoreOnly
      • FirebaseDynamicLinks (~> 4.0.7)
    • Firebase/InAppMessagingDisplay (6.20.0):
      • Firebase/CoreOnly
      • FirebaseInAppMessagingDisplay (~> 0.17.0)
    • Firebase/Messaging (6.20.0):
      • Firebase/CoreOnly
      • FirebaseMessaging (~> 4.3.0)
    • Firebase/RemoteConfig (6.20.0):
      • Firebase/CoreOnly
      • FirebaseRemoteConfig (~> 4.4.9)
    • FirebaseABTesting (3.2.0):
      • FirebaseAnalyticsInterop (~> 1.3)
      • FirebaseCore (~> 6.1)
      • Protobuf (>= 3.9.2, ~> 3.9)
    • FirebaseAnalytics (6.3.1):
      • FirebaseCore (~> 6.6)
      • FirebaseInstallations (~> 1.1)
      • GoogleAppMeasurement (= 6.3.1)
      • GoogleUtilities/AppDelegateSwizzler (~> 6.0)
      • GoogleUtilities/MethodSwizzler (~> 6.0)
      • GoogleUtilities/Network (~> 6.0)
      • "GoogleUtilities/NSData+zlib (~> 6.0)"
      • nanopb (= 0.3.9011)
    • FirebaseAnalyticsInterop (1.5.0)
    • FirebaseCore (6.6.4):
      • FirebaseCoreDiagnostics (~> 1.2)
      • FirebaseCoreDiagnosticsInterop (~> 1.2)
      • GoogleUtilities/Environment (~> 6.5)
      • GoogleUtilities/Logger (~> 6.5)
    • FirebaseCoreDiagnostics (1.2.2):
      • FirebaseCoreDiagnosticsInterop (~> 1.2)
      • GoogleDataTransportCCTSupport (~> 2.0)
      • GoogleUtilities/Environment (~> 6.5)
      • GoogleUtilities/Logger (~> 6.5)
      • nanopb (~> 0.3.901)
    • FirebaseCoreDiagnosticsInterop (1.2.0)
    • FirebaseDynamicLinks (4.0.7):
      • FirebaseAnalyticsInterop (~> 1.3)
      • FirebaseCore (~> 6.2)
    • FirebaseInAppMessagingDisplay (0.17.0)
    • FirebaseInstallations (1.1.0):
      • FirebaseCore (~> 6.6)
      • GoogleUtilities/UserDefaults (~> 6.5)
      • PromisesObjC (~> 1.2)
    • FirebaseInstanceID (4.3.2):
      • FirebaseCore (~> 6.6)
      • FirebaseInstallations (~> 1.0)
      • GoogleUtilities/Environment (~> 6.5)
      • GoogleUtilities/UserDefaults (~> 6.5)
    • FirebaseMessaging (4.3.0):
      • FirebaseAnalyticsInterop (~> 1.5)
      • FirebaseCore (~> 6.6)
      • FirebaseInstanceID (~> 4.3)
      • GoogleUtilities/AppDelegateSwizzler (~> 6.5)
      • GoogleUtilities/Environment (~> 6.5)
      • GoogleUtilities/Reachability (~> 6.5)
      • GoogleUtilities/UserDefaults (~> 6.5)
      • Protobuf (>= 3.9.2, ~> 3.9)
    • FirebaseRemoteConfig (4.4.9):
      • FirebaseABTesting (~> 3.1)
      • FirebaseAnalyticsInterop (~> 1.4)
      • FirebaseCore (~> 6.2)
      • FirebaseInstanceID (~> 4.2)
      • GoogleUtilities/Environment (~> 6.2)
      • "GoogleUtilities/NSData+zlib (~> 6.2)"
      • Protobuf (>= 3.9.2, ~> 3.9)
    • GoogleAnalytics (3.17.0)
    • GoogleAppMeasurement (6.3.1):
      • GoogleUtilities/AppDelegateSwizzler (~> 6.0)
      • GoogleUtilities/MethodSwizzler (~> 6.0)
      • GoogleUtilities/Network (~> 6.0)
      • "GoogleUtilities/NSData+zlib (~> 6.0)"
      • nanopb (= 0.3.9011)
    • GoogleDataTransport (5.0.0)
    • GoogleDataTransportCCTSupport (2.0.0):
      • GoogleDataTransport (~> 5.0)
      • nanopb (~> 0.3.901)
    • GoogleSymbolUtilities (1.1.2)
    • GoogleTagManager (7.1.3):
      • FirebaseAnalytics (~> 6.3)
      • GoogleAnalytics (~> 3.17)
      • GoogleUtilitiesLegacy (~> 1.3)
    • GoogleUtilities/AppDelegateSwizzler (6.5.2):
      • GoogleUtilities/Environment
      • GoogleUtilities/Logger
      • GoogleUtilities/Network
    • GoogleUtilities/Environment (6.5.2)
    • GoogleUtilities/Logger (6.5.2):
      • GoogleUtilities/Environment
    • GoogleUtilities/MethodSwizzler (6.5.2):
      • GoogleUtilities/Logger
    • GoogleUtilities/Network (6.5.2):
      • GoogleUtilities/Logger
      • "GoogleUtilities/NSData+zlib"
      • GoogleUtilities/Reachability
    • "GoogleUtilities/NSData+zlib (6.5.2)"
    • GoogleUtilities/Reachability (6.5.2):
      • GoogleUtilities/Logger
    • GoogleUtilities/UserDefaults (6.5.2):
      • GoogleUtilities/Logger
    • GoogleUtilitiesLegacy (1.3.2):
      • GoogleSymbolUtilities (~> 1.1)

@salla-andre
Copy link
Author

I managed to solve the problem with the follow code:

// FIRInstanceIDTokenStore.m

+ (nullable FIRInstanceIDTokenInfo *)tokenInfoFromKeychainItem:(NSData *)item {
  // Check if it is saved as an archived FIRInstanceIDTokenInfo, otherwise return nil.
  FIRInstanceIDTokenInfo *tokenInfo = nil;
  // NOTE: Passing in nil to unarchiveObjectWithData will result in an iOS error logged
  // in the console on iOS 10 and below. Avoid by checking item.data's existence.
  if (item) {
    // TODO(chliangGoogle: Use the new API and secureCoding protocol.
    @try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
      tokenInfo = [NSKeyedUnarchiver unarchiveObjectWithData:item];

        //WORKAROUND STARTS HERE
        if ([tokenInfo respondsToSelector:@selector(isFresh)]) {
            FIRInstanceIDTokenInfo *tmp = tokenInfo;
            [self.defaultStore removeTokenWithAuthorizedEntity:tmp.authorizedEntity
                                                         scope:tmp.scope];
            tokenInfo = [[FIRInstanceIDTokenInfo alloc]
                         initWithAuthorizedEntity:tmp.authorizedEntity
                         scope:tmp.scope
                         token:tmp.token
                         appVersion:tmp.appVersion
                         firebaseAppID:tmp.firebaseAppID];
            [self.defaultStore saveTokenInfo:tokenInfo handler:nil];
        }
        //WORKAROUND FINISHES HERE

#pragma clang diagnostic pop

    } @catch (NSException *exception) {
      FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenStoreExceptionUnarchivingTokenInfo,
                               @"Unable to parse token info from Keychain item; item was in an "
                               @"invalid format");
      tokenInfo = nil;
    } @finally {
    }
  }
  return tokenInfo;
}

@maksymmalyhin
Copy link
Contributor

@AndreasLS Thank you for additional details. The posted workaround indicates that there are two different versions of FIRInstanceIDTokenInfo class in Objective C runtime of your application. I assume you should have a warning mentioning classes with the same name defined in different modules in your debug console when you lunch the app.

@salla-andre
Copy link
Author

salla-andre commented Mar 20, 2020

@maksymmalyhin You were right! I double checked the debug log and there they are: the logs saying that I have two different definitions of the same class. I found out that one of our dynamic libs had a reference to Firebase libs and based in your answer there #4315 (comment) about Firebase libs being a static library, I removed any reference from our dynamic lib and it worked, no more crashs!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants