diff --git a/analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/project/structure/StandaloneProjectFactory.kt b/analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/project/structure/StandaloneProjectFactory.kt index 19dcd0d8150..46c9c667f04 100644 --- a/analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/project/structure/StandaloneProjectFactory.kt +++ b/analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/project/structure/StandaloneProjectFactory.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndexImpl import org.jetbrains.kotlin.cli.jvm.index.SingleJavaFileRootsIndex import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver +import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem import org.jetbrains.kotlin.cli.jvm.modules.JavaModuleGraph import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.load.kotlin.MetadataFinderFactory @@ -155,16 +156,7 @@ object StandaloneProjectFactory { environment: KotlinCoreProjectEnvironment ): List = withAllTransitiveDependencies(modules) .filterIsInstance() - .flatMap { it.getBinaryRoots() } - .mapNotNull { path -> - val pathString = path.toAbsolutePath().toString() - val jar = - if (pathString.endsWith(JAR_PROTOCOL)) - environment.environment.jarFileSystem.findFileByPath(pathString + JAR_SEPARATOR) - else null - if (jar == null) return@mapNotNull null - JavaRoot(jar, JavaRoot.RootType.BINARY) - } + .flatMap { it.getJavaRoots(environment) } private fun withAllTransitiveDependencies(ktModules: List): List { val visited = hashSetOf() @@ -194,6 +186,45 @@ object StandaloneProjectFactory { } } + private fun KtBinaryModule.getJavaRoots( + environment: KotlinCoreProjectEnvironment, + ): List { + return buildList { + getBinaryRoots().forEach { path -> + val pathString = path.toAbsolutePath().toString() + when { + pathString.endsWith(JAR_PROTOCOL) -> { + environment.environment.jarFileSystem.findFileByPath(pathString + JAR_SEPARATOR) + } + pathString.contains(JAR_SEPARATOR) -> { + environment.environment.jrtFileSystem?.findFileByPath(adjustModulePath(pathString)) + } + else -> { + null + } + }?.let { root -> + add(JavaRoot(root, JavaRoot.RootType.BINARY)) + } + } + } + } + + private fun adjustModulePath(pathString: String): String { + return if (pathString.contains(JAR_SEPARATOR)) { + // URLs loaded from JDK point to module names in a JRT protocol format, + // e.g., "jrt:///path/to/jdk/home!/java.base" (JRT protocol prefix + JDK home path + JAR separator + module name) + // After protocol erasure, we will see "/path/to/jdk/home!/java.base" as a binary root. + // CoreJrtFileSystem.CoreJrtHandler#findFile, which uses Path#resolve, finds a virtual file path to the file itself, + // e.g., "/path/to/jdk/home!/modules/java.base". (JDK home path + JAR separator + actual file path) + // To work with that JRT handler, a hacky workaround here is to add "modules" before the module name so that it can + // find the actual file path. + // See [LLFirJavaFacadeForBinaries#getBinaryPath] for a similar hack. + val (libHomePath, pathInImage) = CoreJrtFileSystem.splitPath(pathString) + libHomePath + JAR_SEPARATOR + "modules/$pathInImage" + } else + pathString + } + fun createPackagePartsProvider( project: MockProject, libraryRoots: List, diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirJavaFacadeForBinaries.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirJavaFacadeForBinaries.kt index 8d7045cf97f..4e59224c379 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirJavaFacadeForBinaries.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirJavaFacadeForBinaries.kt @@ -48,6 +48,7 @@ internal class LLFirJavaFacadeForBinaries( // To work with LibraryPathFilter, a hacky workaround here is to remove "modules/" from actual file path. // e.g. "/path/to/jdk/home!/java.base/java/lang/Object.class", which, from Path viewpoint, belongs to "/java.base", // after splitting at the JAR separator, in a similar way. + // See [StandaloneProjectFactory#getAllBinaryRoots] for a similar hack. Paths.get(path.replace("modules/", "")) } else ->