FileKit 0.14.2 can crash the JVM when opening a picker if the process already contains an Objective-C class named IdeaRunnable
I initially suspected a JBR issue because the failure is reported as a native SIGSEGV, but the call stack consistently ends here:
C [libobjc.A.dylib+0xd348] objc_registerClassPair+0x54
jdk.proxy1.$Proxy0.objc_registerClassPair(...)
io.github.vinceglb.filekit.dialogs.platform.mac.foundation.Foundation.registerObjcClassPair(...)
io.github.vinceglb.filekit.dialogs.platform.mac.foundation.Foundation.initRunnableSupport(...)
io.github.vinceglb.filekit.dialogs.platform.mac.foundation.Foundation.executeOnMainThread(...)
The relevant code is in Foundation.initRunnableSupport:
https://github.com/vinceglb/FileKit/blob/0.14.2/filekit-dialogs/src/jvmMain/kotlin/io/github/vinceglb/filekit/dialogs/platform/mac/foundation/Foundation.kt#L326-L330
objc_allocateClassPair returns nil when the requested class name is already in use. With the current JNA mapping, that comes back as an ID whose value is zero. It is then passed to objc_registerClassPair without being checked
The crash report confirms the null class argument:
siginfo: si_signo: 11 (SIGSEGV), si_addr: 0x0000000000000000
x0=0x0000000000000000
This is reproducible in an otherwise isolated JVM. The only setup required is registering IdeaRunnable before initializing FileKit:
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.NativeLong
import com.sun.jna.Pointer
import io.github.vinceglb.filekit.FileKit
import io.github.vinceglb.filekit.dialogs.openDirectoryPicker
import kotlinx.coroutines.runBlocking
private interface ObjCRuntime : Library {
fun objc_getClass(name: String): Pointer?
fun objc_allocateClassPair(
superclass: Pointer?,
name: String,
extraBytes: NativeLong
): Pointer?
fun objc_registerClassPair(cls: Pointer?)
}
private val objc = Native.load("Foundation", ObjCRuntime::class.java)
private fun registerIdeaRunnable() {
val nsObject = requireNotNull(objc.objc_getClass("NSObject"))
val cls = requireNotNull(
objc.objc_allocateClassPair(
nsObject,
"IdeaRunnable",
NativeLong(0)
)
)
objc.objc_registerClassPair(cls)
}
fun main() = runBlocking {
registerIdeaRunnable()
FileKit.openDirectoryPicker()
}
I ran this reproducer in a separate JBR process and got the same objc_registerClassPair+0x54 crash
The Foundation helper appears to be derived from IntelliJ Platform's implementation. IdeaRunnable was introduced there as an internal adapter class:
JetBrains/intellij-community@37dd1fb
That implementation assumes it owns the process-level class name. The assumption is reasonable inside the IDE, but it does not hold as well for a reusable library, where another dependency may contain the same helper
The straightforward fix would be to give the class a FileKit-specific name and reject a nil allocation before calling objc_registerClassPair. The check should use isNil, rather than only checking Kotlin nullability, because the returned ID may be non-null while holding a zero value
I would avoid reusing a pre-existing class with the same name: its run: implementation and callback state may belong to a different copy of the helper. Replacing the adapter with a GCD dispatch would remove the global class registration entirely, but a namespaced class name plus the defensive check should be sufficient for this failure
Environment:
- FileKit 0.14.2
- JNA 5.19.1
- JBR 25.0.2+10-b315.62
- Kotlin 2.4.10
- macOS 26.5.2
- Apple Silicon
FileKit 0.14.2 can crash the JVM when opening a picker if the process already contains an Objective-C class named
IdeaRunnableI initially suspected a JBR issue because the failure is reported as a native
SIGSEGV, but the call stack consistently ends here:The relevant code is in
Foundation.initRunnableSupport:https://github.com/vinceglb/FileKit/blob/0.14.2/filekit-dialogs/src/jvmMain/kotlin/io/github/vinceglb/filekit/dialogs/platform/mac/foundation/Foundation.kt#L326-L330
objc_allocateClassPairreturns nil when the requested class name is already in use. With the current JNA mapping, that comes back as anIDwhose value is zero. It is then passed toobjc_registerClassPairwithout being checkedThe crash report confirms the null class argument:
This is reproducible in an otherwise isolated JVM. The only setup required is registering
IdeaRunnablebefore initializing FileKit:I ran this reproducer in a separate JBR process and got the same
objc_registerClassPair+0x54crashThe Foundation helper appears to be derived from IntelliJ Platform's implementation.
IdeaRunnablewas introduced there as an internal adapter class:JetBrains/intellij-community@37dd1fb
That implementation assumes it owns the process-level class name. The assumption is reasonable inside the IDE, but it does not hold as well for a reusable library, where another dependency may contain the same helper
The straightforward fix would be to give the class a FileKit-specific name and reject a nil allocation before calling
objc_registerClassPair. The check should useisNil, rather than only checking Kotlin nullability, because the returnedIDmay be non-null while holding a zero valueI would avoid reusing a pre-existing class with the same name: its
run:implementation and callback state may belong to a different copy of the helper. Replacing the adapter with a GCD dispatch would remove the global class registration entirely, but a namespaced class name plus the defensive check should be sufficient for this failureEnvironment: