JpsUtils.isJps: fix NoClassDefFoundError

This commit fixes:

    Caused by: java.lang.NoClassDefFoundError: com/intellij/openapi/components/ComponentManager
      at java.base/java.lang.ClassLoader.defineClass1(Native Method)
      at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
      at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
      at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:555)
      at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:458)
      at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:452)
      at java.base/java.security.AccessController.doPrivileged(Native Method)
      at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:451)
      at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
      at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
      at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
      at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3166)
      at java.base/java.lang.Class.getMethodsRecursive(Class.java:3307)
      at java.base/java.lang.Class.getMethod0(Class.java:3293)
      at java.base/java.lang.Class.getMethod(Class.java:2106)
      at org.jetbrains.kotlin.config.JpsUtilsKt$isJps$2.invoke(JpsUtils.kt:17)

In c1f2d66ed8 I replaced
`ApplicationManager.getApplication() == null` with
`Class.forName("ApplicationManager").getMethod("getApplication").invoke(null)`.
It turned out that those two are not equivalent.

`getMethod` tries to match queried signatures with existing signatures
=> it tries to load classes in signatures => it tries to load
`com.intellij.openapi.application.Application` (and fails because
`Application` supertype isn't in the classpath).

Contrary, `ApplicationManager.getApplication() == null` doesn't try to
load `com.intellij.openapi.application.Application` class, so it doesn't
fail.

I use MethodHandles API because it forces me to write `Class.forName`
myself (compared to implicit `Class.forName("Application")` inside
`getMethod`) which I find a nice safety feature.
This commit is contained in:
Nikita Bobko
2022-05-03 18:43:39 +02:00
committed by Space
parent 239bcea3b9
commit 74307ad434
@@ -5,22 +5,34 @@
package org.jetbrains.kotlin.config
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
const val APPLICATION_MANAGER_CLASS_NAME = "com.intellij.openapi.application.ApplicationManager"
val isJps: Boolean by lazy {
val application = try {
Class.forName("com.intellij.openapi.application.Application")
} catch (ex: LinkageError) {
// If any Application super class is not in the classpath
return@lazy true
} catch (ex: ClassNotFoundException) {
return@lazy true
}
val applicationManager = try {
Class.forName(APPLICATION_MANAGER_CLASS_NAME)
} catch (ex: LinkageError) {
// If any ApplicationManager super class is not in the classpath
return@lazy true
} catch (ex: ClassNotFoundException) {
return@lazy true
}
/*
Normally, JPS shouldn't have an ApplicationManager class in the classpath,
but that's not true for JPS inside IDEA right now.
Though Application is not properly initialized inside JPS so we can use it as a check.
*/
return@lazy if (doesClassExist(APPLICATION_MANAGER_CLASS_NAME)) {
Class.forName(APPLICATION_MANAGER_CLASS_NAME).getMethod("getApplication").invoke(null) == null
} else {
true
}
}
private fun doesClassExist(fqName: String): Boolean {
val classPath = fqName.replace('.', '/') + ".class"
return {}.javaClass.classLoader.getResource(classPath) != null
return@lazy MethodHandles.lookup()
.findStatic(applicationManager, "getApplication", MethodType.methodType(application))
.invoke() == null
}