From 74307ad43409d942a2ad310d67da5a0f61707272 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Tue, 3 May 2022 18:43:39 +0200 Subject: [PATCH] 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 c1f2d66ed8ce75842f1994561e682ca74c31e3d3 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. --- .../org/jetbrains/kotlin/config/JpsUtils.kt | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/jps/jps-common/src/org/jetbrains/kotlin/config/JpsUtils.kt b/jps/jps-common/src/org/jetbrains/kotlin/config/JpsUtils.kt index f502fe8333f..8afe4a23157 100644 --- a/jps/jps-common/src/org/jetbrains/kotlin/config/JpsUtils.kt +++ b/jps/jps-common/src/org/jetbrains/kotlin/config/JpsUtils.kt @@ -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 } \ No newline at end of file