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

Crash at app start with Kotlin/native; objc_getClassList causing initialize to fire before load on non-OneSignal #1042

Closed
ubuntudroid opened this issue Jan 26, 2022 · 21 comments

Comments

@ubuntudroid
Copy link

Description:
Our app is based on Kotlin/native and after integration of the SDK as per the documentation we are seeing a crash right at app start likely caused by the swizzling done by the SDK and how that is handled by Kotlin/native.

I've also reported it over in the Kotlin/native issue tracker because it is more likely to be a problem caused by Kotlin/native, but you might be able to help them debug the problem, thus this issue. You can find the Kotlin/native issue report here: https://youtrack.jetbrains.com/issue/KT-50982

Environment

  • SDK version 3.10.0
  • integration via cocoapods

Steps to Reproduce Issue:

Please check my description here: https://youtrack.jetbrains.com/issue/KT-50982

Anything else:

Stacktrace:

__pthread_kill 0x00000001ba63a9e8
pthread_kill 0x00000001dab8d824
abort 0x000000018b0cb0b4
konan::abort() 0x00000001012ce774
kotlin::internal::RuntimeAssertFailedPanic(bool, char const*, char const*, ...) 0x00000001012ce458
__Kotlin_ObjCExport_initialize_block_invoke 0x00000001012d2cb8
_dispatch_client_callout 0x000000018091e198
_dispatch_once_callout 0x00000001808ee7b8
Kotlin_ObjCExport_initialize 0x00000001012d08b8
+[KotlinBase initialize] 0x00000001010fe2fc
CALLING_SOME_+initialize_METHOD 0x0000000197e031e4
initializeNonMetaClass 0x0000000197df91f8
initializeNonMetaClass 0x0000000197df8f84
initializeNonMetaClass 0x0000000197df8f84
initializeAndMaybeRelock(objc_class *, objc_object *, mutex_tt<…> &, bool) 0x0000000197dfd17c
lookUpImpOrForward 0x0000000197df67bc
_objc_msgSend_uncached 0x0000000197df2400
swift_dynamicCastObjCClassMetatype 0x00000001856048a4
swift_dynamicCastMetatypeImpl(const swift::TargetMetadata<…> *, const swift::TargetMetadata<…> *) 0x00000001855c2dec
swift::_checkGenericRequirements(__swift::__runtime::llvm::ArrayRef<…>, __swift::__runtime::llvm::SmallVectorImpl<…> &, std::function<…>, std::function<…>) 0x00000001855f9e38
_gatherGenericParameters(const swift::TargetContextDescriptor<…> *, __swift::__runtime::llvm::ArrayRef<…>, const swift::TargetMetadata<…> *, __swift::__runtime::llvm::SmallVectorImpl<…> &, __swift::__runtime::llvm::SmallVectorImpl<…> &, swift::Demangle::__runtime::Demangler &) 0x00000001855f5cf8
DecodedMetadataBuilder::createBoundGenericType(const swift::TargetContextDescriptor<…> *, __swift::__runtime::llvm::ArrayRef<…>, const swift::TargetMetadata<…> *) const 0x00000001855f4a88
swift::Demangle::__runtime::TypeDecoder::decodeMangledType(swift::Demangle::__runtime::Node *) 0x00000001855f0f88
swift_getTypeByMangledNodeImpl(swift::MetadataRequest, swift::Demangle::__runtime::Demangler &, swift::Demangle::__runtime::Node *, const void *const *, std::function<…>, std::function<…>) 0x00000001855ee934
swift::swift_getTypeByMangledNode(swift::MetadataRequest, swift::Demangle::__runtime::Demangler &, swift::Demangle::__runtime::Node *, const void *const *, std::function<…>, std::function<…>) 0x00000001855ee6b4
swift_getTypeByMangledNameImpl(swift::MetadataRequest, __swift::__runtime::llvm::StringRef, const void *const *, std::function<…>, std::function<…>) 0x00000001855eee28
swift::swift_getTypeByMangledName(swift::MetadataRequest, __swift::__runtime::llvm::StringRef, const void *const *, std::function<…>, std::function<…>) 0x00000001855ec5d0
getSuperclassMetadata 0x00000001855d6a8c
_swift_initClassMetadataImpl(swift::TargetClassMetadata<…> *, swift::ClassLayoutFlags, unsigned long, const swift::TypeLayout *const *, unsigned long *, bool) 0x00000001855d6c64
type metadata completion function for MyApp.WeightHistoryValueFormatter 0x0000000100b1edf0
swift::MetadataCacheEntryBase::doInitialization(swift::ConcurrencyControl &, swift::MetadataCompletionQueueEntry *, swift::MetadataRequest) 0x00000001855e308c
swift_getSingletonMetadata 0x00000001855d3e30
type metadata accessor for MyApp.WeightHistoryValueFormatter 0x0000000100b1d6dc
ObjC metadata update function for MyApp.WeightHistoryValueFormatter 0x0000000100b1ee34
realizeAllClasses() 0x0000000197e119c4
objc_getClassList 0x0000000197e12a70
ClassGetSubclasses 0x0000000104ef407c
+[OneSignalUNUserNotificationCenter swizzleSelectorsOnDelegate:] 0x0000000104f00288
-[OneSignalUNUserNotificationCenter setOneSignalUNDelegate:] 0x0000000104f0021c
+[OneSignalUNUserNotificationCenter registerDelegate] 0x0000000104effe60
+[UIApplication(OneSignal) load] 0x0000000104edf8d4
load_images 0x0000000197dff14c
dyld4::RuntimeState::notifyObjCInit(const dyld4::Loader *) 0x000000010295d9dc
dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState &, dyld3::Array<…> &) const 0x0000000102961a54
dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState &, dyld3::Array<…> &) const 0x0000000102961a3c
dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState &) const 0x00000001029675c4
dyld4::APIs::runAllInitializersForMain() 0x000000010298402c
dyld4::prepare(dyld4::APIs &, const dyld3::MachOAnalyzer *) 0x00000001029718bc
start 0x0000000102970170
@ubuntudroid
Copy link
Author

For debugging purposes (and as a possible workaround): is it possible to disable swizzling and forward the calls manually to OneSignal?

As a side effect that would allow to circumvent incompatibilities with other swizzling SDKs as well.

@jkasten2
Copy link
Member

@ubuntudroid Thanks for reporting and providing a full stack trace.

Where the crash is happening

From the stack trace you provided it is crashing on this line of code in the OneSignal code base:

objc_getClassList(classes, numClasses);

This code is used to detect what classes are available at runtime, before any swizzling is done.

Why the crash is happening

This class inspection is done very early on when the main app process starts. It seems may trigger a different initialization follow Kotlin is not expecting. This is only based on my understanding of Objective-C class loading and some of the method names I see towards the top stack trace such as __Kotlin_ObjCExport_initialize_block_invoke and +[KotlinBase initialize]. I don't know anything about the interworking of Kotlin here.

The implementation used by OneSignal to list all loaded classes follows Apple's example so it should be the correct way to collect this:
https://developer.apple.com/documentation/objectivec/1418579-objc_getclasslist?language=objc

Disable Swizzling

You can follow the details in PR #297 to test disabling OneSignal's Swizzling when running your app from Xcode to confirm the issue. However this isn't a replacement API to hook up the required events required for a number of the functionally to work, this will just help confirm the issue.

Next steps - Example project

I see someone at Jetbrains tried to reproduce the issue without any luck. There was a follow up reply that the "Notification Service Extension" might be at play. I don't believe it would be as it will run the NSE on its own process and only fire when a pus notification is received. However if you can reproduce try removing the NSE to confirm this.

Possible steps to reproduce

I see you provided the following details the JetBrains ticket, we can attempt to reproduce as well, however a test project would help us get up to speed:

Steps to reproduce

  1. Create a fresh Kotlin/native project
  2. Follow the steps outlined in the OneSignal SDK iOS integration guide. No need to create an account or push certificates or app IDs. It is not even necessary to write any of the integration code. Just include the SDK into the project and create the OneSignalNotificationServiceExtension.
  3. Build and run the app

@ubuntudroid
Copy link
Author

Thanks for the elaborate response @jkasten2 !

I'll try disabling swizzling and report back. Then I'll attempt to create a minimal test project if the guys at Jetbrains still can't reproduce.

Shall we unify and continue this discussion over at the Jetbrains issue tracker? Might make things easier. When everything is said and done there we can post a summary over here.

@artdfel
Copy link

artdfel commented Jan 28, 2022

Hello guys!
My name is Artyom, and I'm the guy trying to reproduce this error from Kotlin side. Right now, I'm trying to make a project with no additional paid Apple Developer accounts. In fact, there is no need to make the result app work. My goal is to get the smallest possible example to fail as described.
Could someone confirm that there is no option to run this problem-causing swizzling without the Push Notifications capability?

@ubuntudroid
Copy link
Author

@artdfel Unfortunately I don't really know which part actually integrates the swizzling code. This is likely something @jkasten2 can easily answer.

I'm stuck in a workshop right now, but will try find some time afterwards (in a few hours) for the test with disabling swizzling and replying to you over in the Kotlin issue tracker. Once more I would ask to move the discussion there (or stay here and pause it there, whatever you prefer) to streamline things.

@ubuntudroid
Copy link
Author

@jkasten2 just in case you are not following along over at the Kotlin issue tracker: I managed to create a sample project and I have invited you to that project and I've described my findings over there: https://youtrack.jetbrains.com/issue/KT-50982#focus=Comments-27-5727582.0-0

It is a Kotlin/native project, so to run it you need to set up a Kotlin/native environment on your machine. But from what I see I think it is a Kotlin/native issue, not a OneSignal one. The issue seems to be just triggered by the way OneSignal does swizzling (as opposed to e.g. Firebase Messaging where swizzling didn't cause such an issue for some reason). But I'm not proficient enough with iOS and swizzling in particular to know the difference.

@SvyatoslavScherbina
Copy link

Hi!
I've posted an explanation and a workaround here: https://youtrack.jetbrains.com/issue/KT-50982#focus=Comments-27-5733384.0-0.

This class inspection is done very early on when the main app process starts. It seems may trigger a different initialization follow Kotlin is not expecting.

By calling objc_getClassList in its load method, OneSignal seems to cause other classes to receive initialize call before load call. I couldn't find any guarantee or recommendation from Apple regarding the order of these two, but I tend to think that other developers might expect load to be called earlier. For example:

And by the time you receive initialize, every class in your process should have already received load (if appropriate).

(from https://stackoverflow.com/a/13326633/9444506).

So this approach might affect not only Kotlin.

In any case, we plan to workaround this on Kotlin side, by making the latter less sensitive to the load vs initialize order.

@jkasten2
Copy link
Member

jkasten2 commented Feb 6, 2022

@SvyatoslavScherbina Thanks for the research and providing those details! We will look into what we can also do on our end to prevent firing initialize before load on non-OneSignal classes, as you noted even if it is fixed in Kotlin this could cause other compatibility issues.

@jkasten2 jkasten2 changed the title Crash at app start with Kotlin/native Crash at app start with Kotlin/native; objc_getClassList causing initialize to fire before load on non-OneSignal Feb 6, 2022
@ubuntudroid
Copy link
Author

As suggested by @SvyatoslavScherbina over at the Kotlin issue tracker I've integrated OneSignal into our main project using SwiftPM and indeed it now works like a charm!

I'm going to keep this open as integrating with CocoaPods would still be preferred by me and we can track the initialize/load changes here. Let me know when you have changes I should test @jkasten2 .

@ubuntudroid
Copy link
Author

@jkasten2 do you have a rough timeline on when you can check out possible changes to the initialization/loading handling? I'm asking because while the SwiftPM solution works for building it breaks our export (likely a Kotlin/native issue), so it would be great if we could just integrate via Cocoapods.

Given that the timeframe for the beta of the Kotlin version fixing this issue is May a fix on your end could land much quicker - that is, if you have the capacities of course. Otherwise I have to postpone migration to OneSignal until summer which would be a shame as everyone at our company was really looking forward to making heavy use of it during spring season.

@ubuntudroid
Copy link
Author

@jkasten2 Alternatively, can I just disable swizzling and implement the necessary function calls on my own?

@jkasten2
Copy link
Member

jkasten2 commented Feb 16, 2022

@ubuntudroid We don't have an option for this currently. The OneSignal change itself to move the swizzling logic from init to initialize is small, however we are not sure if it will be ok in every project setup and binding / wrapper SDK we support. This will take some time to test and verify all the scenarios.

However if you would like we can attempt to make this change and do some light testing and provide it as a beta for you test further for your specific project setup. If this sounds good to you let us know if you will are integrating OneSignal with Cocoapods, SwiftPM, or something else.

@spyrospassas
Copy link

spyrospassas commented Feb 16, 2022

@jkasten2 Following up on this, we seem to be getting the exact same crash on native, swift project on iOS.

Environment

  • SDK version 3.10.0
  • Integration via cocoapods

Stacktrace

Thread 0 Crashed:
0   dyld                        	  	0x0000000104d324dc strcmp + 12
1   dyld                         	 	0x0000000104d31b30 objc::StringHashTable::tryGetIndex(char const*) const + 144 (OptimizerObjC.h:114)
2   dyld                        	  	0x0000000104d31a28 dyld4::APIs::_dyld_get_objc_selector(char const*) + 136 (OptimizerObjC.h:228)
3   libobjc.A.dylib               	0x00000001996e4d60 __sel_registerName(char const*, bool, bool) + 48 (objc-sel.mm:97)
4   libobjc.A.dylib               	0x00000001996e1020 fixupMethodList(method_list_t*, bool, bool) + 260 (objc-sel.mm:131)
5   libobjc.A.dylib               	0x00000001996e3f3c prepareMethodLists(objc_class*, method_list_t**, int, bool, bool, char const*) + 188 (objc-runtime-new.mm:1343)
6   libobjc.A.dylib               	0x00000001996ec020 realizeClassWithoutSwift(objc_class*, objc_class*) + 1648 (objc-runtime-new.mm:1506)
7   libobjc.A.dylib               	0x00000001996e4804 realizeClassMaybeSwiftMaybeRelock(objc_class*, mutex_tt<false>&, bool) + 240 (objc-runtime-new.mm:2851)
8   libobjc.A.dylib               	0x00000001996ffd60 realizeAllClasses() + 172 (objc-runtime-new.mm:2874)
9   libobjc.A.dylib               	0x0000000199701000 objc_getClassList + 84 (objc-runtime-new.mm:5079)
10  App                      	  	0x0000000104776b80 ClassGetSubclasses + 36
11  App                        		0x0000000104783b94 +[OneSignalUNUserNotificationCenter swizzleSelectorsOnDelegate:] + 68
12  App                        		0x0000000104783b28 -[OneSignalUNUserNotificationCenter setOneSignalUNDelegate:] + 132
13  App                        		0x000000010478373c +[OneSignalUNUserNotificationCenter registerDelegate] + 156
14  App                        		0x000000010476101c +[UIApplication(OneSignal) load] + 520
15  libobjc.A.dylib               	0x00000001996eb638 load_images + 1356 (objc-loadmethod.mm:251)
16  dyld                          	0x0000000104d35ea4 dyld4::RuntimeState::notifyObjCInit(dyld4::Loader const*) + 164 (DyldRuntimeState.cpp:1573)
17  dyld                        	  	0x0000000104d3a338 dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&) const + 196 (Loader.cpp:1302)
18  dyld                          	0x0000000104d404b0 dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const + 124 (Loader.cpp:1315)
19  dyld                          	0x0000000104d60124 dyld4::APIs::runAllInitializersForMain() + 320 (DyldAPIs.cpp:3391)
20  dyld                          	0x0000000104d4b508 dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3024 (dyldMain.cpp:738)
21  dyld                       	   	0x0000000104d49a84 start + 488 (dyldMain.cpp:864)

Given that the crash affects a significant number of users, any further input on this would be greatly appreciated

@ubuntudroid
Copy link
Author

@jkasten2 That sounds great! My goal is to integrate via CocoaPods, the SwiftPM solution was just an attempt to work around the issue.

@spyrospassas Looking at your stacktrace I'm not sure this is exactly the same crash, but then again, I might not have enough iOS experience to confidently say this. However, it also does seem to happen during swizzling... 🤔

@OneSignal OneSignal deleted a comment Feb 18, 2022
@ubuntudroid
Copy link
Author

However if you would like we can attempt to make this change and do some light testing and provide it as a beta for you test further for your specific project setup.

@jkasten2 do you have a rough ETA for such a beta? We are eager to adopt OneSignal, but unfortunately are blocked by this issue.

@ubuntudroid
Copy link
Author

Quick update from the Kotlin/native front: we managed to fix the SwiftPM issue and were able to export the archive properly.

I'd still love to use OneSignal via CocoaPods with a statically linked shared library instead of sourcing OneSignal via SwiftPM and a dynamically linked shared library if that becomes possible at any point. But for now, I'm a happy dev. 😁

I'll keep this issue open for now @jkasten2 - feel free to close it if you decide to track the loading changes on another issue.

@ubuntudroid
Copy link
Author

Let me add, that to get reliable builds with OneSignal via SwiftPM I needed to also add OneSignal as a dependency to the Build Phases of OneSignalNotificationService extension. Otherwise builds would fail randomly on CI (and sometimes also in IDE) with No such module 'OneSignal' - likely caused by parallelization of the build process.

CleanShot 2022-02-22 at 12 23 16

@ubuntudroid
Copy link
Author

Update: It turns out builds still weren't reliable, even with the above mentioned workaround implemented.

What seems to work now: setting Build Order to manual via scheme settings. It slows down builds considerably, but builds are reliable now... 🤔

@spyrospassas
Copy link

@jkasten2 I'm reiterating on a very similar issue that we're getting with the native implementation of the SDK and seems related to swizzling:

Environment

  • iOS SDK version 3.10.1
  • Integration via cocoapods [1.11.2]

Steps to reproduce
Unspecified - app seems to crash on load.
Encountered on iOS 15+

Stacktrace

Thread 0 Crashed:
0   dyld                        	  	0x0000000104d324dc strcmp + 12
1   dyld                         	 	0x0000000104d31b30 objc::StringHashTable::tryGetIndex(char const*) const + 144 (OptimizerObjC.h:114)
2   dyld                        	  	0x0000000104d31a28 dyld4::APIs::_dyld_get_objc_selector(char const*) + 136 (OptimizerObjC.h:228)
3   libobjc.A.dylib               	0x00000001996e4d60 __sel_registerName(char const*, bool, bool) + 48 (objc-sel.mm:97)
4   libobjc.A.dylib               	0x00000001996e1020 fixupMethodList(method_list_t*, bool, bool) + 260 (objc-sel.mm:131)
5   libobjc.A.dylib               	0x00000001996e3f3c prepareMethodLists(objc_class*, method_list_t**, int, bool, bool, char const*) + 188 (objc-runtime-new.mm:1343)
6   libobjc.A.dylib               	0x00000001996ec020 realizeClassWithoutSwift(objc_class*, objc_class*) + 1648 (objc-runtime-new.mm:1506)
7   libobjc.A.dylib               	0x00000001996e4804 realizeClassMaybeSwiftMaybeRelock(objc_class*, mutex_tt<false>&, bool) + 240 (objc-runtime-new.mm:2851)
8   libobjc.A.dylib               	0x00000001996ffd60 realizeAllClasses() + 172 (objc-runtime-new.mm:2874)
9   libobjc.A.dylib               	0x0000000199701000 objc_getClassList + 84 (objc-runtime-new.mm:5079)
10  App                      	  	0x0000000104776b80 ClassGetSubclasses + 36
11  App                        		0x0000000104783b94 +[OneSignalUNUserNotificationCenter swizzleSelectorsOnDelegate:] + 68
12  App                        		0x0000000104783b28 -[OneSignalUNUserNotificationCenter setOneSignalUNDelegate:] + 132
13  App                        		0x000000010478373c +[OneSignalUNUserNotificationCenter registerDelegate] + 156
14  App                        		0x000000010476101c +[UIApplication(OneSignal) load] + 520
15  libobjc.A.dylib               	0x00000001996eb638 load_images + 1356 (objc-loadmethod.mm:251)
16  dyld                          	0x0000000104d35ea4 dyld4::RuntimeState::notifyObjCInit(dyld4::Loader const*) + 164 (DyldRuntimeState.cpp:1573)
17  dyld                        	  	0x0000000104d3a338 dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&) const + 196 (Loader.cpp:1302)
18  dyld                          	0x0000000104d404b0 dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const + 124 (Loader.cpp:1315)
19  dyld                          	0x0000000104d60124 dyld4::APIs::runAllInitializersForMain() + 320 (DyldAPIs.cpp:3391)
20  dyld                          	0x0000000104d4b508 dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3024 (dyldMain.cpp:738)
21  dyld                       	   	0x0000000104d49a84 start + 488 (dyldMain.cpp:864)

The crash appears to be affecting a significant number of users (More than 2.000 devices have been affected in the last two weeks according to our data)

@jkasten2
Copy link
Member

jkasten2 commented Jun 2, 2022

We are looking at removing the call to objc_getClassList completely to solve this compatibility issue. Something we are looking to get into the next SDK release.

@jkasten2
Copy link
Member

This fix is now available in the OneSignal 3.11.2 release.

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

No branches or pull requests

5 participants