parse-community / Parse-SDK-iOS-OSX

The Apple SDK for Parse Platform (iOS, macOS, watchOS, tvOS)
https://parseplatform.org
Other
2.81k stars 871 forks source link

iOS 13 crashing on _assertValidInstanceClassName when building for iOS 15 for some users #1631

Closed Svantulden closed 2 years ago

Svantulden commented 2 years ago

New Issue Checklist

Issue Description

Our users with iOS 13 report a crash @ app start, that we cannot reproduce in the simulator. The stack trace is clear however:

Crash log ``` Last Exception Backtrace: 0 CoreFoundation 0x1bf342a48 0x1bf217000 + 1227336 1 libobjc.A.dylib 0x1bf069fa4 0x1bf064000 + 24484 2 CoreFoundation 0x1bf2381c0 0x1bf217000 + 135616 3 FMCore 0x104901190 +[PFObject(Private) _assertValidInstanceClassName:] + 6459792 (PFObject.m:773) 4 FMCore 0x104906c18 -[PFObject init] + 6482968 (PFObject.m:1673) 5 FMCore 0x104906e94 -[PFObject initWithObjectState:] + 6483604 (PFObject.m:1698) 6 FMCore 0x104906f8c +[PFObject objectWithClassName:objectId:completeData:] + 6483852 (PFObject.m:1706) 7 FMCore 0x104907394 +[PFObject object] + 6484884 (PFObject.m:1748) 8 FMCore 0x104910870 __56-[PFCurrentInstallationController getCurrentObjectAsync]_block_invoke.37 + 6522992 (PFCurrentInstallationController.m:121) 9 Bolts 0x10508b41c __62-[BFTask continueWithExecutor:successBlock:cancellationToken:]_block_invoke + 46108 (BFTask.m:398) 10 Bolts 0x10508ae68 __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke + 44648 (BFTask.m:331) 11 Bolts 0x105090890 __29+[BFExecutor defaultExecutor]_block_invoke_2 + 67728 (BFExecutor.m:65) 12 Bolts 0x105090da4 -[BFExecutor execute:] + 69028 (BFExecutor.m:131) 13 Bolts 0x10508aac8 -[BFTask runContinuations] + 43720 (BFTask.m:307) 14 Bolts 0x10508a62c -[BFTask trySetResult:] + 42540 (BFTask.m:247) 15 Bolts 0x105087d3c -[BFTaskCompletionSource trySetResult:] + 32060 (BFTaskCompletionSource.m:66) 16 FMCore 0x1048e3e68 __28-[PFAsyncTaskQueue enqueue:]_block_invoke_2 + 6340200 (PFAsyncTaskQueue.m:0) 17 Bolts 0x10508ae68 __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke + 44648 (BFTask.m:331) 18 libdispatch.dylib 0x1bf00e610 0x1befb3000 + 374288 19 libdispatch.dylib 0x1bf00f184 0x1befb3000 + 377220 20 libdispatch.dylib 0x1befb76f8 0x1befb3000 + 18168 21 libdispatch.dylib 0x1befc3fa4 0x1befb3000 + 69540 22 libdispatch.dylib 0x1befc4770 0x1befb3000 + 71536 23 libsystem_pthread.dylib 0x1bf05eb48 0x1bf053000 + 47944 24 libsystem_pthread.dylib 0x1bf061760 0x1bf053000 + 59232 ```

It seems related to this issue: https://github.com/parse-community/Parse-SDK-iOS-OSX/issues/1297 however, we don't use a PFUser subclass with an underscore in our code. Could it be that iOS 15 bridges code to Swift differently? That it adds an underscore somehow, specifically on iOS 13? We are at a loss for what to do about this.

Please tell me what I can do to narrow this down.

Steps to reproduce

It seems to happen at initialization of the Parse SDK (Parse.initialize(with: parseConfig)) or when querying the .current() of PFUser. We cannot see exactly where it happens in our own code, which isn't that helpful.

Actual Outcome

Crash at startup for some iOS 13 users

Expected Outcome

No crash at intialization

Environment

All iOS 13 versions. We have a minimum of iOS 11, where we don't see this issue on 11 and 12 versions. The amount is 169 users in the last 30 days with a total of 1713 crashes. We are only seeing this crash occurring from our app version where we first brought iOS 15 support (so a binary built with iOS 15). Haven't changed any user-management code in that release.

Client

Logs

Cannot access app logs, as the users crashes @ startup

Crash log ``` Last Exception Backtrace: 0 CoreFoundation 0x1bf342a48 0x1bf217000 + 1227336 1 libobjc.A.dylib 0x1bf069fa4 0x1bf064000 + 24484 2 CoreFoundation 0x1bf2381c0 0x1bf217000 + 135616 3 FMCore 0x104901190 +[PFObject(Private) _assertValidInstanceClassName:] + 6459792 (PFObject.m:773) 4 FMCore 0x104906c18 -[PFObject init] + 6482968 (PFObject.m:1673) 5 FMCore 0x104906e94 -[PFObject initWithObjectState:] + 6483604 (PFObject.m:1698) 6 FMCore 0x104906f8c +[PFObject objectWithClassName:objectId:completeData:] + 6483852 (PFObject.m:1706) 7 FMCore 0x104907394 +[PFObject object] + 6484884 (PFObject.m:1748) 8 FMCore 0x104910870 __56-[PFCurrentInstallationController getCurrentObjectAsync]_block_invoke.37 + 6522992 (PFCurrentInstallationController.m:121) 9 Bolts 0x10508b41c __62-[BFTask continueWithExecutor:successBlock:cancellationToken:]_block_invoke + 46108 (BFTask.m:398) 10 Bolts 0x10508ae68 __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke + 44648 (BFTask.m:331) 11 Bolts 0x105090890 __29+[BFExecutor defaultExecutor]_block_invoke_2 + 67728 (BFExecutor.m:65) 12 Bolts 0x105090da4 -[BFExecutor execute:] + 69028 (BFExecutor.m:131) 13 Bolts 0x10508aac8 -[BFTask runContinuations] + 43720 (BFTask.m:307) 14 Bolts 0x10508a62c -[BFTask trySetResult:] + 42540 (BFTask.m:247) 15 Bolts 0x105087d3c -[BFTaskCompletionSource trySetResult:] + 32060 (BFTaskCompletionSource.m:66) 16 FMCore 0x1048e3e68 __28-[PFAsyncTaskQueue enqueue:]_block_invoke_2 + 6340200 (PFAsyncTaskQueue.m:0) 17 Bolts 0x10508ae68 __55-[BFTask continueWithExecutor:block:cancellationToken:]_block_invoke + 44648 (BFTask.m:331) 18 libdispatch.dylib 0x1bf00e610 0x1befb3000 + 374288 19 libdispatch.dylib 0x1bf00f184 0x1befb3000 + 377220 20 libdispatch.dylib 0x1befb76f8 0x1befb3000 + 18168 21 libdispatch.dylib 0x1befc3fa4 0x1befb3000 + 69540 22 libdispatch.dylib 0x1befc4770 0x1befb3000 + 71536 23 libsystem_pthread.dylib 0x1bf05eb48 0x1bf053000 + 47944 24 libsystem_pthread.dylib 0x1bf061760 0x1bf053000 + 59232 ```
Crash logs in Firebase Crashlytics ![image](https://user-images.githubusercontent.com/7554912/142403534-56ff452b-6cca-4d37-a4ba-51a59d59c17d.png)
parse-github-assistant[bot] commented 2 years ago

Thanks for opening this issue!

mman commented 2 years ago

How do you integrate the SDK please? CocoaPods? Carthage? Manually? Do you have any changes in the SDK? How you compile it?

I just tried to debug this code path on iOS 15 and the stack trace looks different. The [PFObject init] actually correctly goes into [PFInstallation(Private) _assertValidInstanceClassName:], and that method does not check (correctly) for underscore.

In your case on iOS 13 the code here: https://github.com/parse-community/Parse-SDK-iOS-OSX/blob/e09104ee362ee1a98cf684336097d8f5701031ab/Parse/Parse/PFObject.m#L1673 actually ends up in generic [PFObject _assertValidInstanceClassName:] and fails because _Installation has an underscore. Looks super weird.

Screenshot 2021-11-18 at 14 42 23
mman commented 2 years ago

Just tried to debug this again under simulator running iOS 13.0 and iOS 13.7 and it behaves correctly. Not sure if you can achieve the same (for example by including Parse iOS SDK as a Xcode subproject) and try to debug/reproduce this on your machine on any iOS 13 device.

The breakpoint line is here: https://github.com/parse-community/Parse-SDK-iOS-OSX/blob/e09104ee362ee1a98cf684336097d8f5701031ab/Parse/Parse/PFObject.m#L1673

Svantulden commented 2 years ago

Thanks for your quick reply!

How do you integrate the SDK please? CocoaPods? Carthage? Manually? Do you have any changes in the SDK? How you compile it?

We are using Carthage with --use-xcframeworks on an XCode 13 command-line tools version. No custom changes in the SDK, just the 1.19.2 version. Previously (before updating to XCode 13 / iOS 15 stack) we also used Carthage, without --use-xcframeworks.

actually ends up in generic [PFObject _assertValidInstanceClassName:] and fails because _Installation has an underscore. Looks super weird.

Nice find. Strange indeed. Do you know any ways for it to end up using the installation as a class name? I'm yet too unfamiliar with the SDK code to see how this could end up there. Especially that it seems to only happen on iOS 13.

Just tried to debug this again under simulator running iOS 13.0 and iOS 13.7 and it behaves correctly. Not sure if you can achieve the same (for example by including Parse iOS SDK as a Xcode subproject) and try to debug/reproduce this on your machine on any iOS 13 device.

Will try to find a fosselized iOS device with iOS 13 or a way to install iOS 13 on a testdevice when they aren't signed anymore (I think jailbreaking is the only way? 👀).

mman commented 2 years ago

You can quickly try to debug it via Xcode Simulator running iOS 13. Download the iOS 13.0 simulator images via Xcode preferences / components window and just run your app against random iOS 13 simulator.

The PFInstallation.currentInstallation will be called at some point in a lifecycle of your app, and this can give you an idea whether you can actually crash the app in DEBUG builds running against iOS Simulator 13.0. Since you are using Carthage, the SDK will actually be built in release mode, the same way as in your shipping app.

This can help you pinpoint the crash, whether it occurs because of your code, or because of Parse SDK code and the way it is built using Carthage.

Svantulden commented 2 years ago

You can quickly try to debug it via Xcode Simulator running iOS 13. Download the iOS 13.0 simulator images via Xcode preferences / components window and just run your app against random iOS 13 simulator.

Ah, I thought you meant a physical device specifically, as we cannot reproduce the crash in an iOS 13 simulator while debugging yet.

The PFInstallation.currentInstallation will be called at some point in a lifecycle of your app, and this can give you an idea whether you can actually crash the app in DEBUG builds running against iOS Simulator 13.0. Since you are using Carthage, the SDK will actually be built in release mode, the same way as in your shipping app.

This can help you pinpoint the crash, whether it occurs because of your code, or because of Parse SDK code and the way it is built using Carthage.

Good idea. I've tried this (checking out the 1.19.2 project and adding it as a sub-project for my app), but I'm seeing the same stack trace as you do when setting a breakpoint on _assertValidInstanceClassName of PFInstallation. The one on PFObject itself doesn't get hit.

image

I've also tried logging in and doing some Parse-related behaviour in my app, like changing the user properties and doing a few commands/calls to the Parse backend, but the only way _assertValidInstanceClassName gets hit is at startup and only for PFInstallation as you are also seeing.

We are using PFUser in our app and [PFUser _assertValidInstanceClassName] does (also) get hit, so maybe that can provide a hint of some sort?

mman commented 2 years ago

I am not that familiar with the internals, but from pure deduction, each implementation of PFSubclassing protocol (or PFObject) subclass calls _assertValidInstanceClassName during its creation.

The default implementation (in PFObject) checks whether the class name does not start with underscore. Not sure why, I assume underscored class names and mongo collections are reserved for internal parse classes (_User, _Installation, etc.). And that is why each Parse internal subclass like PFInstallation, PFUser, etc... override and customize the _assertValidInstanceClassName check.

In your case, instead of calling overriden method for PFInstallation, your code ends up in default implementation of PFObject...

And because it works mostly, but fails randomly on some versions of iOS, it could be pointing towards a bug in the compiler combined with a bug in the runtime. I know it is a huge speculation.

Could you examine how many iOS 13 variants are actually affected? Do you have any iOS 13 variants where the bug does not happen?

Super weird bug, but I think not in parse code per se, more likely in the way it is integrated in your app and how it is built.

Svantulden commented 2 years ago

And because it works mostly, but fails randomly on some versions of iOS, it could be pointing towards a bug in the compiler combined with a bug in the runtime. I know it is a huge speculation.

Agreed that it's a huge speculation, but this issue only occurring on iOS 13 versions seems so strange to me.

Thanks for the extra explanation. I've experimented some with those different breakpoints in the different _assertValidInstanceClassName versions. When a user is logged in within our app, the PFUser variant of _assertValidInstanceClassName gets hit together of the one in PFInstallation. When logged out, only the PFInstallation one gets hit.

Maybe the PFUser somehow gets a different [self class] working in those affected cases, where it hits the PFObject superclass instead of the PFUser variant 🤔. Though that wouldn't make sense if the affected users cannot fix the issue by reinstalling the app, as they would then be logged out.

I've compared our Flurry tracking data for iOS 13 versions & app versions that do and do not have iOS 15 support. I'm seeing a relatively small amount of users still being able to use the app properly. It's hard to compare accurately between crashing & non-crashing users, as Flurry doesn't always initialise on time when the app crashes at startup.

What's also weird is that those affected users cannot fix the issue by reinstalling the app. So it seems to be very specifically reproducible for those users.

We could try to fork the SDK & comment out the assert, just to try to have a workaround. That could of course still cause weird issues if the PFUser or PFInstallation isn't of it's own class.

Svantulden commented 2 years ago

It has been fixed by us simply recompiling the same Parse version with Carthage on the latest Xcode 13.1 production command-line tools 🙌, where we previously compiled it with Xcode 13.0

So it looks like it was a weird compiler bug after all 😅