[CLI] Extract classes of compiler configuration from :compiler:cli to the separate module

Those classes mainly include KotlinCoreEnvironment and its dependencies

This change is needed for two reasons:
1. Splitting of some common configuration of compiler from logic of CLI
    makes code structure more clean
2. There is a need to add dependency on `:analysis:analysis-api-standalone`
    to `:compiler:cli`, because FIR analogue of AnalysysHandlerExtension uses
    services from it. But the problems is that standalone AA itself depends
    on classes for compiler configuration, which leads to circular
    dependency between those modules. Extracting configuration to
    `:compiler:cli-base` solves the problem
This commit is contained in:
Dmitriy Novozhilov
2023-03-13 10:29:45 +02:00
committed by Space Team
parent 507ef3e951
commit b6a41c6d93
65 changed files with 70 additions and 22 deletions
+53
View File
@@ -0,0 +1,53 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
api(project(":compiler:cli-common"))
api(project(":compiler:resolution.common"))
api(project(":compiler:frontend.java"))
api(project(":compiler:frontend:cfg"))
api(project(":compiler:ir.backend.common"))
api(project(":compiler:backend.jvm"))
api(project(":compiler:light-classes"))
api(project(":compiler:javac-wrapper"))
api(project(":compiler:ir.serialization.jvm"))
api(project(":js:js.translator"))
api(project(":native:frontend.native"))
api(project(":wasm:wasm.frontend"))
api(project(":kotlin-util-klib"))
api(project(":kotlin-util-klib-metadata"))
compileOnly(toolsJarApi())
compileOnly(intellijCore())
compileOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
compileOnly(commonDependency("org.jetbrains.intellij.deps:asm-all"))
}
sourceSets {
"main" {
projectDefault()
}
"test" { none() }
}
allprojects {
optInToExperimentalCompilerApi()
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
kotlinOptions {
languageVersion = "1.4"
apiVersion = "1.4"
freeCompilerArgs = freeCompilerArgs - "-progressive" + listOf(
"-Xskip-prerelease-check", "-Xsuppress-version-warnings", "-Xuse-mixed-named-arguments"
)
}
}
testsJar {}
projectTest {
workingDir = rootDir
}
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.common;
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig;
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfigurationService;
import org.jetbrains.kotlin.cli.common.config.ContentRoot;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.config.CompilerConfigurationKey;
import java.io.File;
import java.util.List;
public class CLIConfigurationKeys {
// Roots, including dependencies and own sources
public static final CompilerConfigurationKey<List<ContentRoot>> CONTENT_ROOTS =
CompilerConfigurationKey.create("content roots");
public static final CompilerConfigurationKey<MessageCollector> MESSAGE_COLLECTOR_KEY =
CompilerConfigurationKey.create("message collector");
// Used by compiler plugins to access delegated message collector in GroupingMessageCollector
public static final CompilerConfigurationKey<MessageCollector> ORIGINAL_MESSAGE_COLLECTOR_KEY =
CompilerConfigurationKey.create("original message collector");
public static final CompilerConfigurationKey<Boolean> RENDER_DIAGNOSTIC_INTERNAL_NAME =
CompilerConfigurationKey.create("render diagnostic internal name");
public static final CompilerConfigurationKey<Boolean> ALLOW_KOTLIN_PACKAGE =
CompilerConfigurationKey.create("allow kotlin package");
public static final CompilerConfigurationKey<CommonCompilerPerformanceManager> PERF_MANAGER =
CompilerConfigurationKey.create("performance manager");
// Used in Eclipse plugin (see KotlinCLICompiler)
public static final CompilerConfigurationKey<String> INTELLIJ_PLUGIN_ROOT =
CompilerConfigurationKey.create("intellij plugin root");
// See K2MetadataCompilerArguments
public static final CompilerConfigurationKey<File> METADATA_DESTINATION_DIRECTORY =
CompilerConfigurationKey.create("metadata destination directory");
public static final CompilerConfigurationKey<PhaseConfig> PHASE_CONFIG =
CompilerConfigurationKey.create("phase configuration");
public static final CompilerConfigurationKey<PhaseConfigurationService> FLEXIBLE_PHASE_CONFIG =
CompilerConfigurationKey.create("flexible phase configuration");
public static final CompilerConfigurationKey<Integer> REPEAT_COMPILE_MODULES =
CompilerConfigurationKey.create("debug key for profiling, repeats compileModules");
// used in FIR IDE uast tests
public static final CompilerConfigurationKey<File> PATH_TO_KOTLIN_COMPILER_JAR =
CompilerConfigurationKey.create("jar of Kotlin compiler in Kotlin plugin");
private CLIConfigurationKeys() {
}
}
@@ -0,0 +1,177 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.common
import org.jetbrains.kotlin.util.PerformanceCounter
import java.io.File
import java.lang.management.GarbageCollectorMXBean
import java.lang.management.ManagementFactory
import java.util.concurrent.TimeUnit
abstract class CommonCompilerPerformanceManager(private val presentableName: String) {
@Suppress("MemberVisibilityCanBePrivate")
protected val measurements: MutableList<PerformanceMeasurement> = mutableListOf()
protected var isEnabled: Boolean = false
private var initStartNanos = PerformanceCounter.currentTime()
private var analysisStart: Long = 0
private var generationStart: Long = 0
private var startGCData = mutableMapOf<String, GCData>()
private var irTranslationStart: Long = 0
private var irLoweringStart: Long = 0
private var irGenerationStart: Long = 0
private var targetDescription: String? = null
protected var files: Int? = null
protected var lines: Int? = null
fun getTargetInfo(): String =
"$targetDescription, $files files ($lines lines)"
fun getMeasurementResults(): List<PerformanceMeasurement> = measurements
fun enableCollectingPerformanceStatistics() {
isEnabled = true
PerformanceCounter.setTimeCounterEnabled(true)
ManagementFactory.getGarbageCollectorMXBeans().associateTo(startGCData) { it.name to GCData(it) }
}
private fun deltaTime(start: Long): Long = PerformanceCounter.currentTime() - start
open fun notifyCompilerInitialized(files: Int, lines: Int, targetDescription: String) {
if (!isEnabled) return
recordInitializationTime()
this.files = files
this.lines = lines
this.targetDescription = targetDescription
}
open fun notifyCompilationFinished() {
if (!isEnabled) return
recordGcTime()
recordJitCompilationTime()
recordPerfCountersMeasurements()
}
open fun addSourcesStats(files: Int, lines: Int) {
if (!isEnabled) return
this.files = this.files?.plus(files) ?: files
this.lines = this.lines?.plus(lines) ?: lines
}
open fun notifyAnalysisStarted() {
analysisStart = PerformanceCounter.currentTime()
}
open fun notifyAnalysisFinished() {
val time = PerformanceCounter.currentTime() - analysisStart
measurements += CodeAnalysisMeasurement(lines, TimeUnit.NANOSECONDS.toMillis(time))
}
open fun notifyGenerationStarted() {
generationStart = PerformanceCounter.currentTime()
}
open fun notifyGenerationFinished() {
val time = PerformanceCounter.currentTime() - generationStart
measurements += CodeGenerationMeasurement(lines, TimeUnit.NANOSECONDS.toMillis(time))
}
open fun notifyIRTranslationStarted() {
irTranslationStart = PerformanceCounter.currentTime()
}
open fun notifyIRTranslationFinished() {
val time = deltaTime(irTranslationStart)
measurements += IRMeasurement(
lines,
TimeUnit.NANOSECONDS.toMillis(time),
IRMeasurement.Kind.TRANSLATION
)
}
open fun notifyIRLoweringStarted() {
irLoweringStart = PerformanceCounter.currentTime()
}
open fun notifyIRLoweringFinished() {
val time = deltaTime(irLoweringStart)
measurements += IRMeasurement(
lines,
TimeUnit.NANOSECONDS.toMillis(time),
IRMeasurement.Kind.LOWERING
)
}
open fun notifyIRGenerationStarted() {
irGenerationStart = PerformanceCounter.currentTime()
}
open fun notifyIRGenerationFinished() {
val time = deltaTime(irGenerationStart)
measurements += IRMeasurement(
lines,
TimeUnit.NANOSECONDS.toMillis(time),
IRMeasurement.Kind.GENERATION
)
}
fun dumpPerformanceReport(destination: File) {
destination.writeBytes(createPerformanceReport())
}
private fun recordGcTime() {
if (!isEnabled) return
ManagementFactory.getGarbageCollectorMXBeans().forEach {
val startCounts = startGCData[it.name]
val startCollectionTime = startCounts?.collectionTime ?: 0
val startCollectionCount = startCounts?.collectionCount ?: 0
measurements += GarbageCollectionMeasurement(
it.name,
it.collectionTime - startCollectionTime,
it.collectionCount - startCollectionCount
)
}
}
private fun recordJitCompilationTime() {
if (!isEnabled) return
val bean = ManagementFactory.getCompilationMXBean() ?: return
measurements += JitCompilationMeasurement(bean.totalCompilationTime)
}
private fun recordInitializationTime() {
val time = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - initStartNanos)
measurements += CompilerInitializationMeasurement(time)
}
private fun recordPerfCountersMeasurements() {
PerformanceCounter.report { s -> measurements += PerformanceCounterMeasurement(s) }
}
private fun createPerformanceReport(): ByteArray = buildString {
append("$presentableName performance report\n")
measurements.map { it.render() }.sorted().forEach { append("$it\n") }
}.toByteArray()
open fun notifyRepeat(total: Int, number: Int) {}
private data class GCData(val name: String, val collectionTime: Long, val collectionCount: Long) {
constructor(bean: GarbageCollectorMXBean) : this(bean.name, bean.collectionTime, bean.collectionCount)
}
fun renderCompilerPerformance(): String {
val relevantMeasurements = getMeasurementResults().filter {
it is CompilerInitializationMeasurement || it is CodeAnalysisMeasurement || it is CodeGenerationMeasurement || it is PerformanceCounterMeasurement
}
return "Compiler perf stats:\n" + relevantMeasurements.joinToString(separator = "\n") { " ${it.render()}" }
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.common.config
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
interface ContentRoot
/**
* @param isCommon whether this source root contains sources of a common module in a multi-platform project
*/
data class KotlinSourceRoot(val path: String, val isCommon: Boolean, val hmppModuleName: String?): ContentRoot
@JvmOverloads
fun CompilerConfiguration.addKotlinSourceRoot(path: String, isCommon: Boolean = false, hmppModuleName: String? = null) {
add(CLIConfigurationKeys.CONTENT_ROOTS, KotlinSourceRoot(path, isCommon, hmppModuleName))
}
fun CompilerConfiguration.addKotlinSourceRoots(sources: List<String>): Unit =
sources.forEach { addKotlinSourceRoot(it) }
val CompilerConfiguration.kotlinSourceRoots: List<KotlinSourceRoot>
get() = get(CLIConfigurationKeys.CONTENT_ROOTS)?.filterIsInstance<KotlinSourceRoot>().orEmpty()
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.common.extensions
import com.intellij.core.JavaCoreProjectEnvironment
import org.jetbrains.kotlin.cli.common.repl.ReplCompiler
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
import java.io.File
interface ReplFactoryExtension {
companion object : ProjectExtensionDescriptor<ReplFactoryExtension>(
"org.jetbrains.kotlin.replFactoryExtension",
ReplFactoryExtension::class.java
)
fun makeReplCompiler(
templateClassName: String,
templateClasspath: List<File>,
baseClassLoader: ClassLoader?,
configuration: CompilerConfiguration,
projectEnvironment: JavaCoreProjectEnvironment
): ReplCompiler
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.common.extensions
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
interface ScriptEvaluationExtension {
companion object : ProjectExtensionDescriptor<ScriptEvaluationExtension>(
"org.jetbrains.kotlin.scriptEvaluationExtension",
ScriptEvaluationExtension::class.java
)
fun isAccepted(arguments: CommonCompilerArguments): Boolean
fun eval(
arguments: CommonCompilerArguments,
configuration: CompilerConfiguration,
projectEnvironment: KotlinCoreEnvironment.ProjectEnvironment
): ExitCode
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.common.extensions
import com.intellij.core.JavaCoreProjectEnvironment
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
interface ShellExtension {
companion object : ProjectExtensionDescriptor<ShellExtension>(
"org.jetbrains.kotlin.shellExtension",
ShellExtension::class.java
)
fun isAccepted(arguments: CommonCompilerArguments): Boolean
fun run(
arguments: CommonCompilerArguments,
configuration: CompilerConfiguration,
projectEnvironment: JavaCoreProjectEnvironment
): ExitCode
}
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.common.messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
import org.jetbrains.kotlin.diagnostics.PsiDiagnosticUtils;
import static com.intellij.openapi.util.io.FileUtil.toSystemDependentName;
public class MessageUtil {
private MessageUtil() {}
@Nullable
public static CompilerMessageSourceLocation psiElementToMessageLocation(@Nullable PsiElement element) {
if (element == null) return null;
PsiFile file = element.getContainingFile();
return psiFileToMessageLocation(file, "<no path>", DiagnosticUtils.getLineAndColumnRangeInPsiFile(file, element.getTextRange()));
}
@Nullable
public static CompilerMessageSourceLocation psiFileToMessageLocation(
@NotNull PsiFile file,
@Nullable String defaultValue,
@NotNull PsiDiagnosticUtils.LineAndColumnRange range
) {
VirtualFile virtualFile = file.getVirtualFile();
String path = virtualFile != null ? virtualFileToPath(virtualFile) : defaultValue;
PsiDiagnosticUtils.LineAndColumn start = range.getStart();
PsiDiagnosticUtils.LineAndColumn end = range.getEnd();
return createMessageLocation(path, start.getLineContent(), start.getLine(), start.getColumn(), end.getLine(), end.getColumn());
}
@Nullable
public static CompilerMessageLocationWithRange createMessageLocation(
@Nullable String path,
@Nullable String lineContent,
int line,
int column,
int endLine,
int endColumn
) {
return CompilerMessageLocationWithRange.create(path, line, column, endLine, endColumn, lineContent);
}
@NotNull
public static String virtualFileToPath(@NotNull VirtualFile virtualFile) {
return toSystemDependentName(virtualFile.getPath());
}
}
@@ -0,0 +1,99 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.common
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager
import org.jetbrains.kotlin.load.kotlin.getSourceElement
import org.jetbrains.kotlin.load.kotlin.isContainedByCompiledPartOfOurModule
import org.jetbrains.kotlin.modules.Module
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.util.ModuleVisibilityHelper
import java.io.File
class ModuleVisibilityHelperImpl : ModuleVisibilityHelper {
override fun isInFriendModule(what: DeclarationDescriptor, from: DeclarationDescriptor): Boolean {
val fromSource = getSourceElement(from)
// We should check accessibility of 'from' in current module (some set of source files, which are compiled together),
// so we can assume that 'from' should have sources or is a LazyPackageDescriptor with some package files.
val project: Project = if (fromSource is KotlinSourceElement) {
fromSource.psi.project
} else {
(from as? LazyPackageDescriptor)?.declarationProvider?.getPackageFiles()?.firstOrNull()?.project ?: return true
}
val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(project)
if (!moduleVisibilityManager.enabled) return true
moduleVisibilityManager.friendPaths.forEach {
if (isContainedByCompiledPartOfOurModule(what, File(it))) return true
}
val modules = moduleVisibilityManager.chunk
val whatSource = getSourceElement(what)
if (whatSource is KotlinSourceElement) {
if (modules.size > 1 && fromSource is KotlinSourceElement) {
return findModule(what, modules) === findModule(from, modules)
}
return true
}
if (modules.isEmpty()) return false
if (modules.size == 1 && isContainedByCompiledPartOfOurModule(what, File(modules.single().getOutputDirectory()))) return true
return findModule(from, modules) === findModule(what, modules)
}
private fun findModule(descriptor: DeclarationDescriptor, modules: Collection<Module>): Module? {
val sourceElement = getSourceElement(descriptor)
return if (sourceElement is KotlinSourceElement) {
modules.singleOrNull() ?: modules.firstOrNull { sourceElement.psi.containingKtFile.virtualFile.path in it.getSourceFiles() }
} else {
modules.firstOrNull { module ->
isContainedByCompiledPartOfOurModule(descriptor, File(module.getOutputDirectory())) ||
module.getFriendPaths().any { isContainedByCompiledPartOfOurModule(descriptor, File(it)) }
}
}
}
}
/*
At the moment, there is no proper support for module infrastructure in the compiler.
So we add try to remember given list of interdependent modules and use it for checking visibility.
*/
class CliModuleVisibilityManagerImpl(override val enabled: Boolean) : ModuleVisibilityManager, Disposable {
override val chunk: MutableList<Module> = arrayListOf()
override val friendPaths: MutableList<String> = arrayListOf()
override fun addModule(module: Module) {
chunk.add(module)
}
override fun addFriendPath(path: String) {
friendPaths.add(File(path).absolutePath)
}
override fun dispose() {
chunk.clear()
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.common
interface PerformanceMeasurement {
fun render(): String
}
class JitCompilationMeasurement(private val milliseconds: Long) : PerformanceMeasurement {
override fun render(): String = "JIT time is $milliseconds ms"
}
class CompilerInitializationMeasurement(val milliseconds: Long) : PerformanceMeasurement {
override fun render(): String = "INIT: Compiler initialized in $milliseconds ms"
}
class CodeAnalysisMeasurement(val lines: Int?, val milliseconds: Long) : PerformanceMeasurement {
override fun render(): String = formatMeasurement("ANALYZE", milliseconds, lines)
}
class CodeGenerationMeasurement(val lines: Int?, val milliseconds: Long) : PerformanceMeasurement {
override fun render(): String = formatMeasurement("GENERATE", milliseconds, lines)
}
class GarbageCollectionMeasurement(val garbageCollectionKind: String, val milliseconds: Long, val count: Long) : PerformanceMeasurement {
override fun render(): String = "GC time for $garbageCollectionKind is $milliseconds ms, $count collections"
}
class PerformanceCounterMeasurement(private val counterReport: String) : PerformanceMeasurement {
override fun render(): String = counterReport
}
class IRMeasurement(val lines: Int?, val milliseconds: Long, val kind: Kind) : PerformanceMeasurement {
override fun render(): String = formatMeasurement("IR $kind", milliseconds, lines)
enum class Kind {
TRANSLATION, LOWERING, GENERATION
}
}
private fun formatMeasurement(name: String, time: Long, lines: Int?): String =
"%15s%8s ms".format(name, time) +
(lines?.let {
val lps = it.toDouble() * 1000 / time
"%12.3f loc/s".format(lps)
} ?: "")
@@ -0,0 +1,313 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiJavaModule
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.light.LightJavaModule
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.cli.common.config.ContentRoot
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageUtil
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRootBase
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootBase
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
import org.jetbrains.kotlin.cli.jvm.modules.JavaModuleGraph
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.isValidJavaFqName
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo
import org.jetbrains.kotlin.resolve.jvm.modules.KOTLIN_STDLIB_MODULE_NAME
import java.io.IOException
import java.util.jar.Attributes
import java.util.jar.Manifest
import kotlin.LazyThreadSafetyMode.NONE
class ClasspathRootsResolver(
private val psiManager: PsiManager,
private val messageCollector: MessageCollector?,
private val additionalModules: List<String>,
private val contentRootToVirtualFile: (JvmContentRootBase) -> VirtualFile?,
private val javaModuleFinder: CliJavaModuleFinder,
private val requireStdlibModule: Boolean,
private val outputDirectory: VirtualFile?,
private val javaFileManager: KotlinCliJavaFileManager,
private val jdkRelease: Int?
) {
val javaModuleGraph = JavaModuleGraph(javaModuleFinder)
private val searchScope = GlobalSearchScope.allScope(psiManager.project)
data class RootsAndModules(val roots: List<JavaRoot>, val modules: List<JavaModule>)
private data class RootWithPrefix(val root: VirtualFile, val packagePrefix: String?)
fun convertClasspathRoots(contentRoots: List<ContentRoot>): RootsAndModules {
val javaSourceRoots = mutableListOf<RootWithPrefix>()
val jvmClasspathRoots = mutableListOf<VirtualFile>()
val jvmModulePathRoots = mutableListOf<VirtualFile>()
for (contentRoot in contentRoots) {
if (contentRoot !is JvmContentRootBase) continue
val root = contentRootToVirtualFile(contentRoot) ?: continue
when (contentRoot) {
is JavaSourceRoot -> javaSourceRoots += RootWithPrefix(root, contentRoot.packagePrefix)
is JvmClasspathRootBase -> jvmClasspathRoots += root
is JvmModulePathRoot -> jvmModulePathRoots += root
else -> error("Unknown root type: $contentRoot")
}
}
return computeRoots(javaSourceRoots, jvmClasspathRoots, jvmModulePathRoots)
}
private fun computeRoots(
javaSourceRoots: List<RootWithPrefix>,
jvmClasspathRoots: List<VirtualFile>,
jvmModulePathRoots: List<VirtualFile>
): RootsAndModules {
val result = mutableListOf<JavaRoot>()
val modules = mutableListOf<JavaModule>()
val hasOutputDirectoryInClasspath = outputDirectory in jvmClasspathRoots || outputDirectory in jvmModulePathRoots
for ((root, packagePrefix) in javaSourceRoots) {
val modularRoot = modularSourceRoot(root, hasOutputDirectoryInClasspath)
if (modularRoot != null) {
modules += modularRoot
} else {
result += JavaRoot(root, JavaRoot.RootType.SOURCE, packagePrefix?.let { prefix ->
if (isValidJavaFqName(prefix)) FqName(prefix)
else null.also {
report(STRONG_WARNING, "Invalid package prefix name is ignored: $prefix")
}
})
}
}
for (root in jvmClasspathRoots) {
result += JavaRoot(root, JavaRoot.RootType.BINARY)
}
val outputDirectoryAddedAsPartOfModule = modules.any { module -> module.moduleRoots.any { it.file == outputDirectory } }
for (root in jvmModulePathRoots) {
// Do not add output directory as a separate module if we're compiling an explicit named module.
// It's going to be included as a root of our module in modularSourceRoot.
if (outputDirectoryAddedAsPartOfModule && root == outputDirectory) continue
val module = modularBinaryRoot(root)
if (module != null) {
modules += module
}
}
if (jdkRelease == null || jdkRelease >= 9) {
addModularRoots(modules, result)
} else {
//TODO: see also `addJvmSdkRoots` usages, some refactoring is required with moving such logic into one place
result += JavaRoot(javaModuleFinder.nonModuleRoot.file, JavaRoot.RootType.BINARY_SIG)
}
return RootsAndModules(result, modules)
}
private fun findSourceModuleInfo(root: VirtualFile): Pair<VirtualFile, PsiJavaModule>? {
val moduleInfoFile =
when {
root.isDirectory -> root.findChild(PsiJavaModule.MODULE_INFO_FILE)
root.name == PsiJavaModule.MODULE_INFO_FILE -> root
else -> null
} ?: return null
val psiFile = psiManager.findFile(moduleInfoFile) ?: return null
val psiJavaModule = psiFile.children.singleOrNull { it is PsiJavaModule } as? PsiJavaModule ?: return null
return moduleInfoFile to psiJavaModule
}
private fun modularSourceRoot(root: VirtualFile, hasOutputDirectoryInClasspath: Boolean): JavaModule.Explicit? {
val (moduleInfoFile, psiJavaModule) = findSourceModuleInfo(root) ?: return null
val sourceRoot = JavaModule.Root(root, isBinary = false)
val roots =
if (hasOutputDirectoryInClasspath)
listOf(sourceRoot, JavaModule.Root(outputDirectory!!, isBinary = true))
else listOf(sourceRoot)
return JavaModule.Explicit(JavaModuleInfo.create(psiJavaModule), roots, moduleInfoFile)
}
private fun modularBinaryRoot(root: VirtualFile): JavaModule? {
val isJar = root.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL
val manifest = lazy(NONE) { readManifestAttributes(root) }
val moduleInfoFile =
root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
?: if (isJar) tryLoadVersionSpecificModuleInfo(root, manifest) else null
if (moduleInfoFile != null) {
val moduleInfo = JavaModuleInfo.read(moduleInfoFile, javaFileManager, searchScope) ?: return null
return JavaModule.Explicit(moduleInfo, listOf(JavaModule.Root(root, isBinary = true)), moduleInfoFile)
}
// Only .jar files can be automatic modules
if (isJar) {
val moduleRoot = listOf(JavaModule.Root(root, isBinary = true))
val automaticModuleName = manifest.value?.getValue(AUTOMATIC_MODULE_NAME)
if (automaticModuleName != null) {
return JavaModule.Automatic(automaticModuleName, moduleRoot)
}
val originalFile = VfsUtilCore.virtualToIoFile(root)
val moduleName = LightJavaModule.moduleName(originalFile.nameWithoutExtension)
if (moduleName.isEmpty()) {
report(ERROR, "Cannot infer automatic module name for the file", VfsUtilCore.getVirtualFileForJar(root) ?: root)
return null
}
return JavaModule.Automatic(moduleName, moduleRoot)
}
return null
}
private fun tryLoadVersionSpecificModuleInfo(root: VirtualFile, manifest: Lazy<Attributes?>): VirtualFile? {
val versionsDir = root.findChild("META-INF")?.findChild("versions") ?: return null
val isMultiReleaseJar = manifest.value?.getValue(IS_MULTI_RELEASE)?.equals("true", ignoreCase = true)
if (isMultiReleaseJar != true) return null
val versions = versionsDir.children.filter {
val version = it.name.toIntOrNull()
version != null && version >= 9
}.sortedBy { it.name.toInt() }
for (version in versions) {
val file = version.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
if (file != null) return file
}
return null
}
private fun readManifestAttributes(jarRoot: VirtualFile): Attributes? {
val manifestFile = jarRoot.findChild("META-INF")?.findChild("MANIFEST.MF")
return try {
manifestFile?.inputStream?.let(::Manifest)?.mainAttributes
} catch (e: IOException) {
null
}
}
private fun addModularRoots(modules: List<JavaModule>, result: MutableList<JavaRoot>) {
// In current implementation, at most one source module is supported. This can be relaxed in the future if we support another
// compilation mode, similar to java's --module-source-path
val sourceModules = modules.filterIsInstance<JavaModule.Explicit>().filter(JavaModule::isSourceModule)
if (sourceModules.size > 1) {
for (module in sourceModules) {
report(ERROR, "Too many source module declarations found", module.moduleInfoFile)
}
return
}
for (module in modules) {
val existing = javaModuleFinder.findModule(module.name)
if (existing == null) {
javaModuleFinder.addUserModule(module)
} else if (module.moduleRoots != existing.moduleRoots) {
fun JavaModule.getRootFile() =
moduleRoots.firstOrNull()?.file?.let { VfsUtilCore.getVirtualFileForJar(it) ?: it }
val thisFile = module.getRootFile()
val existingFile = existing.getRootFile()
val atExistingPath = if (existingFile == null) "" else " at: ${existingFile.path}"
report(
STRONG_WARNING, "The root is ignored because a module with the same name '${module.name}' " +
"has been found earlier on the module path$atExistingPath", thisFile
)
}
}
if (javaModuleFinder.allObservableModules.none()) return
val sourceModule = sourceModules.singleOrNull()
val addAllModulePathToRoots = "ALL-MODULE-PATH" in additionalModules
if (addAllModulePathToRoots && sourceModule != null) {
report(ERROR, "-Xadd-modules=ALL-MODULE-PATH can only be used when compiling the unnamed module")
return
}
val rootModules = when {
sourceModule != null -> listOf(sourceModule.name) + additionalModules
addAllModulePathToRoots -> modules.map(JavaModule::name)
else -> javaModuleFinder.computeDefaultRootModules() + additionalModules
}
val allDependencies = javaModuleGraph.getAllDependencies(rootModules)
if (allDependencies.any { moduleName -> javaModuleFinder.findModule(moduleName) is JavaModule.Automatic }) {
// According to java.lang.module javadoc, if at least one automatic module is added to the module graph,
// all observable automatic modules should be added.
// There are no automatic modules in the JDK, so we select all automatic modules out of user modules
for (module in modules) {
if (module is JavaModule.Automatic) {
allDependencies += module.name
}
}
}
report(LOGGING, "Loading modules: $allDependencies")
for (moduleName in allDependencies) {
val module = javaModuleFinder.findModule(moduleName)
if (module == null) {
report(ERROR, "Module $moduleName cannot be found in the module graph")
} else {
result.addAll(module.getJavaModuleRoots())
}
}
if (requireStdlibModule && sourceModule != null && !javaModuleGraph.reads(sourceModule.name, KOTLIN_STDLIB_MODULE_NAME)) {
report(
ERROR,
"The Kotlin standard library is not found in the module graph. " +
"Please ensure you have the 'requires $KOTLIN_STDLIB_MODULE_NAME' clause in your module definition",
sourceModule.moduleInfoFile
)
}
}
private fun report(severity: CompilerMessageSeverity, message: String, file: VirtualFile? = null) {
if (messageCollector == null) {
throw IllegalStateException("${if (file != null) file.path + ":" else ""}$severity: $message (no MessageCollector configured)")
}
messageCollector.report(
severity, message,
if (file == null) null else CompilerMessageLocation.create(MessageUtil.virtualFileToPath(file))
)
}
private companion object {
const val AUTOMATIC_MODULE_NAME = "Automatic-Module-Name"
const val IS_MULTI_RELEASE = "Multi-Release"
}
}
@@ -0,0 +1,131 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.PsiSearchScopeUtil
import com.intellij.util.SmartList
import org.jetbrains.kotlin.asJava.KotlinAsJavaSupportBase
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.classes.KtDescriptorBasedFakeLightClass
import org.jetbrains.kotlin.asJava.classes.KtFakeLightClass
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.load.java.components.FilesByFacadeFqNameIndexer
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.lazy.ResolveSessionUtils
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
class CliKotlinAsJavaSupport(project: Project, private val traceHolder: CliTraceHolder) : KotlinAsJavaSupportBase<KtFile>(project) {
override fun findFilesForFacadeByPackage(
packageFqName: FqName,
searchScope: GlobalSearchScope
) = traceHolder.bindingContext.get(FilesByFacadeFqNameIndexer.FACADE_FILES_BY_PACKAGE_NAME, packageFqName)
?.filter { PsiSearchScopeUtil.isInScope(searchScope, it) }
.orEmpty()
override fun KtFile.findModule(): KtFile = this
override fun createInstanceOfDecompiledLightFacade(facadeFqName: FqName, files: List<KtFile>): KtLightClassForFacade? {
error("Should not be called")
}
override fun librariesTracker(element: PsiElement): ModificationTracker {
error("Should not be called")
}
override fun createInstanceOfLightFacade(facadeFqName: FqName, files: List<KtFile>): KtLightClassForFacade {
return LightClassGenerationSupport.getInstance(files.first().project).createUltraLightClassForFacade(facadeFqName, files)
}
override val KtFile.contentSearchScope: GlobalSearchScope get() = GlobalSearchScope.allScope(project)
override fun facadeIsApplicable(module: KtFile, file: KtFile): Boolean = !module.isCompiled
override fun findFilesForFacade(facadeFqName: FqName, searchScope: GlobalSearchScope): List<KtFile> {
if (facadeFqName.isRoot) return emptyList()
return traceHolder.bindingContext.get(FilesByFacadeFqNameIndexer.FACADE_FILES_BY_FQ_NAME, facadeFqName)?.filter {
PsiSearchScopeUtil.isInScope(searchScope, it)
}.orEmpty()
}
override fun getScriptClasses(scriptFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> {
if (scriptFqName.isRoot) {
return emptyList()
}
return findFilesForPackage(scriptFqName.parent(), scope).mapNotNull { file ->
file.script?.takeIf { it.fqName == scriptFqName }?.let { getLightClassForScript(it) }
}
}
override fun getKotlinInternalClasses(fqName: FqName, scope: GlobalSearchScope): Collection<PsiClass> = emptyList()
override fun getFakeLightClass(classOrObject: KtClassOrObject): KtFakeLightClass = KtDescriptorBasedFakeLightClass(classOrObject)
override fun findClassOrObjectDeclarationsInPackage(
packageFqName: FqName, searchScope: GlobalSearchScope
): Collection<KtClassOrObject> {
val files = findFilesForPackage(packageFqName, searchScope)
val result = SmartList<KtClassOrObject>()
for (file in files) {
for (declaration in file.declarations) {
if (declaration is KtClassOrObject) {
result.add(declaration)
}
}
}
return result
}
override fun packageExists(fqName: FqName, scope: GlobalSearchScope): Boolean = !traceHolder.module.getPackage(fqName).isEmpty()
override fun getSubPackages(fqn: FqName, scope: GlobalSearchScope): Collection<FqName> {
val packageView = traceHolder.module.getPackage(fqn)
return packageView.memberScope.getContributedDescriptors(
DescriptorKindFilter.PACKAGES,
MemberScope.ALL_NAME_FILTER
).mapNotNull { member -> (member as? PackageViewDescriptor)?.fqName }
}
override fun createInstanceOfLightScript(script: KtScript): KtLightClass {
return LightClassGenerationSupport.getInstance(script.project).createUltraLightClassForScript(script)
}
override fun findClassOrObjectDeclarations(fqName: FqName, searchScope: GlobalSearchScope): Collection<KtClassOrObject> {
return ResolveSessionUtils.getClassDescriptorsByFqName(traceHolder.module, fqName).mapNotNull {
val element = DescriptorToSourceUtils.getSourceFromDescriptor(it)
if (element is KtClassOrObject && PsiSearchScopeUtil.isInScope(searchScope, element)) {
element
} else null
}
}
override fun findFilesForPackage(packageFqName: FqName, searchScope: GlobalSearchScope): Collection<KtFile> {
return traceHolder.bindingContext.get(BindingContext.PACKAGE_TO_FILES, packageFqName)?.filter {
PsiSearchScopeUtil.isInScope(searchScope, it)
}.orEmpty()
}
override fun createFacadeForSyntheticFile(file: KtFile): KtLightClassForFacade = error("Should not be called")
override fun declarationLocation(file: KtFile): DeclarationLocation = DeclarationLocation.ProjectSources
override fun createInstanceOfDecompiledLightClass(classOrObject: KtClassOrObject): KtLightClass = error("Should not be called")
override fun createInstanceOfLightClass(classOrObject: KtClassOrObject): KtLightClass {
return LightClassGenerationSupport.getInstance(classOrObject.project).createUltraLightClass(classOrObject)
}
}
@@ -0,0 +1,109 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.classes.KtUltraLightSupport
import org.jetbrains.kotlin.asJava.classes.cleanFromAnonymousTypes
import org.jetbrains.kotlin.asJava.classes.lazyPub
import org.jetbrains.kotlin.asJava.classes.tryGetPredefinedName
import org.jetbrains.kotlin.cli.jvm.compiler.builder.LightClassConstructionContext
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.codegen.JvmCodegenUtil
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.load.java.components.JavaDeprecationSettings
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.KotlinType
/**
* This class solves the problem of interdependency between analyzing Kotlin code and generating KotlinLightClasses
* Consider the following example:
* KClass.kt refers to JClass.java and vice versa
* To analyze KClass.kt we need to load descriptors from JClass.java, and to do that we need a JetLightClass instance for KClass,
* which can only be constructed when the structure of KClass is known.
* To mitigate this, CliLightClassGenerationSupport hold a trace that is shared between the analyzer and JetLightClasses
*/
class CliLightClassGenerationSupport(
val traceHolder: CliTraceHolder,
private val project: Project
) : LightClassGenerationSupport() {
private class CliLightClassSupport(
private val project: Project,
override val languageVersionSettings: LanguageVersionSettings,
override val jvmTarget: JvmTarget
) : KtUltraLightSupport {
// This is the way to untie CliLightClassSupport and CliLightClassGenerationSupport to prevent descriptors leak
private val traceHolder: CliTraceHolder
get() = (getInstance(project) as CliLightClassGenerationSupport).traceHolder
override fun possiblyHasAlias(file: KtFile, shortName: Name): Boolean = true
override val moduleDescriptor get() = traceHolder.module
override val moduleName: String get() = JvmCodegenUtil.getModuleName(moduleDescriptor)
override val deprecationResolver: DeprecationResolver
get() = DeprecationResolver(
LockBasedStorageManager.NO_LOCKS,
languageVersionSettings,
JavaDeprecationSettings
)
override val typeMapper: KotlinTypeMapper by lazyPub {
KotlinTypeMapper(
BindingContext.EMPTY,
ClassBuilderMode.LIGHT_CLASSES,
moduleName,
languageVersionSettings,
useOldInlineClassesManglingScheme = false,
jvmTarget = jvmTarget,
typePreprocessor = KotlinType::cleanFromAnonymousTypes,
namePreprocessor = ::tryGetPredefinedName
)
}
}
private val ultraLightSupport: KtUltraLightSupport by lazyPub {
CliLightClassSupport(project, traceHolder.languageVersionSettings, traceHolder.jvmTarget)
}
override fun getUltraLightClassSupport(element: KtElement): KtUltraLightSupport {
require(element.project == project) { "ULC support created from another project from requested" }
return ultraLightSupport
}
val context: LightClassConstructionContext
get() = LightClassConstructionContext(
traceHolder.bindingContext,
traceHolder.module,
traceHolder.languageVersionSettings,
traceHolder.jvmTarget,
)
override fun resolveToDescriptor(declaration: KtDeclaration): DeclarationDescriptor? {
return traceHolder.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration)
}
override fun analyze(element: KtElement) = traceHolder.bindingContext
override fun analyzeAnnotation(element: KtAnnotationEntry) = traceHolder.bindingContext.get(BindingContext.ANNOTATION, element)
override fun analyzeWithContent(element: KtClassOrObject) = traceHolder.bindingContext
}
@@ -0,0 +1,28 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.js.resolve.getAnnotationsOnContainingJsModule
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
import org.jetbrains.kotlin.load.kotlin.getJvmModuleNameForDeserializedDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver
class CliModuleAnnotationsResolver : ModuleAnnotationsResolver {
private val packagePartProviders = mutableListOf<PackagePartProvider>()
fun addPackagePartProvider(packagePartProvider: PackagePartProvider) {
packagePartProviders += packagePartProvider
}
override fun getAnnotationsOnContainingModule(descriptor: DeclarationDescriptor): List<ClassId> {
getAnnotationsOnContainingJsModule(descriptor)?.let { return it }
val moduleName = getJvmModuleNameForDeserializedDescriptor(descriptor) ?: return emptyList()
return packagePartProviders.flatMap { it.getAnnotationsOnBinaryModule(moduleName) }
}
}
@@ -0,0 +1,107 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.jvm.JvmCodeAnalyzerInitializer
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
class CliTraceHolder : JvmCodeAnalyzerInitializer() {
lateinit var bindingContext: BindingContext
private set
lateinit var module: ModuleDescriptor
private set
lateinit var languageVersionSettings: LanguageVersionSettings
private set
lateinit var jvmTarget: JvmTarget
private set
override fun initialize(
trace: BindingTrace,
module: ModuleDescriptor,
codeAnalyzer: KotlinCodeAnalyzer,
languageVersionSettings: LanguageVersionSettings,
jvmTarget: JvmTarget
) {
this.bindingContext = trace.bindingContext
this.module = module
this.languageVersionSettings = languageVersionSettings
this.jvmTarget = jvmTarget
if (trace !is CliBindingTrace) {
throw IllegalArgumentException("Shared trace is expected to be subclass of ${CliBindingTrace::class.java.simpleName} class")
}
trace.setKotlinCodeAnalyzer(codeAnalyzer)
}
override fun createTrace(): BindingTraceContext {
return NoScopeRecordCliBindingTrace()
}
}
// TODO: needs better name + list of keys to skip somewhere
class NoScopeRecordCliBindingTrace : CliBindingTrace() {
override fun <K, V> record(slice: WritableSlice<K, V>, key: K, value: V) {
if (slice == BindingContext.LEXICAL_SCOPE || slice == BindingContext.DATA_FLOW_INFO_BEFORE) {
// In the compiler there's no need to keep scopes
return
}
super.record(slice, key, value)
}
override fun toString(): String {
return NoScopeRecordCliBindingTrace::class.java.name
}
}
open class CliBindingTrace @TestOnly constructor() : BindingTraceContext() {
private var kotlinCodeAnalyzer: KotlinCodeAnalyzer? = null
override fun toString(): String {
return CliBindingTrace::class.java.name
}
fun setKotlinCodeAnalyzer(kotlinCodeAnalyzer: KotlinCodeAnalyzer) {
this.kotlinCodeAnalyzer = kotlinCodeAnalyzer
}
@Suppress("UNCHECKED_CAST")
override fun <K, V> get(slice: ReadOnlySlice<K, V>, key: K): V? {
val value = super.get(slice, key)
if (value == null) {
if (key is KtDeclaration) {
// NB: intentional code duplication, see https://youtrack.jetbrains.com/issue/KT-43296
if (BindingContext.FUNCTION === slice) {
if (!KtPsiUtil.isLocal(key)) {
kotlinCodeAnalyzer!!.resolveToDescriptor(key)
return super.get(slice, key) as V?
}
}
if (BindingContext.VARIABLE === slice) {
if (!KtPsiUtil.isLocal(key)) {
kotlinCodeAnalyzer!!.resolveToDescriptor(key)
return super.get(slice, key) as V?
}
}
}
}
return value
}
}
@@ -0,0 +1,106 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope
import gnu.trove.THashSet
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
import java.io.InputStream
class CliVirtualFileFinder(
private val index: JvmDependenciesIndex,
private val scope: GlobalSearchScope,
private val enableSearchInCtSym: Boolean
) : VirtualFileFinder() {
override fun findVirtualFileWithHeader(classId: ClassId): VirtualFile? =
findBinaryOrSigClass(classId)
override fun findSourceOrBinaryVirtualFile(classId: ClassId) =
findBinaryOrSigClass(classId)
?: findSourceClass(classId, classId.relativeClassName.asString() + ".java")
override fun findMetadata(classId: ClassId): InputStream? {
assert(!classId.isNestedClass) { "Nested classes are not supported here: $classId" }
return findBinaryClass(
classId,
classId.shortClassName.asString() + MetadataPackageFragment.DOT_METADATA_FILE_EXTENSION
)?.inputStream
}
override fun findMetadataTopLevelClassesInPackage(packageFqName: FqName): Set<String> {
val result = THashSet<String>()
index.traverseDirectoriesInPackage(packageFqName, continueSearch = { dir, _ ->
for (child in dir.children) {
if (child.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION) {
result.add(child.nameWithoutExtension)
}
}
true
})
return result
}
override fun hasMetadataPackage(fqName: FqName): Boolean {
var found = false
index.traverseDirectoriesInPackage(fqName, continueSearch = { dir, _ ->
found = found or dir.children.any { it.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION }
!found
})
return found
}
override fun findBuiltInsData(packageFqName: FqName): InputStream? {
// "<builtins-metadata>" is just a made-up name
// JvmDependenciesIndex requires the ClassId of the class which we're searching for, to cache the last request+result
val classId = ClassId(packageFqName, Name.special("<builtins-metadata>"))
return findBinaryClass(classId, BuiltInSerializerProtocol.getBuiltInsFileName(packageFqName))?.inputStream
}
private fun findClass(classId: ClassId, fileName: String, rootType: Set<JavaRoot.RootType>) =
index.findClass(classId, acceptedRootTypes = rootType) { dir, _ ->
dir.findChild(fileName)?.takeIf(VirtualFile::isValid)
}?.takeIf { it in scope }
private fun findSigFileIfEnabled(
dir: VirtualFile,
simpleName: String
) = if (enableSearchInCtSym) dir.findChild("$simpleName.sig") else null
private fun findBinaryOrSigClass(classId: ClassId, simpleName: String, rootType: Set<JavaRoot.RootType>) =
index.findClass(classId, acceptedRootTypes = rootType) { dir, _ ->
val file = dir.findChild("$simpleName.class") ?: findSigFileIfEnabled(dir, simpleName)
if (file != null && file.isValid) file else null
}?.takeIf { it in scope }
private fun findBinaryOrSigClass(classId: ClassId) =
findBinaryOrSigClass(classId, classId.relativeClassName.asString().replace('.', '$'), JavaRoot.OnlyBinary)
private fun findBinaryClass(classId: ClassId, fileName: String) = findClass(classId, fileName, JavaRoot.OnlyBinary)
private fun findSourceClass(classId: ClassId, fileName: String) = findClass(classId, fileName, JavaRoot.OnlySource)
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
// TODO: create different JvmDependenciesIndex instances for different sets of source roots to improve performance
class CliVirtualFileFinderFactory(private val index: JvmDependenciesIndex, private val enableSearchInCtSym: Boolean) : VirtualFileFinderFactory {
override fun create(scope: GlobalSearchScope): VirtualFileFinder = CliVirtualFileFinder(index, scope, enableSearchInCtSym)
override fun create(project: Project, module: ModuleDescriptor): VirtualFileFinder =
CliVirtualFileFinder(index, GlobalSearchScope.allScope(project), enableSearchInCtSym)
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler;
public enum EnvironmentConfigFiles {
JVM_CONFIG_FILES,
JS_CONFIG_FILES,
NATIVE_CONFIG_FILES,
WASM_CONFIG_FILES,
METADATA_CONFIG_FILES,
}
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.core.CoreApplicationEnvironment
import com.intellij.lang.jvm.facade.JvmElementProvider
import com.intellij.openapi.extensions.ExtensionsArea
import com.intellij.psi.JavaModuleSystem
import com.intellij.psi.impl.compiled.ClsCustomNavigationPolicy
internal object IdeaExtensionPoints {
fun registerVersionSpecificAppExtensionPoints(area: ExtensionsArea) {
@Suppress("DEPRECATION")
CoreApplicationEnvironment.registerExtensionPoint(area, ClsCustomNavigationPolicy.EP_NAME, ClsCustomNavigationPolicy::class.java)
CoreApplicationEnvironment.registerExtensionPoint(area, JavaModuleSystem.EP_NAME, JavaModuleSystem::class.java)
}
fun registerVersionSpecificProjectExtensionPoints(area: ExtensionsArea) {
CoreApplicationEnvironment.registerExtensionPoint(area, JvmElementProvider.EP_NAME, JvmElementProvider::class.java)
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.LanguageLevelProjectExtension
import com.intellij.pom.java.LanguageLevel
fun Project.setupHighestLanguageLevel() {
LanguageLevelProjectExtension.getInstance(this).languageLevel =
LanguageLevel.values().firstOrNull { it.name == "JDK_17" }
?: LanguageLevel.values().firstOrNull { it.name == "JDK_15_PREVIEW" }
?: LanguageLevel.JDK_X
}
@@ -0,0 +1,91 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.SmartList
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.LOGGING
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartProviderBase
import org.jetbrains.kotlin.load.kotlin.loadModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
import org.jetbrains.kotlin.resolve.jvm.JvmCompilerDeserializationConfiguration
import java.io.ByteArrayOutputStream
import java.io.EOFException
import java.io.PrintStream
class JvmPackagePartProvider(
languageVersionSettings: LanguageVersionSettings,
private val scope: GlobalSearchScope
) : JvmPackagePartProviderBase<VirtualFile>() {
override val deserializationConfiguration = JvmCompilerDeserializationConfiguration(languageVersionSettings)
override val loadedModules: MutableList<ModuleMappingInfo<VirtualFile>> = SmartList()
fun addRoots(roots: List<JavaRoot>, messageCollector: MessageCollector) {
for ((root, type) in roots) {
if (type != JavaRoot.RootType.BINARY) continue
if (root !in scope) continue
val metaInf = root.findChild("META-INF") ?: continue
for (moduleFile in metaInf.children) {
if (!moduleFile.name.endsWith(ModuleMapping.MAPPING_FILE_EXT)) continue
tryLoadModuleMapping(
{ moduleFile.contentsToByteArray() }, moduleFile.toString(), moduleFile.path,
deserializationConfiguration, messageCollector
)?.let {
loadedModules.add(ModuleMappingInfo(root, it, moduleFile.nameWithoutExtension))
}
}
}
}
}
fun tryLoadModuleMapping(
getModuleBytes: () -> ByteArray,
debugName: String,
modulePath: String,
deserializationConfiguration: CompilerDeserializationConfiguration,
messageCollector: MessageCollector
): ModuleMapping? = try {
ModuleMapping.loadModuleMapping(getModuleBytes(), debugName, deserializationConfiguration) { incompatibleVersion ->
messageCollector.report(
ERROR,
"Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is " +
"$incompatibleVersion, expected version is ${JvmMetadataVersion.INSTANCE}.",
CompilerMessageLocation.create(modulePath)
)
}
} catch (e: EOFException) {
messageCollector.report(
ERROR, "Error occurred when reading the module: ${e.message}", CompilerMessageLocation.create(modulePath)
)
messageCollector.report(
LOGGING,
String(ByteArrayOutputStream().also { e.printStackTrace(PrintStream(it)) }.toByteArray()),
CompilerMessageLocation.create(modulePath)
)
null
}
@@ -0,0 +1,329 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.core.CoreJavaFileManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.impl.file.PsiPackageImpl
import com.intellij.psi.search.GlobalSearchScope
import gnu.trove.THashMap
import gnu.trove.THashSet
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex
import org.jetbrains.kotlin.cli.jvm.index.SingleJavaFileRootsIndex
import org.jetbrains.kotlin.load.java.JavaClassFinder
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.isNotTopLevelClass
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
import org.jetbrains.kotlin.util.PerformanceCounter
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.*
// TODO: do not inherit from CoreJavaFileManager to avoid accidental usage of its methods which do not use caches/indices
// Currently, the only relevant usage of this class as CoreJavaFileManager is at CoreJavaDirectoryService.getPackage,
// which is indirectly invoked from PsiPackage.getSubPackages
class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager) : CoreJavaFileManager(myPsiManager), KotlinCliJavaFileManager {
private val perfCounter = PerformanceCounter.create("Find Java class")
private lateinit var index: JvmDependenciesIndex
private lateinit var singleJavaFileRootsIndex: SingleJavaFileRootsIndex
private lateinit var packagePartProviders: List<JvmPackagePartProvider>
private val topLevelClassesCache: MutableMap<FqName, VirtualFile?> = THashMap()
private val allScope = GlobalSearchScope.allScope(myPsiManager.project)
private var usePsiClassFilesReading = false
fun initialize(
index: JvmDependenciesIndex,
packagePartProviders: List<JvmPackagePartProvider>,
singleJavaFileRootsIndex: SingleJavaFileRootsIndex,
usePsiClassFilesReading: Boolean
) {
this.index = index
this.packagePartProviders = packagePartProviders
this.singleJavaFileRootsIndex = singleJavaFileRootsIndex
this.usePsiClassFilesReading = usePsiClassFilesReading
}
private fun findPsiClass(classId: ClassId, searchScope: GlobalSearchScope): PsiClass? = perfCounter.time {
findVirtualFileForTopLevelClass(classId, searchScope)?.findPsiClassInVirtualFile(classId.relativeClassName.asString())
}
private fun findVirtualFileForTopLevelClass(classId: ClassId, searchScope: GlobalSearchScope): VirtualFile? {
val relativeClassName = classId.relativeClassName.asString()
val outerMostClassFqName = classId.packageFqName.child(classId.relativeClassName.pathSegments().first())
return topLevelClassesCache.getOrPut(outerMostClassFqName) {
// Search java sources first. For build tools, it makes sense to build new files passing all the
// class files for the previous build on the class path.
//
// Suppose we have A.java and B.kt, we compile them and have the class files in previous.jar.
// Now we change both. A field is added to A which is used from B.kt.
//
// For a compilation such as:
//
// kotlinc -cp previous.jar A.java B.kt
//
// we want to make sure that we get the new A.java and not the old version A.class from previous.jar.
//
// Otherwise B.kt will not see the newly added field in A.
val outerMostClassId = ClassId.topLevel(outerMostClassFqName)
singleJavaFileRootsIndex.findJavaSourceClass(outerMostClassId)
?: index.findClass(outerMostClassId) { dir, type ->
findVirtualFileGivenPackage(dir, relativeClassName, type)
}
}?.takeIf { it in searchScope }
}
private val binaryCache: MutableMap<ClassId, JavaClass?> = THashMap()
private val signatureParsingComponent = BinaryClassSignatureParser()
fun findClass(classId: ClassId, searchScope: GlobalSearchScope) = findClass(JavaClassFinder.Request(classId), searchScope)
override fun findClass(request: JavaClassFinder.Request, searchScope: GlobalSearchScope): JavaClass? {
val (classId, classFileContentFromRequest, outerClassFromRequest) = request
val virtualFile = findVirtualFileForTopLevelClass(classId, searchScope) ?: return null
if (!usePsiClassFilesReading && (virtualFile.extension == "class" || virtualFile.extension == "sig")) {
// We return all class files' names in the directory in knownClassNamesInPackage method, so one may request an inner class
return binaryCache.getOrPut(classId) {
// Note that currently we implicitly suppose that searchScope for binary classes is constant and we do not use it
// as a key in cache
// This is a true assumption by now since there are two search scopes in compiler: one for sources and another one for binary
// When it become wrong because we introduce the modules into CLI, it's worth to consider
// having different KotlinCliJavaFileManagerImpl's for different modules
classId.outerClassId?.let { outerClassId ->
val outerClass = outerClassFromRequest ?: findClass(outerClassId, searchScope)
return@getOrPut if (outerClass is BinaryJavaClass)
outerClass.findInnerClass(classId.shortClassName, classFileContentFromRequest)
else
outerClass?.findInnerClass(classId.shortClassName)
}
// Here, we assume the class is top-level
val classContent = classFileContentFromRequest ?: virtualFile.contentsToByteArray()
if (virtualFile.nameWithoutExtension.contains("$") && isNotTopLevelClass(classContent)) return@getOrPut null
val resolver = ClassifierResolutionContext { findClass(it, allScope) }
BinaryJavaClass(
virtualFile, classId.asSingleFqName(), resolver, signatureParsingComponent,
outerClass = null, classContent = classContent
)
}
}
return virtualFile.findPsiClassInVirtualFile(classId.relativeClassName.asString())?.let(::JavaClassImpl)
}
// this method is called from IDEA to resolve dependencies in Java code
// which supposedly shouldn't have errors so the dependencies exist in general
override fun findClass(qName: String, scope: GlobalSearchScope): PsiClass? {
// String cannot be reliably converted to ClassId because we don't know where the package name ends and class names begin.
// For example, if qName is "a.b.c.d.e", we should either look for a top level class "e" in the package "a.b.c.d",
// or, for example, for a nested class with the relative qualified name "c.d.e" in the package "a.b".
// Below, we start by looking for the top level class "e" in the package "a.b.c.d" first, then for the class "d.e" in the package
// "a.b.c", and so on, until we find something. Most classes are top level, so most of the times the search ends quickly
forEachClassId(qName) { classId ->
findPsiClass(classId, scope)?.let { return it }
}
return null
}
private inline fun forEachClassId(fqName: String, block: (ClassId) -> Unit) {
var classId = fqName.toSafeTopLevelClassId() ?: return
while (true) {
block(classId)
val packageFqName = classId.packageFqName
if (packageFqName.isRoot) break
classId = ClassId(
packageFqName.parent(),
FqName(packageFqName.shortName().asString() + "." + classId.relativeClassName.asString()),
false
)
}
}
override fun findClasses(qName: String, scope: GlobalSearchScope): Array<PsiClass> = perfCounter.time {
val result = ArrayList<PsiClass>(1)
forEachClassId(qName) { classId ->
val relativeClassName = classId.relativeClassName.asString()
// Search java sources first. For build tools, it makes sense to build new files passing all the
// class files for the previous build on the class path.
result.addIfNotNull(
singleJavaFileRootsIndex.findJavaSourceClass(classId)
?.takeIf { it in scope }
?.findPsiClassInVirtualFile(relativeClassName)
)
index.traverseDirectoriesInPackage(classId.packageFqName) { dir, rootType ->
val psiClass =
findVirtualFileGivenPackage(dir, relativeClassName, rootType)
?.takeIf { it in scope }
?.findPsiClassInVirtualFile(relativeClassName)
if (psiClass != null) {
result.add(psiClass)
}
// traverse all
true
}
if (result.isNotEmpty()) {
return@time result.toTypedArray()
}
}
PsiClass.EMPTY_ARRAY
}
override fun findPackage(packageName: String): PsiPackage? {
var found = false
val packageFqName = packageName.toSafeFqName() ?: return null
index.traverseDirectoriesInPackage(packageFqName) { _, _ ->
found = true
//abort on first found
false
}
if (!found) {
found = packagePartProviders.any { it.findPackageParts(packageName).isNotEmpty() }
}
if (!found) {
found = singleJavaFileRootsIndex.findJavaSourceClasses(packageFqName).isNotEmpty()
}
if (!found) return null
return object : PsiPackageImpl(myPsiManager, packageName) {
// Do not check validness for packages we just made sure are actually present
// It might be important for source roots that have non-trivial package prefix
override fun isValid() = true
}
}
private fun findVirtualFileGivenPackage(
packageDir: VirtualFile,
classNameWithInnerClasses: String,
rootType: JavaRoot.RootType
): VirtualFile? {
val topLevelClassName = classNameWithInnerClasses.substringBefore('.')
val vFile = when (rootType) {
JavaRoot.RootType.BINARY -> packageDir.findChild("$topLevelClassName.class")
JavaRoot.RootType.BINARY_SIG -> packageDir.findChild("$topLevelClassName.sig")
JavaRoot.RootType.SOURCE -> packageDir.findChild("$topLevelClassName.java")
} ?: return null
if (!vFile.isValid) {
LOG.error("Invalid child of valid parent: ${vFile.path}; ${packageDir.isValid} path=${packageDir.path}")
return null
}
return vFile
}
private fun VirtualFile.findPsiClassInVirtualFile(classNameWithInnerClasses: String): PsiClass? {
val file = myPsiManager.findFile(this) as? PsiClassOwner ?: return null
return findClassInPsiFile(classNameWithInnerClasses, file)
}
override fun knownClassNamesInPackage(packageFqName: FqName): Set<String> {
val result = THashSet<String>()
index.traverseDirectoriesInPackage(packageFqName, continueSearch = { dir, _ ->
for (child in dir.children) {
if (child.extension == "class" || child.extension == "java" || child.extension == "sig") {
result.add(child.nameWithoutExtension)
}
}
true
})
for (classId in singleJavaFileRootsIndex.findJavaSourceClasses(packageFqName)) {
assert(!classId.isNestedClass) { "ClassId of a single .java source class should not be nested: $classId" }
result.add(classId.shortClassName.asString())
}
return result
}
override fun findModules(moduleName: String, scope: GlobalSearchScope): Collection<PsiJavaModule> {
// TODO
return emptySet()
}
override fun getNonTrivialPackagePrefixes(): Collection<String> = emptyList()
companion object {
private val LOG = Logger.getInstance(KotlinCliJavaFileManagerImpl::class.java)
private fun findClassInPsiFile(classNameWithInnerClassesDotSeparated: String, file: PsiClassOwner): PsiClass? {
for (topLevelClass in file.classes) {
val candidate = findClassByTopLevelClass(classNameWithInnerClassesDotSeparated, topLevelClass)
if (candidate != null) {
return candidate
}
}
return null
}
private fun findClassByTopLevelClass(className: String, topLevelClass: PsiClass): PsiClass? {
if (className.indexOf('.') < 0) {
return if (className == topLevelClass.name) topLevelClass else null
}
val segments = StringUtil.split(className, ".").iterator()
if (!segments.hasNext() || segments.next() != topLevelClass.name) {
return null
}
var curClass = topLevelClass
while (segments.hasNext()) {
val innerClassName = segments.next()
val innerClass = curClass.findInnerClassByName(innerClassName, false) ?: return null
curClass = innerClass
}
return curClass
}
}
}
// a sad workaround to avoid throwing exception when called from inside IDEA code
private fun <T : Any> safely(compute: () -> T): T? =
try {
compute()
} catch (e: IllegalArgumentException) {
null
} catch (e: AssertionError) {
null
}
private fun String.toSafeFqName(): FqName? = safely { FqName(this) }
private fun String.toSafeTopLevelClassId(): ClassId? = safely { ClassId.topLevel(FqName(this)) }
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.DynamicBundle
import com.intellij.codeInsight.ContainerProvider
import com.intellij.codeInsight.runner.JavaMainMethodProvider
import com.intellij.core.JavaCoreApplicationEnvironment
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.lang.MetaLanguage
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.psi.FileContextProvider
import com.intellij.psi.augment.PsiAugmentProvider
import com.intellij.psi.impl.smartPointers.SmartPointerAnchorProvider
import com.intellij.psi.meta.MetaDataContributor
import org.jetbrains.kotlin.cli.jvm.compiler.IdeaExtensionPoints.registerVersionSpecificAppExtensionPoints
import org.jetbrains.kotlin.cli.jvm.compiler.jarfs.FastJarFileSystem
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
class KotlinCoreApplicationEnvironment private constructor(
parentDisposable: Disposable, unitTestMode: Boolean
) :
JavaCoreApplicationEnvironment(parentDisposable, unitTestMode) {
init {
registerFileType(JavaClassFileType.INSTANCE, "sig");
}
override fun createJrtFileSystem(): VirtualFileSystem {
return CoreJrtFileSystem()
}
private var fastJarFileSystemField: FastJarFileSystem? = null
private var fastJarFileSystemFieldInitialized = false
val fastJarFileSystem: FastJarFileSystem?
get() {
synchronized(KotlinCoreEnvironment.APPLICATION_LOCK) {
if (!fastJarFileSystemFieldInitialized) {
// may return null e.g. on the old JDKs, therefore fastJarFileSystemFieldInitialized flag is needed
fastJarFileSystemField = FastJarFileSystem.createIfUnmappingPossible()?.also {
Disposer.register(parentDisposable) {
it.clearHandlersCache()
}
}
fastJarFileSystemFieldInitialized = true
}
return fastJarFileSystemField
}
}
fun idleCleanup() {
fastJarFileSystemField?.clearHandlersCache()
}
companion object {
fun create(
parentDisposable: Disposable, unitTestMode: Boolean
): KotlinCoreApplicationEnvironment {
val environment = KotlinCoreApplicationEnvironment(parentDisposable, unitTestMode)
registerExtensionPoints()
return environment
}
@Suppress("UnstableApiUsage")
private fun registerExtensionPoints() {
registerApplicationExtensionPoint(DynamicBundle.LanguageBundleEP.EP_NAME, DynamicBundle.LanguageBundleEP::class.java)
registerApplicationExtensionPoint(FileContextProvider.EP_NAME, FileContextProvider::class.java)
registerApplicationExtensionPoint(MetaDataContributor.EP_NAME, MetaDataContributor::class.java)
registerApplicationExtensionPoint(PsiAugmentProvider.EP_NAME, PsiAugmentProvider::class.java)
registerApplicationExtensionPoint(JavaMainMethodProvider.EP_NAME, JavaMainMethodProvider::class.java)
registerApplicationExtensionPoint(ContainerProvider.EP_NAME, ContainerProvider::class.java)
registerApplicationExtensionPoint(MetaLanguage.EP_NAME, MetaLanguage::class.java)
registerApplicationExtensionPoint(SmartPointerAnchorProvider.EP_NAME, SmartPointerAnchorProvider::class.java)
registerVersionSpecificAppExtensionPoints(ApplicationManager.getApplication().extensionArea)
}
}
}
@@ -0,0 +1,775 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("DEPRECATION")
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.codeInsight.ExternalAnnotationsManager
import com.intellij.codeInsight.InferredAnnotationsManager
import com.intellij.core.CoreApplicationEnvironment
import com.intellij.core.CoreJavaFileManager
import com.intellij.core.JavaCoreProjectEnvironment
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.lang.java.JavaParserDefinition
import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.TransactionGuard
import com.intellij.openapi.application.TransactionGuardImpl
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionsArea
import com.intellij.openapi.fileTypes.PlainTextFileType
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.PersistentFSConstants
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.openapi.vfs.impl.ZipHandler
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.JavaClassSupersImpl
import com.intellij.psi.impl.PsiElementFinderImpl
import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.impl.file.impl.JavaFileManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.JavaClassSupers
import com.intellij.util.io.URLUtil
import com.intellij.util.lang.UrlClassLoader
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.jvm.extensions.ClassGeneratorExtension
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.CliModuleVisibilityManagerImpl
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.cli.common.config.ContentRoot
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension
import org.jetbrains.kotlin.cli.common.extensions.ShellExtension
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.toBooleanLenient
import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.cli.jvm.index.*
import org.jetbrains.kotlin.cli.jvm.javac.JavacWrapperRegistrar
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver
import org.jetbrains.kotlin.codegen.extensions.ClassFileFactoryFinalizerExtension
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.compiler.plugin.registerInProject
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.extensions.*
import org.jetbrains.kotlin.extensions.internal.CandidateInterceptor
import org.jetbrains.kotlin.extensions.internal.InternalNonStableExtensionPoints
import org.jetbrains.kotlin.extensions.internal.TypeResolutionInterceptor
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.js.translate.extensions.JsSyntheticTranslateExtension
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache
import org.jetbrains.kotlin.load.kotlin.MetadataFinderFactory
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer
import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver
import org.jetbrains.kotlin.resolve.extensions.AssignResolutionAltererExtension
import org.jetbrains.kotlin.resolve.extensions.ExtraImportsProviderExtension
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.SyntheticJavaResolveExtension
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
import org.jetbrains.kotlin.resolve.lazy.declarations.CliDeclarationProviderFactoryService
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService
import org.jetbrains.kotlin.serialization.DescriptorSerializerPlugin
import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
import java.nio.file.FileSystems
import java.util.zip.ZipFile
class KotlinCoreEnvironment private constructor(
val projectEnvironment: ProjectEnvironment,
val configuration: CompilerConfiguration,
configFiles: EnvironmentConfigFiles
) {
class ProjectEnvironment(
disposable: Disposable,
applicationEnvironment: KotlinCoreApplicationEnvironment,
configuration: CompilerConfiguration
) :
KotlinCoreProjectEnvironment(disposable, applicationEnvironment) {
val jarFileSystem: VirtualFileSystem
init {
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
setIdeaIoUseFallback()
if (configuration.getBoolean(JVMConfigurationKeys.USE_FAST_JAR_FILE_SYSTEM)) {
messageCollector?.report(
STRONG_WARNING,
"Using new faster version of JAR FS: it should make your build faster, but the new implementation is experimental"
)
}
jarFileSystem = when {
configuration.getBoolean(JVMConfigurationKeys.USE_PSI_CLASS_FILES_READING) -> {
applicationEnvironment.jarFileSystem
}
configuration.getBoolean(JVMConfigurationKeys.USE_FAST_JAR_FILE_SYSTEM) || configuration.getBoolean(CommonConfigurationKeys.USE_FIR) -> {
val fastJarFs = applicationEnvironment.fastJarFileSystem
if (fastJarFs == null) {
messageCollector?.report(
CompilerMessageSeverity.STRONG_WARNING,
"Your JDK doesn't seem to support mapped buffer unmapping, so the slower (old) version of JAR FS will be used"
)
applicationEnvironment.jarFileSystem
} else fastJarFs
}
else -> applicationEnvironment.jarFileSystem
}
}
private var extensionRegistered = false
override fun preregisterServices() {
registerProjectExtensionPoints(project.extensionArea)
}
fun registerExtensionsFromPlugins(configuration: CompilerConfiguration) {
if (!extensionRegistered) {
registerPluginExtensionPoints(project)
registerExtensionsFromPlugins(project, configuration)
extensionRegistered = true
}
}
override fun registerJavaPsiFacade() {
with(project) {
registerService(
CoreJavaFileManager::class.java,
ServiceManager.getService(this, JavaFileManager::class.java) as CoreJavaFileManager
)
registerKotlinLightClassSupport(project)
registerService(ExternalAnnotationsManager::class.java, MockExternalAnnotationsManager())
registerService(InferredAnnotationsManager::class.java, MockInferredAnnotationsManager())
}
super.registerJavaPsiFacade()
}
}
private val sourceFiles = mutableListOf<KtFile>()
private val rootsIndex: JvmDependenciesDynamicCompoundIndex
private val packagePartProviders = mutableListOf<JvmPackagePartProvider>()
private val classpathRootsResolver: ClasspathRootsResolver
private val initialRoots = ArrayList<JavaRoot>()
init {
projectEnvironment.configureProjectEnvironment(configuration, configFiles)
val project = projectEnvironment.project
project.registerService(DeclarationProviderFactoryService::class.java, CliDeclarationProviderFactoryService(sourceFiles))
sourceFiles += createSourceFilesFromSourceRoots(configuration, project, getSourceRootsCheckingForDuplicates())
collectAdditionalSources(project)
sourceFiles.sortBy { it.virtualFile.path }
val javaFileManager = ServiceManager.getService(project, CoreJavaFileManager::class.java) as KotlinCliJavaFileManagerImpl
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
val jdkHome = configuration.get(JVMConfigurationKeys.JDK_HOME)
val releaseTarget = configuration.get(JVMConfigurationKeys.JDK_RELEASE)
val javaModuleFinder = CliJavaModuleFinder(
jdkHome,
messageCollector,
javaFileManager,
project,
releaseTarget
)
val outputDirectory =
configuration.get(JVMConfigurationKeys.MODULES)?.singleOrNull()?.getOutputDirectory()
?: configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)?.absolutePath
classpathRootsResolver = ClasspathRootsResolver(
PsiManager.getInstance(project),
messageCollector,
configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES),
this::contentRootToVirtualFile,
javaModuleFinder,
!configuration.getBoolean(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE),
outputDirectory?.let(this::findLocalFile),
javaFileManager,
releaseTarget
)
val (initialRoots, javaModules) =
classpathRootsResolver.convertClasspathRoots(configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS))
this.initialRoots.addAll(initialRoots)
val (roots, singleJavaFileRoots) =
initialRoots.partition { (file) -> file.isDirectory || file.extension != JavaFileType.DEFAULT_EXTENSION }
// REPL and kapt2 update classpath dynamically
rootsIndex = JvmDependenciesDynamicCompoundIndex().apply {
addIndex(JvmDependenciesIndexImpl(roots))
updateClasspathFromRootsIndex(this)
}
javaFileManager.initialize(
rootsIndex,
packagePartProviders,
SingleJavaFileRootsIndex(singleJavaFileRoots),
configuration.getBoolean(JVMConfigurationKeys.USE_PSI_CLASS_FILES_READING)
)
project.registerService(
JavaModuleResolver::class.java,
CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules, javaModuleFinder.systemModules.toList(), project)
)
val finderFactory = CliVirtualFileFinderFactory(rootsIndex, releaseTarget != null)
project.registerService(MetadataFinderFactory::class.java, finderFactory)
project.registerService(VirtualFileFinderFactory::class.java, finderFactory)
project.putUserData(APPEND_JAVA_SOURCE_ROOTS_HANDLER_KEY, fun(roots: List<File>) {
updateClasspath(roots.map { JavaSourceRoot(it, null) })
})
project.setupHighestLanguageLevel()
}
private fun collectAdditionalSources(project: MockProject) {
var unprocessedSources: Collection<KtFile> = sourceFiles
val processedSources = HashSet<KtFile>()
val processedSourcesByExtension = HashMap<CollectAdditionalSourcesExtension, Collection<KtFile>>()
// repeat feeding extensions with sources while new sources a being added
var sourceCollectionIterations = 0
while (unprocessedSources.isNotEmpty()) {
if (sourceCollectionIterations++ > 10) { // TODO: consider using some appropriate global constant
throw IllegalStateException("Unable to collect additional sources in reasonable number of iterations")
}
processedSources.addAll(unprocessedSources)
val allNewSources = ArrayList<KtFile>()
for (extension in CollectAdditionalSourcesExtension.getInstances(project)) {
// do not feed the extension with the sources it returned on the previous iteration
val sourcesToProcess = unprocessedSources - (processedSourcesByExtension[extension] ?: emptyList())
val newSources = extension.collectAdditionalSourcesAndUpdateConfiguration(sourcesToProcess, configuration, project)
if (newSources.isNotEmpty()) {
allNewSources.addAll(newSources)
processedSourcesByExtension[extension] = newSources
}
}
unprocessedSources = allNewSources.filterNot { processedSources.contains(it) }.distinct()
sourceFiles += unprocessedSources
}
}
fun addKotlinSourceRoots(rootDirs: List<File>) {
val roots = rootDirs.map { KotlinSourceRoot(it.absolutePath, isCommon = false, hmppModuleName = null) }
sourceFiles += createSourceFilesFromSourceRoots(configuration, project, roots).toSet() - sourceFiles
}
fun createPackagePartProvider(scope: GlobalSearchScope): JvmPackagePartProvider {
return JvmPackagePartProvider(configuration.languageVersionSettings, scope).apply {
addRoots(initialRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY))
packagePartProviders += this
(ModuleAnnotationsResolver.getInstance(project) as CliModuleAnnotationsResolver).addPackagePartProvider(this)
}
}
private val VirtualFile.javaFiles: List<VirtualFile>
get() = mutableListOf<VirtualFile>().apply {
VfsUtilCore.processFilesRecursively(this@javaFiles) { file ->
if (file.extension == JavaFileType.DEFAULT_EXTENSION || file.fileType == JavaFileType.INSTANCE) {
add(file)
}
true
}
}
private val allJavaFiles: List<File>
get() = configuration.javaSourceRoots
.mapNotNull(this::findLocalFile)
.flatMap { it.javaFiles }
.map { File(it.canonicalPath) }
fun registerJavac(
javaFiles: List<File> = allJavaFiles,
kotlinFiles: List<KtFile> = sourceFiles,
arguments: Array<String>? = null,
bootClasspath: List<File>? = null,
sourcePath: List<File>? = null
): Boolean {
return JavacWrapperRegistrar.registerJavac(
projectEnvironment.project, configuration, javaFiles, kotlinFiles, arguments, bootClasspath, sourcePath,
LightClassGenerationSupport.getInstance(project), packagePartProviders
)
}
private val applicationEnvironment: CoreApplicationEnvironment
get() = projectEnvironment.environment
val project: Project
get() = projectEnvironment.project
fun countLinesOfCode(sourceFiles: List<KtFile>): Int =
sourceFiles.sumBy { sourceFile ->
val text = sourceFile.text
StringUtil.getLineBreakCount(text) + (if (StringUtil.endsWithLineBreak(text)) 0 else 1)
}
private fun updateClasspathFromRootsIndex(index: JvmDependenciesIndex) {
index.indexedRoots.forEach {
projectEnvironment.addSourcesToClasspath(it.file)
}
}
fun updateClasspath(contentRoots: List<ContentRoot>): List<File>? {
// TODO: add new Java modules to CliJavaModuleResolver
val newRoots = classpathRootsResolver.convertClasspathRoots(contentRoots).roots - initialRoots
if (packagePartProviders.isEmpty()) {
initialRoots.addAll(newRoots)
} else {
for (packagePartProvider in packagePartProviders) {
packagePartProvider.addRoots(newRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY))
}
}
return rootsIndex.addNewIndexForRoots(newRoots)?.let { newIndex ->
updateClasspathFromRootsIndex(newIndex)
newIndex.indexedRoots.mapNotNull { (file) ->
VfsUtilCore.virtualToIoFile(VfsUtilCore.getVirtualFileForJar(file) ?: file)
}.toList()
}.orEmpty()
}
private fun contentRootToVirtualFile(root: JvmContentRootBase): VirtualFile? =
when (root) {
is JvmClasspathRoot ->
if (root.file.isFile) findJarRoot(root.file) else findExistingRoot(root, "Classpath entry")
is JvmModulePathRoot ->
if (root.file.isFile) findJarRoot(root.file) else findExistingRoot(root, "Java module root")
is JavaSourceRoot ->
findExistingRoot(root, "Java source root")
is VirtualJvmClasspathRoot -> root.file
else ->
throw IllegalStateException("Unexpected root: $root")
}
fun findLocalFile(path: String): VirtualFile? =
applicationEnvironment.localFileSystem.findFileByPath(path)
private fun findExistingRoot(root: JvmContentRoot, rootDescription: String): VirtualFile? {
return findLocalFile(root.file.absolutePath).also {
if (it == null) {
report(STRONG_WARNING, "$rootDescription points to a non-existent location: ${root.file}")
}
}
}
private fun findJarRoot(file: File): VirtualFile? =
projectEnvironment.jarFileSystem.findFileByPath("$file${URLUtil.JAR_SEPARATOR}")
private fun getSourceRootsCheckingForDuplicates(): List<KotlinSourceRoot> {
val uniqueSourceRoots = hashSetOf<String>()
val result = mutableListOf<KotlinSourceRoot>()
for (root in configuration.kotlinSourceRoots) {
if (!uniqueSourceRoots.add(root.path)) {
report(STRONG_WARNING, "Duplicate source root: ${root.path}")
}
result.add(root)
}
return result
}
fun getSourceFiles(): List<KtFile> =
ProcessSourcesBeforeCompilingExtension.getInstances(project)
.fold(sourceFiles as Collection<KtFile>) { files, extension ->
extension.processSources(files, configuration)
}.toList()
internal fun report(severity: CompilerMessageSeverity, message: String) = configuration.report(severity, message)
companion object {
private val LOG = Logger.getInstance(KotlinCoreEnvironment::class.java)
@PublishedApi
internal val APPLICATION_LOCK = Object()
private var ourApplicationEnvironment: KotlinCoreApplicationEnvironment? = null
private var ourProjectCount = 0
inline fun <R> underApplicationLock(action: () -> R): R =
synchronized(APPLICATION_LOCK) { action() }
@JvmStatic
fun createForProduction(
parentDisposable: Disposable, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles
): KotlinCoreEnvironment {
setupIdeaStandaloneExecution()
val appEnv = getOrCreateApplicationEnvironmentForProduction(parentDisposable, configuration)
val projectEnv = ProjectEnvironment(parentDisposable, appEnv, configuration)
val environment = KotlinCoreEnvironment(projectEnv, configuration, configFiles)
return environment
}
@JvmStatic
fun createForProduction(
projectEnvironment: ProjectEnvironment, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles
): KotlinCoreEnvironment {
return KotlinCoreEnvironment(projectEnvironment, configuration, configFiles)
}
@TestOnly
@JvmStatic
fun createForTests(
parentDisposable: Disposable, initialConfiguration: CompilerConfiguration, extensionConfigs: EnvironmentConfigFiles
): KotlinCoreEnvironment {
val configuration = initialConfiguration.copy()
// Tests are supposed to create a single project and dispose it right after use
val appEnv =
createApplicationEnvironment(
parentDisposable, configuration, unitTestMode = true
)
val projectEnv = ProjectEnvironment(parentDisposable, appEnv, configuration)
return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs)
}
@TestOnly
@JvmStatic
fun createForTests(
projectEnvironment: ProjectEnvironment, initialConfiguration: CompilerConfiguration, extensionConfigs: EnvironmentConfigFiles
): KotlinCoreEnvironment {
return KotlinCoreEnvironment(projectEnvironment, initialConfiguration, extensionConfigs)
}
@TestOnly
fun createProjectEnvironmentForTests(parentDisposable: Disposable, configuration: CompilerConfiguration): ProjectEnvironment {
val appEnv = createApplicationEnvironment(
parentDisposable,
configuration,
unitTestMode = true
)
return ProjectEnvironment(parentDisposable, appEnv, configuration)
}
// used in the daemon for jar cache cleanup
val applicationEnvironment: KotlinCoreApplicationEnvironment? get() = ourApplicationEnvironment
fun getOrCreateApplicationEnvironmentForProduction(
parentDisposable: Disposable, configuration: CompilerConfiguration
): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(parentDisposable, configuration, unitTestMode = false)
fun getOrCreateApplicationEnvironmentForTests(
parentDisposable: Disposable, configuration: CompilerConfiguration
): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(parentDisposable, configuration, unitTestMode = true)
private fun getOrCreateApplicationEnvironment(
parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean
): KotlinCoreApplicationEnvironment {
synchronized(APPLICATION_LOCK) {
if (ourApplicationEnvironment == null) {
val disposable = Disposer.newDisposable("Disposable for the KotlinCoreApplicationEnvironment")
ourApplicationEnvironment =
createApplicationEnvironment(
disposable,
configuration,
unitTestMode
)
ourProjectCount = 0
Disposer.register(disposable, Disposable {
synchronized(APPLICATION_LOCK) {
ourApplicationEnvironment = null
}
})
}
try {
val disposeAppEnv =
CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.value.toBooleanLenient() != true
// Disposer uses identity of passed object to deduplicate registered disposables
// We should everytime pass new instance to avoid un-registering from previous one
@Suppress("ObjectLiteralToLambda")
Disposer.register(parentDisposable, object : Disposable {
override fun dispose() {
synchronized(APPLICATION_LOCK) {
// Build-systems may run many instances of the compiler in parallel
// All projects share the same ApplicationEnvironment, and when the last project is disposed,
// the ApplicationEnvironment is disposed as well
if (--ourProjectCount <= 0) {
// Do not use this property unless you sure need it, causes Application to MEMORY LEAK
// Only valid use-case is when Application should be cached to avoid
// initialization costs
if (disposeAppEnv) {
disposeApplicationEnvironment()
} else {
ourApplicationEnvironment?.idleCleanup()
}
}
}
}
})
} finally {
ourProjectCount++
}
return ourApplicationEnvironment!!
}
}
/**
* This method is also used in Gradle after configuration phase finished.
*/
@JvmStatic
fun disposeApplicationEnvironment() {
synchronized(APPLICATION_LOCK) {
val environment = ourApplicationEnvironment ?: return
ourApplicationEnvironment = null
Disposer.dispose(environment.parentDisposable)
ZipHandler.clearFileAccessorCache()
}
}
@JvmStatic
fun ProjectEnvironment.configureProjectEnvironment(
configuration: CompilerConfiguration,
configFiles: EnvironmentConfigFiles
) {
PersistentFSConstants::class.java.getDeclaredField("ourMaxIntellisenseFileSize")
.apply { isAccessible = true }
.setInt(null, FileUtilRt.LARGE_FOR_CONTENT_LOADING)
registerExtensionsFromPlugins(configuration)
// otherwise consider that project environment is properly configured before passing to the environment
// TODO: consider some asserts to check important extension points
val isJvm = configFiles == EnvironmentConfigFiles.JVM_CONFIG_FILES
project.registerService(ModuleVisibilityManager::class.java, CliModuleVisibilityManagerImpl(isJvm))
registerProjectServicesForCLI(this)
registerProjectServices(project)
for (extension in CompilerConfigurationExtension.getInstances(project)) {
extension.updateConfiguration(configuration)
}
}
private fun createApplicationEnvironment(
parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean
): KotlinCoreApplicationEnvironment {
val applicationEnvironment = KotlinCoreApplicationEnvironment.create(
parentDisposable, unitTestMode
)
registerApplicationExtensionPointsAndExtensionsFrom(configuration, "extensions/compiler.xml")
registerApplicationServicesForCLI(applicationEnvironment)
registerApplicationServices(applicationEnvironment)
return applicationEnvironment
}
private fun registerApplicationExtensionPointsAndExtensionsFrom(configuration: CompilerConfiguration, configFilePath: String) {
fun File.hasConfigFile(configFile: String): Boolean =
if (isDirectory) File(this, "META-INF" + File.separator + configFile).exists()
else try {
ZipFile(this).use {
it.getEntry("META-INF/$configFile") != null
}
} catch (e: Throwable) {
false
}
val pluginRoot: File =
configuration.get(CLIConfigurationKeys.INTELLIJ_PLUGIN_ROOT)?.let(::File)
?: PathUtil.getResourcePathForClass(this::class.java).takeIf { it.hasConfigFile(configFilePath) }
// hack for load extensions when compiler run directly from project directory (e.g. in tests)
?: File("compiler/cli/cli-common/resources").takeIf { it.hasConfigFile(configFilePath) }
?: configuration.get(CLIConfigurationKeys.PATH_TO_KOTLIN_COMPILER_JAR)?.takeIf { it.hasConfigFile(configFilePath) }
?: throw IllegalStateException(
"Unable to find extension point configuration $configFilePath " +
"(cp:\n ${(Thread.currentThread().contextClassLoader as? UrlClassLoader)?.urls?.joinToString("\n ") { it.file }})"
)
CoreApplicationEnvironment.registerExtensionPointAndExtensions(
FileSystems.getDefault().getPath(pluginRoot.path),
configFilePath,
ApplicationManager.getApplication().extensionArea
)
}
@JvmStatic
@OptIn(InternalNonStableExtensionPoints::class)
@Suppress("MemberVisibilityCanPrivate") // made public for CLI Android Lint
fun registerPluginExtensionPoints(project: MockProject) {
ExpressionCodegenExtension.registerExtensionPoint(project)
SyntheticResolveExtension.registerExtensionPoint(project)
SyntheticJavaResolveExtension.registerExtensionPoint(project)
@Suppress("DEPRECATION_ERROR")
org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension.registerExtensionPoint(project)
ClassGeneratorExtension.registerExtensionPoint(project)
ClassFileFactoryFinalizerExtension.registerExtensionPoint(project)
AnalysisHandlerExtension.registerExtensionPoint(project)
PackageFragmentProviderExtension.registerExtensionPoint(project)
StorageComponentContainerContributor.registerExtensionPoint(project)
DeclarationAttributeAltererExtension.registerExtensionPoint(project)
PreprocessedVirtualFileFactoryExtension.registerExtensionPoint(project)
JsSyntheticTranslateExtension.registerExtensionPoint(project)
CompilerConfigurationExtension.registerExtensionPoint(project)
CollectAdditionalSourcesExtension.registerExtensionPoint(project)
ProcessSourcesBeforeCompilingExtension.registerExtensionPoint(project)
ExtraImportsProviderExtension.registerExtensionPoint(project)
IrGenerationExtension.registerExtensionPoint(project)
ScriptEvaluationExtension.registerExtensionPoint(project)
ShellExtension.registerExtensionPoint(project)
TypeResolutionInterceptor.registerExtensionPoint(project)
CandidateInterceptor.registerExtensionPoint(project)
DescriptorSerializerPlugin.registerExtensionPoint(project)
FirExtensionRegistrarAdapter.registerExtensionPoint(project)
TypeAttributeTranslatorExtension.registerExtensionPoint(project)
AssignResolutionAltererExtension.registerExtensionPoint(project)
}
internal fun registerExtensionsFromPlugins(project: MockProject, configuration: CompilerConfiguration) {
fun createErrorMessage(extension: Any): String {
return "The provided plugin ${extension.javaClass.name} is not compatible with this version of compiler"
}
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) {
try {
registrar.registerProjectComponents(project, configuration)
} catch (e: AbstractMethodError) {
val message = createErrorMessage(registrar)
// Since the scripting plugin is often discovered in the compiler environment, it is often taken from the incompatible
// location, and in many cases this is not a fatal error, therefore strong warning is generated instead of exception
if (registrar.javaClass.simpleName == "ScriptingCompilerConfigurationComponentRegistrar") {
messageCollector?.report(STRONG_WARNING, "Default scripting plugin is disabled: $message")
} else {
val errorMessageWithStackTrace = "$message.\n" +
e.stackTraceToString().lines().take(6).joinToString("\n")
messageCollector?.report(ERROR, errorMessageWithStackTrace)
}
}
}
val extensionStorage = CompilerPluginRegistrar.ExtensionStorage()
for (registrar in configuration.getList(CompilerPluginRegistrar.COMPILER_PLUGIN_REGISTRARS)) {
with(registrar) { extensionStorage.registerExtensions(configuration) }
}
extensionStorage.registerInProject(project) { createErrorMessage(it) }
}
private fun registerApplicationServicesForCLI(applicationEnvironment: KotlinCoreApplicationEnvironment) {
// ability to get text from annotations xml files
applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml")
applicationEnvironment.registerParserDefinition(JavaParserDefinition())
}
// made public for Upsource
@Suppress("MemberVisibilityCanBePrivate")
@JvmStatic
fun registerApplicationServices(applicationEnvironment: KotlinCoreApplicationEnvironment) {
with(applicationEnvironment) {
registerFileType(KotlinFileType.INSTANCE, "kt")
registerFileType(KotlinFileType.INSTANCE, KotlinParserDefinition.STD_SCRIPT_SUFFIX)
registerParserDefinition(KotlinParserDefinition())
application.registerService(KotlinBinaryClassCache::class.java, KotlinBinaryClassCache())
application.registerService(JavaClassSupers::class.java, JavaClassSupersImpl::class.java)
application.registerService(TransactionGuard::class.java, TransactionGuardImpl::class.java)
}
}
@JvmStatic
fun registerProjectExtensionPoints(area: ExtensionsArea) {
CoreApplicationEnvironment.registerExtensionPoint(
area, PsiTreeChangePreprocessor.EP.name, PsiTreeChangePreprocessor::class.java
)
CoreApplicationEnvironment.registerExtensionPoint(area, PsiElementFinder.EP.name, PsiElementFinder::class.java)
IdeaExtensionPoints.registerVersionSpecificProjectExtensionPoints(area)
}
// made public for Upsource
@JvmStatic
@Deprecated("Use registerProjectServices(project) instead.", ReplaceWith("registerProjectServices(projectEnvironment.project)"))
fun registerProjectServices(
projectEnvironment: JavaCoreProjectEnvironment,
@Suppress("UNUSED_PARAMETER") messageCollector: MessageCollector?
) {
registerProjectServices(projectEnvironment.project)
}
// made public for Android Lint
@JvmStatic
fun registerProjectServices(project: MockProject) {
with(project) {
registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this))
registerService(ModuleAnnotationsResolver::class.java, CliModuleAnnotationsResolver())
}
}
fun registerProjectServicesForCLI(@Suppress("UNUSED_PARAMETER") projectEnvironment: JavaCoreProjectEnvironment) {
/**
* Note that Kapt may restart code analysis process, and CLI services should be aware of that.
* Use PsiManager.getModificationTracker() to ensure that all the data you cached is still valid.
*/
}
// made public for Android Lint
@JvmStatic
fun registerKotlinLightClassSupport(project: MockProject) {
with(project) {
val traceHolder = CliTraceHolder()
val cliLightClassGenerationSupport = CliLightClassGenerationSupport(traceHolder, project)
val kotlinAsJavaSupport = CliKotlinAsJavaSupport(project, traceHolder)
registerService(LightClassGenerationSupport::class.java, cliLightClassGenerationSupport)
registerService(CliLightClassGenerationSupport::class.java, cliLightClassGenerationSupport)
registerService(KotlinAsJavaSupport::class.java, kotlinAsJavaSupport)
registerService(CodeAnalyzerInitializer::class.java, traceHolder)
// We don't pass Disposable because in some tests, we manually unregister these extensions, and that leads to LOG.error
// exception from `ExtensionPointImpl.doRegisterExtension`, because the registered extension can no longer be found
// when the project is being disposed.
// For example, see the `unregisterExtension` call in `GenerationUtils.compileFilesUsingFrontendIR`.
// TODO: refactor this to avoid registering unneeded extensions in the first place, and avoid using deprecated API.
@Suppress("DEPRECATION")
PsiElementFinder.EP.getPoint(project).registerExtension(JavaElementFinder(this))
@Suppress("DEPRECATION")
PsiElementFinder.EP.getPoint(project).registerExtension(PsiElementFinderImpl(this))
}
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.core.JavaCoreProjectEnvironment
import com.intellij.openapi.Disposable
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
open class KotlinCoreProjectEnvironment(
disposable: Disposable,
applicationEnvironment: KotlinCoreApplicationEnvironment
) : JavaCoreProjectEnvironment(disposable, applicationEnvironment) {
override fun createCoreFileManager(): KotlinCliJavaFileManager = KotlinCliJavaFileManagerImpl(PsiManager.getInstance(project))
}
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.codeInsight.ExternalAnnotationsManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
class MockExternalAnnotationsManager : ExternalAnnotationsManager() {
override fun chooseAnnotationsPlace(element: PsiElement): AnnotationPlace = AnnotationPlace.NOWHERE
override fun chooseAnnotationsPlaceNoUi(element: PsiElement): AnnotationPlace = AnnotationPlace.NOWHERE
override fun isExternalAnnotationWritable(listOwner: PsiModifierListOwner, annotationFQN: String): Boolean = false
override fun isExternalAnnotation(annotation: PsiAnnotation): Boolean = false
override fun findExternalAnnotationsFiles(listOwner: PsiModifierListOwner): List<PsiFile>? = null
override fun findExternalAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? = null
override fun findExternalAnnotations(listOwner: PsiModifierListOwner): Array<out PsiAnnotation>? = null
override fun annotateExternally(
listOwner: PsiModifierListOwner,
annotationFQName: String,
fromFile: PsiFile,
value: Array<out PsiNameValuePair>?
) {
throw UnsupportedOperationException("not implemented")
}
override fun deannotate(listOwner: PsiModifierListOwner, annotationFQN: String): Boolean {
throw UnsupportedOperationException("not implemented")
}
override fun editExternalAnnotation(
listOwner: PsiModifierListOwner,
annotationFQN: String,
value: Array<out PsiNameValuePair>?
): Boolean {
throw UnsupportedOperationException("not implemented")
}
override fun hasAnnotationRootsForFile(file: VirtualFile): Boolean = false
override fun findDefaultConstructorExternalAnnotations(aClass: PsiClass, annotationFQN: String): List<PsiAnnotation> = emptyList()
override fun findDefaultConstructorExternalAnnotations(aClass: PsiClass): List<PsiAnnotation> = emptyList()
override fun findExternalAnnotations(listOwner: PsiModifierListOwner, annotationFQN: String): List<PsiAnnotation> = emptyList()
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.codeInsight.InferredAnnotationsManager
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiModifierListOwner
class MockInferredAnnotationsManager : InferredAnnotationsManager() {
override fun findInferredAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? = null
override fun findInferredAnnotations(listOwner: PsiModifierListOwner): Array<out PsiAnnotation> = EMPTY_PSI_ANNOTATION_ARRAY
override fun isInferredAnnotation(annotation: PsiAnnotation): Boolean = false
companion object {
val EMPTY_PSI_ANNOTATION_ARRAY = arrayOf<PsiAnnotation>()
}
}
@@ -0,0 +1,366 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns
import org.jetbrains.kotlin.builtins.jvm.JvmBuiltInsPackageFragmentProvider
import org.jetbrains.kotlin.cli.jvm.config.ClassicFrontendSpecificJvmConfigurationKeys
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.container.useImpl
import org.jetbrains.kotlin.context.ContextForNewModule
import org.jetbrains.kotlin.context.ModuleContext
import org.jetbrains.kotlin.context.MutableModuleContext
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleCapability
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.frontend.java.di.createContainerForLazyResolveWithJava
import org.jetbrains.kotlin.frontend.java.di.initJvmBuiltInsForTopDownAnalysis
import org.jetbrains.kotlin.frontend.java.di.initialize
import org.jetbrains.kotlin.incremental.components.EnumWhenTracker
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.InlineConstTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.ir.backend.jvm.jvmLibrariesProvidedByDefault
import org.jetbrains.kotlin.javac.components.JavacBasedClassFinder
import org.jetbrains.kotlin.javac.components.JavacBasedSourceElementFactory
import org.jetbrains.kotlin.javac.components.StubJavaResolverCache
import org.jetbrains.kotlin.konan.properties.propertyList
import org.jetbrains.kotlin.library.KLIB_PROPERTY_DEPENDS
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.metadata.KlibMetadataFactories
import org.jetbrains.kotlin.library.metadata.NullFlexibleTypeDeserializer
import org.jetbrains.kotlin.load.java.JavaClassesTracker
import org.jetbrains.kotlin.load.java.lazy.ModuleClassResolver
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.impl.VirtualFileBoundJavaClass
import org.jetbrains.kotlin.load.kotlin.DeserializationComponentsForJava
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
import org.jetbrains.kotlin.load.kotlin.incremental.IncrementalPackageFragmentProvider
import org.jetbrains.kotlin.load.kotlin.incremental.IncrementalPackagePartProvider
import org.jetbrains.kotlin.modules.TargetId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.tower.ImplicitsExtensionsResolutionFilter
import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.resolve.jvm.multiplatform.OptionalAnnotationPackageFragmentProvider
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.storage.StorageManager
import kotlin.reflect.KFunction1
object TopDownAnalyzerFacadeForJVM {
@JvmStatic
@JvmOverloads
fun analyzeFilesWithJavaIntegration(
project: Project,
files: Collection<KtFile>,
trace: BindingTrace,
configuration: CompilerConfiguration,
packagePartProvider: (GlobalSearchScope) -> PackagePartProvider,
declarationProviderFactory: (StorageManager, Collection<KtFile>) -> DeclarationProviderFactory = ::FileBasedDeclarationProviderFactory,
sourceModuleSearchScope: GlobalSearchScope = newModuleSearchScope(project, files),
klibList: List<KotlinLibrary> = emptyList(),
explicitModuleDependencyList: List<ModuleDescriptorImpl> = emptyList(),
explicitModuleFriendsList: List<ModuleDescriptorImpl> = emptyList(),
explicitCompilerEnvironment: TargetEnvironment = CompilerEnvironment
): AnalysisResult {
val container = createContainer(
project, files, trace, configuration, packagePartProvider, declarationProviderFactory, explicitCompilerEnvironment,
sourceModuleSearchScope, klibList, explicitModuleDependencyList = explicitModuleDependencyList,
explicitModuleFriendsList = explicitModuleFriendsList
)
val module = container.get<ModuleDescriptor>()
val moduleContext = container.get<ModuleContext>()
val analysisHandlerExtensions = AnalysisHandlerExtension.getInstances(project)
fun invokeExtensionsOnAnalysisComplete(): AnalysisResult? {
container.get<JavaClassesTracker>().onCompletedAnalysis(module)
for (extension in analysisHandlerExtensions) {
val result = extension.analysisCompleted(project, module, trace, files)
if (result != null) return result
}
return null
}
for (extension in analysisHandlerExtensions) {
val result = extension.doAnalysis(project, module, moduleContext, files, trace, container)
if (result != null) {
invokeExtensionsOnAnalysisComplete()?.let { return it }
return result
}
}
container.get<LazyTopDownAnalyzer>().analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, files)
invokeExtensionsOnAnalysisComplete()?.let { return it }
return AnalysisResult.success(trace.bindingContext, module)
}
fun createContainer(
project: Project,
files: Collection<KtFile>,
trace: BindingTrace,
configuration: CompilerConfiguration,
packagePartProvider: (GlobalSearchScope) -> PackagePartProvider,
declarationProviderFactory: (StorageManager, Collection<KtFile>) -> DeclarationProviderFactory,
targetEnvironment: TargetEnvironment = CompilerEnvironment,
sourceModuleSearchScope: GlobalSearchScope = newModuleSearchScope(project, files),
klibList: List<KotlinLibrary> = emptyList(),
implicitsResolutionFilter: ImplicitsExtensionsResolutionFilter? = null,
explicitModuleDependencyList: List<ModuleDescriptorImpl> = emptyList(),
explicitModuleFriendsList: List<ModuleDescriptorImpl> = emptyList(),
moduleCapabilities: Map<ModuleCapability<*>, Any?> = emptyMap()
): ComponentProvider {
val jvmTarget = configuration.get(JVMConfigurationKeys.JVM_TARGET, JvmTarget.DEFAULT)
val languageVersionSettings = configuration.languageVersionSettings
val jvmPlatform = JvmPlatforms.jvmPlatformByTargetVersion(jvmTarget)
val moduleContext = createModuleContext(project, configuration, jvmPlatform, moduleCapabilities)
val storageManager = moduleContext.storageManager
val module = moduleContext.module
val incrementalComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS)
val lookupTracker = configuration.get(CommonConfigurationKeys.LOOKUP_TRACKER) ?: LookupTracker.DO_NOTHING
val expectActualTracker = configuration.get(CommonConfigurationKeys.EXPECT_ACTUAL_TRACKER) ?: ExpectActualTracker.DoNothing
val inlineConstTracker = configuration.get(CommonConfigurationKeys.INLINE_CONST_TRACKER) ?: InlineConstTracker.DoNothing
val enumWhenTracker = configuration.get(CommonConfigurationKeys.ENUM_WHEN_TRACKER) ?: EnumWhenTracker.DoNothing
val targetIds = configuration.get(JVMConfigurationKeys.MODULES)?.map(::TargetId)
val moduleClassResolver = SourceOrBinaryModuleClassResolver(sourceModuleSearchScope)
val fallbackBuiltIns = JvmBuiltIns(storageManager, JvmBuiltIns.Kind.FALLBACK).apply {
initialize(builtInsModule, languageVersionSettings)
}.builtInsModule
fun StorageComponentContainer.useJavac() {
useImpl<JavacBasedClassFinder>()
useImpl<StubJavaResolverCache>()
useImpl<JavacBasedSourceElementFactory>()
}
@Suppress("USELESS_CAST")
val configureJavaClassFinder =
if (configuration.getBoolean(JVMConfigurationKeys.USE_JAVAC)) StorageComponentContainer::useJavac
else null as KFunction1<StorageComponentContainer, Unit>?
val dependencyModule = run {
val dependenciesContext = ContextForNewModule(
moduleContext, Name.special("<dependencies of ${configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME)}>"),
module.builtIns, jvmPlatform
)
// Scope for the dependency module contains everything except files present in the scope for the source module
val dependencyScope = GlobalSearchScope.notScope(sourceModuleSearchScope)
val dependenciesContainer = createContainerForLazyResolveWithJava(
jvmPlatform,
dependenciesContext, trace, DeclarationProviderFactory.EMPTY, dependencyScope, moduleClassResolver,
targetEnvironment, lookupTracker, expectActualTracker, inlineConstTracker, enumWhenTracker,
packagePartProvider(dependencyScope), languageVersionSettings,
useBuiltInsProvider = true,
configureJavaClassFinder = configureJavaClassFinder,
implicitsResolutionFilter = implicitsResolutionFilter
)
moduleClassResolver.compiledCodeResolver = dependenciesContainer.get()
dependenciesContext.setDependencies(listOf(dependenciesContext.module, fallbackBuiltIns))
dependenciesContext.initializeModuleContents(
CompositePackageFragmentProvider(
listOf(
moduleClassResolver.compiledCodeResolver.packageFragmentProvider,
dependenciesContainer.get<JvmBuiltInsPackageFragmentProvider>(),
dependenciesContainer.get<OptionalAnnotationPackageFragmentProvider>()
),
"CompositeProvider@TopDownAnalyzerForJvm for dependencies ${dependenciesContext.module}"
)
)
dependenciesContext.module
}
val partProvider = packagePartProvider(sourceModuleSearchScope).let { fragment ->
if (targetIds == null || incrementalComponents == null) fragment
else IncrementalPackagePartProvider(fragment, targetIds.map(incrementalComponents::getIncrementalCache))
}
// Note that it's necessary to create container for sources _after_ creation of container for dependencies because
// CliLightClassGenerationSupport#initialize is invoked when container is created, so only the last module descriptor is going
// to be stored in CliLightClassGenerationSupport, and it better be the source one (otherwise light classes would not be found)
// TODO: get rid of duplicate invocation of CodeAnalyzerInitializer#initialize, or refactor CliLightClassGenerationSupport
val container = createContainerForLazyResolveWithJava(
jvmPlatform,
moduleContext, trace, declarationProviderFactory(storageManager, files), sourceModuleSearchScope, moduleClassResolver,
targetEnvironment, lookupTracker, expectActualTracker, inlineConstTracker, enumWhenTracker,
partProvider, languageVersionSettings,
useBuiltInsProvider = true,
configureJavaClassFinder = configureJavaClassFinder,
javaClassTracker = configuration[ClassicFrontendSpecificJvmConfigurationKeys.JAVA_CLASSES_TRACKER],
implicitsResolutionFilter = implicitsResolutionFilter
).apply {
initJvmBuiltInsForTopDownAnalysis()
(partProvider as? IncrementalPackagePartProvider)?.deserializationConfiguration = get()
}
moduleClassResolver.sourceCodeResolver = container.get()
val additionalProviders = ArrayList<PackageFragmentProvider>()
if (incrementalComponents != null) {
targetIds?.mapTo(additionalProviders) { targetId ->
IncrementalPackageFragmentProvider(
files, module, storageManager, container.get<DeserializationComponentsForJava>().components,
incrementalComponents.getIncrementalCache(targetId), targetId, container.get()
)
}
}
additionalProviders.add(container.get<JavaDescriptorResolver>().packageFragmentProvider)
// TODO: consider putting extension package fragment providers into the dependency module
PackageFragmentProviderExtension.getInstances(project).mapNotNullTo(additionalProviders) { extension ->
extension.getPackageFragmentProvider(project, module, storageManager, trace, null, lookupTracker)
}
val klibModules = getKlibModules(klibList, dependencyModule)
// TODO: remove dependencyModule from friends
module.setDependencies(
listOf(module, dependencyModule, fallbackBuiltIns) + klibModules + explicitModuleDependencyList,
setOf(dependencyModule) + explicitModuleFriendsList,
)
module.initialize(
CompositePackageFragmentProvider(
listOf(
container.get<KotlinCodeAnalyzer>().packageFragmentProvider,
container.get<OptionalAnnotationPackageFragmentProvider>()
) + additionalProviders,
"CompositeProvider@TopDownAnalzyerForJvm for $module"
)
)
return container
}
fun newModuleSearchScope(project: Project, files: Collection<KtFile>): GlobalSearchScope {
// In case of separate modules, the source module scope generally consists of the following scopes:
// 1) scope which only contains passed Kotlin source files (.kt and .kts)
// 2) scope which contains all Java source files (.java) in the project
return GlobalSearchScope.filesScope(project, files.map { it.virtualFile }.toSet()).uniteWith(AllJavaSourcesInProjectScope(project))
}
// TODO: limit this scope to the Java source roots, which the module has in its CONTENT_ROOTS
class AllJavaSourcesInProjectScope(project: Project) : DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) {
// 'isDirectory' check is needed because otherwise directories such as 'frontend.java' would be recognized
// as Java source files, which makes no sense
override fun contains(file: VirtualFile) =
(file.extension == JavaFileType.DEFAULT_EXTENSION || file.fileType === JavaFileType.INSTANCE) && !file.isDirectory
override fun toString() = "All Java sources in the project"
}
class SourceOrBinaryModuleClassResolver(private val sourceScope: GlobalSearchScope) : ModuleClassResolver {
lateinit var compiledCodeResolver: JavaDescriptorResolver
lateinit var sourceCodeResolver: JavaDescriptorResolver
override fun resolveClass(javaClass: JavaClass): ClassDescriptor? {
val resolver = if (javaClass is VirtualFileBoundJavaClass && javaClass.isFromSourceCodeInScope(sourceScope))
sourceCodeResolver
else
compiledCodeResolver
return resolver.resolveClass(javaClass)
}
}
private fun createModuleContext(
project: Project,
configuration: CompilerConfiguration,
platform: TargetPlatform?,
capabilities: Map<ModuleCapability<*>, Any?> = emptyMap()
): MutableModuleContext {
val projectContext = ProjectContext(project, "TopDownAnalyzer for JVM")
val builtIns = JvmBuiltIns(projectContext.storageManager, JvmBuiltIns.Kind.FROM_DEPENDENCIES)
return ContextForNewModule(
projectContext, Name.special("<${configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME)}>"),
builtIns, platform, capabilities
).apply {
builtIns.builtInsModule = module
}
}
}
// From serialization.js....klib.kt
val jvmFactories = KlibMetadataFactories(
{ storageManager -> JvmBuiltIns(storageManager, JvmBuiltIns.Kind.FROM_DEPENDENCIES) },
NullFlexibleTypeDeserializer
)
private fun getKlibModules(klibList: List<KotlinLibrary>, dependencyModule: ModuleDescriptorImpl?): List<ModuleDescriptorImpl> {
val descriptorMap = mutableMapOf<String, ModuleDescriptorImpl>()
return klibList.map { library ->
descriptorMap.getOrPut(library.libraryName) { getModuleDescriptorByLibrary(library, descriptorMap, dependencyModule) }
}
}
private fun getModuleDescriptorByLibrary(
current: KotlinLibrary, mapping:
Map<String, ModuleDescriptorImpl>,
dependencyModule: ModuleDescriptorImpl?
): ModuleDescriptorImpl {
val module = jvmFactories.DefaultDeserializedDescriptorFactory.createDescriptorOptionalBuiltIns(
current,
LanguageVersionSettingsImpl.DEFAULT,
LockBasedStorageManager.NO_LOCKS,
null,
packageAccessHandler = null, // TODO: This is a speed optimization used by Native. Don't bother for now.
lookupTracker = LookupTracker.DO_NOTHING
)
val dependencies = current.manifestProperties.propertyList(KLIB_PROPERTY_DEPENDS, escapeInQuotes = true).mapNotNull {
mapping[it] ?: run {
assert(it in jvmLibrariesProvidedByDefault) { "Unknown library $it" }
null
}
}
module.setDependencies(listOf(module) + dependencies + listOfNotNull(dependencyModule))
return module
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.builder
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.stubs.StubElement
import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
class KotlinLightClassBuilderFactory(private val javaFileStub: PsiJavaFileStub) : ClassBuilderFactory {
private val stubStack = Stack<StubElement<PsiElement>>().apply {
@Suppress("UNCHECKED_CAST")
push(javaFileStub as StubElement<PsiElement>)
}
override fun getClassBuilderMode(): ClassBuilderMode = ClassBuilderMode.LIGHT_CLASSES
override fun newClassBuilder(origin: JvmDeclarationOrigin) =
StubClassBuilder(stubStack, javaFileStub)
override fun asText(builder: ClassBuilder) = throw UnsupportedOperationException("asText is not implemented")
override fun asBytes(builder: ClassBuilder) = throw UnsupportedOperationException("asBytes is not implemented")
override fun close() {}
fun result(): PsiJavaFileStub {
val pop = stubStack.pop()
if (pop !== javaFileStub) {
LOG.error("Unbalanced stack operations: $pop")
}
return javaFileStub
}
}
private val LOG = Logger.getInstance(KotlinLightClassBuilderFactory::class.java)
@@ -0,0 +1,90 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.builder
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
data class CodeGenerationResult(val stub: PsiJavaFileStub, val bindingContext: BindingContext, val diagnostics: Diagnostics)
fun extraJvmDiagnosticsFromBackend(
packageFqName: FqName,
files: Collection<KtFile>,
generateClassFilter: GenerationState.GenerateClassFilter,
context: LightClassConstructionContext,
generate: (state: GenerationState, files: Collection<KtFile>) -> Unit,
): CodeGenerationResult {
val project = files.first().project
try {
val classBuilderFactory = KotlinLightClassBuilderFactory(createJavaFileStub(packageFqName, files))
val state = GenerationState.Builder(
project,
classBuilderFactory,
context.module,
context.bindingContext,
context.languageVersionSettings?.let {
CompilerConfiguration().apply {
languageVersionSettings = it
put(JVMConfigurationKeys.JVM_TARGET, context.jvmTarget)
isReadOnly = true
}
} ?: CompilerConfiguration.EMPTY,
).generateDeclaredClassFilter(generateClassFilter).wantsDiagnostics(false).build()
state.beforeCompile()
state.oldBEInitTrace(files)
generate(state, files)
val javaFileStub = classBuilderFactory.result()
return CodeGenerationResult(javaFileStub, context.bindingContext, state.collectedExtraJvmDiagnostics)
} catch (e: ProcessCanceledException) {
throw e
} catch (e: RuntimeException) {
logErrorWithOSInfo(e, packageFqName, files.firstOrNull()?.virtualFile)
throw e
}
}
private fun createJavaFileStub(packageFqName: FqName, files: Collection<KtFile>): PsiJavaFileStub {
val javaFileStub = PsiJavaFileStubImpl(packageFqName.asString(), /* compiled = */true)
javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE
val fakeFile = object : ClsFileImpl(files.first().viewProvider) {
override fun getStub() = javaFileStub
override fun getPackageName() = packageFqName.asString()
override fun isPhysical() = false
override fun getText(): String = files.singleOrNull()?.text ?: super.getText()
}
javaFileStub.psi = fakeFile
return javaFileStub
}
private fun logErrorWithOSInfo(cause: Throwable?, fqName: FqName, virtualFile: VirtualFile?) {
val path = virtualFile?.path ?: "<null>"
LOG.error(
"Could not generate LightClass for $fqName declared in $path\n" +
"System: ${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION} Java Runtime: ${SystemInfo.JAVA_RUNTIME_VERSION}",
cause,
)
}
private val LOG = Logger.getInstance(CodeGenerationResult::class.java)
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.builder
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.resolve.BindingContext
open class LightClassConstructionContext(
val bindingContext: BindingContext,
val module: ModuleDescriptor,
val languageVersionSettings: LanguageVersionSettings?,
val jvmTarget: JvmTarget,
)
@@ -0,0 +1,203 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.builder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.compiled.InnerClassSourceStrategy;
import com.intellij.psi.impl.compiled.StubBuildingVisitor;
import com.intellij.psi.impl.java.stubs.PsiClassStub;
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
import com.intellij.psi.stubs.StubBase;
import com.intellij.psi.stubs.StubElement;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory;
import org.jetbrains.kotlin.asJava.builder.LightElementOrigin;
import org.jetbrains.kotlin.asJava.builder.LightElementOriginKt;
import org.jetbrains.kotlin.codegen.AbstractClassBuilder;
import org.jetbrains.kotlin.fileClasses.OldPackageFacadeClassUtils;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.org.objectweb.asm.ClassVisitor;
import org.jetbrains.org.objectweb.asm.FieldVisitor;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import java.util.List;
public class StubClassBuilder extends AbstractClassBuilder {
private static final InnerClassSourceStrategy<Object> EMPTY_STRATEGY = new InnerClassSourceStrategy<Object>() {
@Override
public Object findInnerClass(String s, Object o) {
return null;
}
@Override
public void accept(Object innerClass, StubBuildingVisitor<Object> visitor) {
throw new UnsupportedOperationException("Shall not be called!");
}
};
private final StubElement parent;
private final PsiJavaFileStub fileStub;
private StubBuildingVisitor v;
private final Stack<StubElement> parentStack;
private boolean isPackageClass = false;
public StubClassBuilder(@NotNull Stack<StubElement> parentStack, @NotNull PsiJavaFileStub fileStub) {
this.parentStack = parentStack;
this.parent = parentStack.peek();
this.fileStub = fileStub;
}
@NotNull
@Override
public ClassVisitor getVisitor() {
assert v != null : "Called before class is defined";
return v;
}
@Override
public void defineClass(
PsiElement origin,
int version,
int access,
@NotNull String name,
@Nullable String signature,
@NotNull String superName,
@NotNull String[] interfaces
) {
assert v == null : "defineClass() called twice?";
//noinspection ConstantConditions
v = new StubBuildingVisitor<>(null, EMPTY_STRATEGY, parent, access, calculateShortName(name));
super.defineClass(origin, version, access, name, signature, superName, interfaces);
if (origin instanceof KtFile) {
FqName packageName = ((KtFile) origin).getPackageFqName();
String packageClassName = OldPackageFacadeClassUtils.getPackageClassName(packageName);
if (name.equals(packageClassName) || name.endsWith("/" + packageClassName)) {
isPackageClass = true;
}
}
if (!isPackageClass) {
parentStack.push(v.getResult());
}
((StubBase) v.getResult()).putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightClassOrigin(origin));
}
@Nullable
private String calculateShortName(@NotNull String internalName) {
if (parent instanceof PsiJavaFileStub) {
assert parent == fileStub;
String packagePrefix = getPackageInternalNamePrefix();
assert internalName.startsWith(packagePrefix) : internalName + " : " + packagePrefix;
return internalName.substring(packagePrefix.length());
}
if (parent instanceof PsiClassStub<?>) {
String parentPrefix = getClassInternalNamePrefix((PsiClassStub) parent);
if (parentPrefix == null) return null;
assert internalName.startsWith(parentPrefix) : internalName + " : " + parentPrefix;
return internalName.substring(parentPrefix.length());
}
return null;
}
@Nullable
private String getClassInternalNamePrefix(@NotNull PsiClassStub classStub) {
String packageName = fileStub.getPackageName();
String classStubQualifiedName = classStub.getQualifiedName();
if (classStubQualifiedName == null) return null;
if (packageName.isEmpty()) {
return classStubQualifiedName.replace('.', '$') + "$";
}
else {
return packageName.replace('.', '/') + "/" + classStubQualifiedName.substring(packageName.length() + 1).replace('.', '$') + "$";
}
}
@NotNull
private String getPackageInternalNamePrefix() {
String packageName = fileStub.getPackageName();
if (packageName.isEmpty()) {
return "";
}
else {
return packageName.replace('.', '/') + "/";
}
}
@NotNull
@Override
public MethodVisitor newMethod(
@NotNull JvmDeclarationOrigin origin,
int access,
@NotNull String name,
@NotNull String desc,
@Nullable String signature,
@Nullable String[] exceptions
) {
MethodVisitor internalVisitor = super.newMethod(origin, access, name, desc, signature, exceptions);
if (internalVisitor != EMPTY_METHOD_VISITOR) {
// If stub for method generated
markLastChild(origin);
}
return internalVisitor;
}
@NotNull
@Override
public FieldVisitor newField(
@NotNull JvmDeclarationOrigin origin,
int access,
@NotNull String name,
@NotNull String desc,
@Nullable String signature,
@Nullable Object value
) {
FieldVisitor internalVisitor = super.newField(origin, access, name, desc, signature, value);
if (internalVisitor != EMPTY_FIELD_VISITOR) {
// If stub for field generated
markLastChild(origin);
}
return internalVisitor;
}
private void markLastChild(@NotNull JvmDeclarationOrigin origin) {
List children = v.getResult().getChildrenStubs();
StubBase last = (StubBase) children.get(children.size() - 1);
LightElementOrigin oldOrigin = last.getUserData(ClsWrapperStubPsiFactory.ORIGIN);
if (oldOrigin != null) {
PsiElement originalElement = oldOrigin.getOriginalElement();
throw new IllegalStateException("Rewriting origin element: " +
(originalElement != null ? originalElement.getText() : null) + " for stub " + last.toString());
}
last.putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightMemberOrigin(origin));
}
@Override
public void done(boolean generateSmapCopyToAnnotation) {
if (!isPackageClass) {
StubElement pop = parentStack.pop();
assert pop == v.getResult() : "parentStack: got " + pop + ", expected " + v.getResult();
}
super.done(generateSmapCopyToAnnotation);
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
fun JavaModule.getJavaModuleRoots(): List<JavaRoot> =
moduleRoots.map { (root, isBinary, isBinarySignature) ->
val type = when {
isBinarySignature -> JavaRoot.RootType.BINARY_SIG
isBinary -> JavaRoot.RootType.BINARY
else -> JavaRoot.RootType.SOURCE
}
JavaRoot(root, type)
}
/**
* Computes the JDK's default root modules. See [JEP 261: Module System](http://openjdk.java.net/jeps/261).
*/
fun CliJavaModuleFinder.computeDefaultRootModules(): List<String> {
val result = arrayListOf<String>()
val systemModules = systemModules.associateBy(JavaModule::name)
val javaSeExists = "java.se" in systemModules
if (javaSeExists) {
// The java.se module is a root, if it exists.
result.add("java.se")
}
fun JavaModule.Explicit.exportsAtLeastOnePackageUnqualified(): Boolean = moduleInfo.exports.any { it.toModules.isEmpty() }
if (!javaSeExists) {
// If it does not exist then every java.* module on the upgrade module path or among the system modules
// that exports at least one package, without qualification, is a root.
for ((name, module) in systemModules) {
if (name.startsWith("java.") && module.exportsAtLeastOnePackageUnqualified()) {
result.add(name)
}
}
}
for ((name, module) in systemModules) {
// Every non-java.* module on the upgrade module path or among the system modules that exports at least one package,
// without qualification, is also a root.
if (!name.startsWith("java.") && module.exportsAtLeastOnePackageUnqualified()) {
result.add(name)
}
}
return result
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
object IdeaStandaloneExecutionSetup {
private val LOG: Logger = Logger.getInstance(IdeaStandaloneExecutionSetup::class.java)
// Copy-pasted from com.intellij.openapi.util.BuildNumber#FALLBACK_VERSION
private const val FALLBACK_IDEA_BUILD_NUMBER = "999.SNAPSHOT"
fun doSetup() {
checkInHeadlessMode()
System.getProperties().setProperty("project.structure.add.tools.jar.to.new.jdk", "false")
System.getProperties().setProperty("psi.track.invalidation", "true")
System.getProperties().setProperty("psi.incremental.reparse.depth.limit", "1000")
System.getProperties().setProperty("ide.hide.excluded.files", "false")
System.getProperties().setProperty("ast.loading.filter", "false")
System.getProperties().setProperty("idea.ignore.disabled.plugins", "true")
// Setting the build number explicitly avoids the command-line compiler
// reading /tmp/build.txt in an attempt to get a build number from there.
// See intellij platform PluginManagerCore.getBuildNumber.
System.getProperties().setProperty("idea.plugins.compatible.build", FALLBACK_IDEA_BUILD_NUMBER)
}
private fun checkInHeadlessMode() {
// if application is null it means that we are in progress of set-up application environment i.e. we are not in the running IDEA
val application = ApplicationManager.getApplication() ?: return
if (!application.isHeadlessEnvironment) {
LOG.error(Throwable("IdeaStandaloneExecutionSetup should be called only in headless environment"))
}
}
}
// We safe this function to minimize the patch for release branches
fun setupIdeaStandaloneExecution() = IdeaStandaloneExecutionSetup.doSetup()
@@ -0,0 +1,145 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension
import org.jetbrains.kotlin.extensions.PreprocessedFileCreator
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.modules.Module
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.multiplatform.hmppModuleName
import org.jetbrains.kotlin.resolve.multiplatform.isCommonSource
import java.io.File
fun CompilerConfiguration.report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation? = null) {
get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)?.report(severity, message, location)
}
inline fun List<KotlinSourceRoot>.forAllFiles(
configuration: CompilerConfiguration,
project: Project,
reportLocation: CompilerMessageLocation? = null,
body: (VirtualFile, Boolean, moduleName: String?) -> Unit
) {
val localFileSystem = VirtualFileManager.getInstance()
.getFileSystem(StandardFileSystems.FILE_PROTOCOL)
val processedFiles = hashSetOf<VirtualFile>()
val virtualFileCreator = PreprocessedFileCreator(project)
var pluginsConfigured = false
for ((sourceRootPath, isCommon, hmppModuleName) in this) {
val sourceRoot = File(sourceRootPath)
val vFile = localFileSystem.findFileByPath(sourceRoot.normalize().path)
if (vFile == null) {
val message = "Source file or directory not found: $sourceRootPath"
val buildFilePath = configuration.get(JVMConfigurationKeys.MODULE_XML_FILE)
if (buildFilePath != null && Logger.isInitialized()) {
Logger.getInstance(KotlinCoreEnvironment::class.java)
.warn("$message\n\nbuild file path: $buildFilePath\ncontent:\n${buildFilePath.readText()}")
}
configuration.report(CompilerMessageSeverity.ERROR, message, reportLocation)
continue
}
if (!vFile.isDirectory && vFile.extension != KotlinFileType.EXTENSION) {
if (!pluginsConfigured) {
vFile.registerPluginsSuppliedExtensionsIfNeeded(project)
pluginsConfigured = true
}
if (vFile.fileType != KotlinFileType.INSTANCE) {
configuration.report(CompilerMessageSeverity.ERROR, "Source entry is not a Kotlin file: $sourceRootPath", reportLocation)
continue
}
}
for (file in sourceRoot.walkTopDown()) {
if (!file.isFile) continue
val virtualFile = localFileSystem.findFileByPath(file.absoluteFile.normalize().path)?.let(virtualFileCreator::create)
if (virtualFile != null && processedFiles.add(virtualFile)) {
if (!pluginsConfigured) {
virtualFile.registerPluginsSuppliedExtensionsIfNeeded(project)
pluginsConfigured = true
}
body(virtualFile, isCommon, hmppModuleName)
}
}
}
}
fun VirtualFile.registerPluginsSuppliedExtensionsIfNeeded(project: Project) {
if (
extension == null ||
extension == KotlinFileType.EXTENSION ||
extension == JavaFileType.INSTANCE.defaultExtension ||
extension == JavaClassFileType.INSTANCE.defaultExtension ||
fileType == KotlinFileType.INSTANCE
) return
for (extension in CompilerConfigurationExtension.getInstances(project)) {
extension.updateFileRegistry()
}
}
fun createSourceFilesFromSourceRoots(
configuration: CompilerConfiguration,
project: Project,
sourceRoots: List<KotlinSourceRoot>,
reportLocation: CompilerMessageLocation? = null
): MutableList<KtFile> {
val psiManager = PsiManager.getInstance(project)
val result = mutableListOf<KtFile>()
sourceRoots.forAllFiles(configuration, project, reportLocation) { virtualFile, isCommon, moduleName ->
psiManager.findFile(virtualFile)?.let {
if (it is KtFile) {
it.isCommonSource = isCommon
if (moduleName != null) {
it.hmppModuleName = moduleName
}
result.add(it)
}
}
}
return result
}
val KotlinCoreEnvironment.messageCollector: MessageCollector
get() = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
fun CompilerConfiguration.applyModuleProperties(module: Module, buildFile: File?): CompilerConfiguration {
return copy().apply {
if (buildFile != null) {
fun checkKeyIsNull(key: CompilerConfigurationKey<*>, name: String) {
assert(get(key) == null) { "$name should be null, when buildFile is used" }
}
checkKeyIsNull(JVMConfigurationKeys.OUTPUT_DIRECTORY, "OUTPUT_DIRECTORY")
checkKeyIsNull(JVMConfigurationKeys.OUTPUT_JAR, "OUTPUT_JAR")
put(JVMConfigurationKeys.OUTPUT_DIRECTORY, File(module.getOutputDirectory()))
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
class ByteArrayCharSequence(
private val bytes: ByteArray,
private val start: Int = 0,
private val end: Int = bytes.size
) : CharSequence {
override fun hashCode(): Int {
error("Do not try computing hashCode ByteArrayCharSequence")
}
override fun equals(other: Any?): Boolean {
error("Do not try comparing ByteArrayCharSequence")
}
override val length get() = end - start
override fun get(index: Int): Char = bytes[index + start].toChar()
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
if (startIndex == 0 && endIndex == length) return this
return ByteArrayCharSequence(bytes, start + startIndex, start + endIndex)
}
override fun toString(): String {
val chars = CharArray(length)
for (i in 0 until length) {
chars[i] = bytes[i + start].toChar()
}
return String(chars)
}
}
@@ -0,0 +1,119 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
import com.intellij.openapi.util.Couple
import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.io.FileAccessorCache
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
private typealias RandomAccessFileAndBuffer = Pair<RandomAccessFile, MappedByteBuffer>
class FastJarFileSystem private constructor(internal val unmapBuffer: MappedByteBuffer.() -> Unit) : DeprecatedVirtualFileSystem() {
private val myHandlers: MutableMap<String, FastJarHandler> =
ConcurrentFactoryMap.createMap { key: String -> FastJarHandler(this@FastJarFileSystem, key) }
internal val cachedOpenFileHandles: FileAccessorCache<File, RandomAccessFileAndBuffer> =
object : FileAccessorCache<File, RandomAccessFileAndBuffer>(20, 10) {
@Throws(IOException::class)
override fun createAccessor(file: File): RandomAccessFileAndBuffer {
val randomAccessFile = RandomAccessFile(file, "r")
return Pair(randomAccessFile, randomAccessFile.channel.map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length()))
}
@Throws(IOException::class)
override fun disposeAccessor(fileAccessor: RandomAccessFileAndBuffer) {
fileAccessor.first.close()
fileAccessor.second.unmapBuffer()
}
override fun isEqual(val1: File, val2: File): Boolean {
return val1 == val2 // reference equality to handle different jars for different ZipHandlers on the same path
}
}
override fun getProtocol(): String {
return StandardFileSystems.JAR_PROTOCOL
}
override fun findFileByPath(path: String): VirtualFile? {
val pair = splitPath(path)
return myHandlers[pair.first]!!.findFileByPath(pair.second)
}
override fun refresh(asynchronous: Boolean) {}
override fun refreshAndFindFileByPath(path: String): VirtualFile? {
return findFileByPath(path)
}
fun clearHandlersCache() {
myHandlers.clear()
cleanOpenFilesCache()
}
fun cleanOpenFilesCache() {
cachedOpenFileHandles.clear()
}
companion object {
fun splitPath(path: String): Couple<String> {
val separator = path.indexOf("!/")
require(separator >= 0) { "Path in JarFileSystem must contain a separator: $path" }
val localPath = path.substring(0, separator)
val pathInJar = path.substring(separator + 2)
return Couple.of(localPath, pathInJar)
}
fun createIfUnmappingPossible(): FastJarFileSystem? {
val cleanerCallBack = prepareCleanerCallback() ?: return null
return FastJarFileSystem(cleanerCallBack)
}
}
}
private val IS_PRIOR_9_JRE = System.getProperty("java.specification.version", "").startsWith("1.")
private fun prepareCleanerCallback(): ((ByteBuffer) -> Unit)? {
return try {
if (IS_PRIOR_9_JRE) {
val cleaner = Class.forName("java.nio.DirectByteBuffer").getMethod("cleaner")
cleaner.isAccessible = true
val clean = Class.forName("sun.misc.Cleaner").getMethod("clean")
clean.isAccessible = true
{ buffer: ByteBuffer -> clean.invoke(cleaner.invoke(buffer)) }
} else {
val unsafeClass = try {
Class.forName("sun.misc.Unsafe")
} catch (ex: Exception) {
// jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method,
// but that method should be added if sun.misc.Unsafe is removed.
Class.forName("jdk.internal.misc.Unsafe")
}
val clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer::class.java)
clean.isAccessible = true
val theUnsafeField = unsafeClass.getDeclaredField("theUnsafe")
theUnsafeField.isAccessible = true
val theUnsafe = theUnsafeField.get(null);
{ buffer: ByteBuffer -> clean.invoke(theUnsafe, buffer) }
}
} catch (ex: Exception) {
null
}
}
@@ -0,0 +1,116 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import java.io.File
import java.io.FileNotFoundException
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import kotlin.collections.HashMap
class FastJarHandler(val fileSystem: FastJarFileSystem, path: String) {
private val myRoot: VirtualFile?
internal val file = File(path)
private val cachedManifest: ByteArray?
init {
val entries: List<ZipEntryDescription>
RandomAccessFile(file, "r").use { randomAccessFile ->
val mappedByteBuffer = randomAccessFile.channel.map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length())
try {
entries = try {
mappedByteBuffer.parseCentralDirectory()
} catch (e: Exception) {
// copying the behavior of ArchiveHandler (and therefore ZipHandler)
// TODO: consider propagating to compiler error or warning, but take into account that both javac and K1 simply ignore invalid jars in such cases
Logger.getInstance(this::class.java).warn("Error while reading zip file: ${file.path}: $e", e)
emptyList()
}
cachedManifest =
entries.singleOrNull { StringUtil.equals(MANIFEST_PATH, it.relativePath) }
?.let(mappedByteBuffer::contentsToByteArray)
} finally {
with(fileSystem) {
mappedByteBuffer.unmapBuffer()
}
}
}
myRoot = FastJarVirtualFile(this, "", -1, parent = null, entryDescription = null)
// ByteArrayCharSequence should not be used instead of String
// because the former class does not support equals/hashCode properly
val filesByRelativePath = HashMap<String, FastJarVirtualFile>(entries.size)
filesByRelativePath[""] = myRoot
for (entryDescription in entries) {
if (!entryDescription.isDirectory) {
createFile(entryDescription, filesByRelativePath)
} else {
getOrCreateDirectory(entryDescription.relativePath, filesByRelativePath)
}
}
for (node in filesByRelativePath.values) {
node.initChildrenArrayFromList()
}
}
private fun createFile(entry: ZipEntryDescription, directories: MutableMap<String, FastJarVirtualFile>): FastJarVirtualFile {
val (parentName, shortName) = entry.relativePath.splitPath()
val parentFile = getOrCreateDirectory(parentName, directories)
if ("." == shortName) {
return parentFile
}
return FastJarVirtualFile(
this, shortName,
if (entry.isDirectory) -1 else entry.uncompressedSize,
parentFile,
entry,
)
}
private fun getOrCreateDirectory(entryName: CharSequence, directories: MutableMap<String, FastJarVirtualFile>): FastJarVirtualFile {
return directories.getOrPut(entryName.toString()) {
val (parentPath, shortName) = entryName.splitPath()
val parentFile = getOrCreateDirectory(parentPath, directories)
FastJarVirtualFile(this, shortName, -1, parentFile, entryDescription = null)
}
}
private fun CharSequence.splitPath(): Pair<CharSequence, CharSequence> {
var slashIndex = this.length - 1
while (slashIndex >= 0 && this[slashIndex] != '/') {
slashIndex--
}
if (slashIndex == -1) return Pair("", this)
return Pair(subSequence(0, slashIndex), subSequence(slashIndex + 1, this.length))
}
fun findFileByPath(pathInJar: String): VirtualFile? {
return myRoot?.findFileByRelativePath(pathInJar)
}
fun contentsToByteArray(zipEntryDescription: ZipEntryDescription): ByteArray {
val relativePath = zipEntryDescription.relativePath
if (StringUtil.equals(relativePath, MANIFEST_PATH)) return cachedManifest ?: throw FileNotFoundException("$file!/$relativePath")
return fileSystem.cachedOpenFileHandles[file].use {
synchronized(it) {
it.get().second.contentsToByteArray(zipEntryDescription)
}
}
}
}
private const val MANIFEST_PATH = "META-INF/MANIFEST.MF"
@@ -0,0 +1,108 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
internal class FastJarVirtualFile(
private val handler: FastJarHandler,
private val name: CharSequence,
private val length: Int,
private val parent: FastJarVirtualFile?,
private val entryDescription: ZipEntryDescription?,
) : VirtualFile() {
private var myChildrenArray = EMPTY_ARRAY
private val myChildrenList: MutableList<VirtualFile> = mutableListOf()
init {
parent?.myChildrenList?.add(this)
}
fun initChildrenArrayFromList() {
myChildrenArray = myChildrenList.toTypedArray()
myChildrenList.clear()
}
override fun getName(): String {
return name.toString()
}
override fun getNameSequence(): CharSequence {
return name
}
override fun getFileSystem(): VirtualFileSystem {
return handler.fileSystem
}
override fun getPath(): String {
if (parent == null) {
return FileUtil.toSystemIndependentName(handler.file.path) + "!/"
}
val parentPath = parent.path
val answer = StringBuilder(parentPath.length + 1 + name.length)
answer.append(parentPath)
if (answer[answer.length - 1] != '/') {
answer.append('/')
}
answer.append(name)
return answer.toString()
}
override fun isWritable(): Boolean {
return false
}
override fun isDirectory(): Boolean {
return length < 0
}
override fun isValid(): Boolean {
return true
}
override fun getParent(): VirtualFile? {
return parent
}
override fun getChildren(): Array<VirtualFile> {
return myChildrenArray
}
@Throws(IOException::class)
override fun getOutputStream(requestor: Any, newModificationStamp: Long, newTimeStamp: Long): OutputStream {
throw UnsupportedOperationException("JarFileSystem is read-only")
}
@Throws(IOException::class)
override fun contentsToByteArray(): ByteArray {
if (entryDescription == null) return EMPTY_BYTE_ARRAY
return handler.contentsToByteArray(entryDescription)
}
override fun getTimeStamp(): Long = 0
override fun getLength(): Long = length.toLong()
override fun refresh(asynchronous: Boolean, recursive: Boolean, postRunnable: Runnable?) {}
@Throws(IOException::class)
override fun getInputStream(): InputStream {
return BufferExposingByteArrayInputStream(contentsToByteArray())
}
override fun getModificationStamp(): Long {
return 0
}
}
private val EMPTY_BYTE_ARRAY = ByteArray(0)
@@ -0,0 +1,165 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.jarfs
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.MappedByteBuffer
import java.util.zip.Inflater
class ZipEntryDescription(
val relativePath: CharSequence,
val compressedSize: Int,
val uncompressedSize: Int,
val offsetInFile: Int,
val compressionKind: CompressionKind,
val fileNameSize: Int,
) {
enum class CompressionKind {
PLAIN, DEFLATE
}
val isDirectory get() = uncompressedSize == 0
}
private const val END_OF_CENTRAL_DIR_SIZE = 22
private const val END_OF_CENTRAL_DIR_ZIP64_SIZE = 56
private const val LOCAL_FILE_HEADER_EXTRA_OFFSET = 28
private const val LOCAL_FILE_HEADER_SIZE = LOCAL_FILE_HEADER_EXTRA_OFFSET + 2
fun MappedByteBuffer.contentsToByteArray(
zipEntryDescription: ZipEntryDescription
): ByteArray {
order(ByteOrder.LITTLE_ENDIAN)
val extraSize =
getUnsignedShort(zipEntryDescription.offsetInFile + LOCAL_FILE_HEADER_EXTRA_OFFSET)
position(
zipEntryDescription.offsetInFile + LOCAL_FILE_HEADER_SIZE + zipEntryDescription.fileNameSize + extraSize
)
val compressed = ByteArray(zipEntryDescription.compressedSize + 1)
get(compressed, 0, zipEntryDescription.compressedSize)
return when (zipEntryDescription.compressionKind) {
ZipEntryDescription.CompressionKind.DEFLATE -> {
val inflater = Inflater(true)
inflater.setInput(compressed, 0, zipEntryDescription.compressedSize)
val result = ByteArray(zipEntryDescription.uncompressedSize)
inflater.inflate(result)
result
}
ZipEntryDescription.CompressionKind.PLAIN -> compressed.copyOf(zipEntryDescription.compressedSize)
}
}
fun MappedByteBuffer.parseCentralDirectory(): List<ZipEntryDescription> {
order(ByteOrder.LITTLE_ENDIAN)
val (entriesNumber, offsetOfCentralDirectory) = parseCentralDirectoryRecordsNumberAndOffset()
var currentOffset = offsetOfCentralDirectory
val result = mutableListOf<ZipEntryDescription>()
for (i in 0 until entriesNumber) {
val headerConst = getInt(currentOffset)
require(headerConst == 0x02014b50) {
"$i: $headerConst"
}
val versionNeededToExtract =
getShort(currentOffset + 6).toInt()
val compressionMethod = getShort(currentOffset + 10).toInt()
val compressedSize = getInt(currentOffset + 20)
val uncompressedSize = getInt(currentOffset + 24)
val fileNameLength = getUnsignedShort(currentOffset + 28)
val extraLength = getUnsignedShort(currentOffset + 30)
val fileCommentLength = getUnsignedShort(currentOffset + 32)
val offsetOfFileData = getInt(currentOffset + 42)
val bytesForName = ByteArray(fileNameLength)
position(currentOffset + 46)
get(bytesForName)
val name =
if (bytesForName.all { it >= 0 })
ByteArrayCharSequence(bytesForName)
else
String(bytesForName, Charsets.UTF_8)
currentOffset += 46 + fileNameLength + extraLength + fileCommentLength
// We support version needed to extract 10 and 20. However, there are zip
// files in the eco-system with entries with invalid version to extract
// of 0. Therefore, we just check that the version is between 0 and 20.
require(versionNeededToExtract in 0..20) {
"Unexpected versionNeededToExtract ($versionNeededToExtract) at $name"
}
val compressionKind = when (compressionMethod) {
0 -> ZipEntryDescription.CompressionKind.PLAIN
8 -> ZipEntryDescription.CompressionKind.DEFLATE
else -> error("Unexpected compression method ($compressionMethod) at $name")
}
result += ZipEntryDescription(
name, compressedSize, uncompressedSize, offsetOfFileData, compressionKind,
fileNameLength
)
}
return result
}
private fun MappedByteBuffer.parseCentralDirectoryRecordsNumberAndOffset(): Pair<Int, Int> {
var endOfCentralDirectoryOffset = capacity() - END_OF_CENTRAL_DIR_SIZE
while (endOfCentralDirectoryOffset >= 0) {
// header of "End of central directory" (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
if (getInt(endOfCentralDirectoryOffset) == 0x06054b50) break
endOfCentralDirectoryOffset--
}
val entriesNumber = getUnsignedShort(endOfCentralDirectoryOffset + 10)
val offsetOfCentralDirectory = getInt(endOfCentralDirectoryOffset + 16)
// Offset of start of central directory, relative to start of archive (or -1 for ZIP64)
// (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
if (entriesNumber == 0xffff || offsetOfCentralDirectory == -1) return parseZip64CentralDirectoryRecordsNumberAndOffset()
return Pair(entriesNumber, offsetOfCentralDirectory)
}
private fun MappedByteBuffer.parseZip64CentralDirectoryRecordsNumberAndOffset(): Pair<Int, Int> {
var endOfCentralDirectoryOffset = capacity() - END_OF_CENTRAL_DIR_ZIP64_SIZE
while (endOfCentralDirectoryOffset >= 0) {
// header of "End of central directory" (see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
if (getInt(endOfCentralDirectoryOffset) == 0x06064b50) break
endOfCentralDirectoryOffset--
}
val entriesNumber = getLong(endOfCentralDirectoryOffset + 32)
val offsetOfCentralDirectory = getLong(endOfCentralDirectoryOffset + 48)
require(entriesNumber <= Int.MAX_VALUE) {
"Jar $entriesNumber entries number equal or more than ${Int.MAX_VALUE} is not supported by FastJarFS"
}
require(offsetOfCentralDirectory <= Int.MAX_VALUE) {
"Jar $offsetOfCentralDirectory offset equal or more than ${Int.MAX_VALUE} is not supported by FastJarFS"
}
return Pair(entriesNumber.toInt(), offsetOfCentralDirectory.toInt())
}
private fun ByteBuffer.getUnsignedShort(offset: Int): Int = java.lang.Short.toUnsignedInt(getShort(offset))
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.config
import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.load.java.JavaClassesTracker
object ClassicFrontendSpecificJvmConfigurationKeys {
@JvmField
val JAVA_CLASSES_TRACKER: CompilerConfigurationKey<JavaClassesTracker> = CompilerConfigurationKey.create("Java classes tracker")
}
@@ -0,0 +1,104 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.config
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.config.ContentRoot
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.jvm.compiler.report
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
interface JvmContentRootBase : ContentRoot
interface JvmClasspathRootBase : JvmContentRootBase {
val isSdkRoot: Boolean
}
interface JvmContentRoot : JvmContentRootBase {
val file: File
}
data class JvmClasspathRoot(override val file: File, override val isSdkRoot: Boolean) : JvmContentRoot, JvmClasspathRootBase {
constructor(file: File) : this(file, false)
}
@Suppress("unused") // Might be useful for external tools which invoke kotlinc with their own file system, not based on java.io.File.
data class VirtualJvmClasspathRoot(val file: VirtualFile, override val isSdkRoot: Boolean) : JvmClasspathRootBase {
constructor(file: VirtualFile) : this(file, false)
}
data class JavaSourceRoot(override val file: File, val packagePrefix: String?) : JvmContentRoot
data class JvmModulePathRoot(override val file: File) : JvmContentRoot
fun CompilerConfiguration.addJvmClasspathRoot(file: File) {
add(CLIConfigurationKeys.CONTENT_ROOTS, JvmClasspathRoot(file))
}
fun CompilerConfiguration.addJvmClasspathRoots(files: List<File>) {
files.forEach(this::addJvmClasspathRoot)
}
fun CompilerConfiguration.addJvmSdkRoots(files: List<File>) {
addAll(CLIConfigurationKeys.CONTENT_ROOTS, 0, files.map { file -> JvmClasspathRoot(file, true) })
}
val CompilerConfiguration.jvmClasspathRoots: List<File>
get() = getList(CLIConfigurationKeys.CONTENT_ROOTS).filterIsInstance<JvmClasspathRoot>().map(JvmContentRoot::file)
val CompilerConfiguration.jvmModularRoots: List<File>
get() = getList(CLIConfigurationKeys.CONTENT_ROOTS).filterIsInstance<JvmModulePathRoot>().map(JvmContentRoot::file)
@JvmOverloads
fun CompilerConfiguration.addJavaSourceRoot(file: File, packagePrefix: String? = null) {
add(CLIConfigurationKeys.CONTENT_ROOTS, JavaSourceRoot(file, packagePrefix))
}
@JvmOverloads
fun CompilerConfiguration.addJavaSourceRoots(files: List<File>, packagePrefix: String? = null) {
files.forEach { addJavaSourceRoot(it, packagePrefix) }
}
val CompilerConfiguration.javaSourceRoots: Set<String>
get() = getList(CLIConfigurationKeys.CONTENT_ROOTS).mapNotNullTo(linkedSetOf()) { root ->
when (root) {
is KotlinSourceRoot -> root.path
is JavaSourceRoot -> root.file.path
else -> null
}
}
fun CompilerConfiguration.configureJdkClasspathRoots() {
if (getBoolean(JVMConfigurationKeys.NO_JDK)) return
val javaRoot = get(JVMConfigurationKeys.JDK_HOME) ?: File(System.getProperty("java.home"))
val classesRoots = PathUtil.getJdkClassesRootsFromJdkOrJre(javaRoot)
if (!CoreJrtFileSystem.isModularJdk(javaRoot)) {
if (classesRoots.isEmpty()) {
report(CompilerMessageSeverity.ERROR, "No class roots are found in the JDK path: $javaRoot")
} else {
addJvmSdkRoots(classesRoots)
}
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.index
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
class JvmDependenciesDynamicCompoundIndex : JvmDependenciesIndex {
private val indices = arrayListOf<JvmDependenciesIndex>()
private val lock = ReentrantReadWriteLock()
fun addIndex(index: JvmDependenciesIndex) {
lock.write {
indices.add(index)
}
}
fun addNewIndexForRoots(roots: Iterable<JavaRoot>): JvmDependenciesIndex? =
lock.read {
val alreadyIndexed = indexedRoots.toHashSet()
val newRoots = roots.filter { root -> root !in alreadyIndexed }
if (newRoots.isEmpty()) null
else JvmDependenciesIndexImpl(newRoots).also(this::addIndex)
}
override val indexedRoots: Sequence<JavaRoot> get() = indices.asSequence().flatMap { it.indexedRoots }
override fun <T : Any> findClass(
classId: ClassId,
acceptedRootTypes: Set<JavaRoot.RootType>,
findClassGivenDirectory: (VirtualFile, JavaRoot.RootType) -> T?
): T? = lock.read {
indices.asSequence().mapNotNull { it.findClass(classId, acceptedRootTypes, findClassGivenDirectory) }.firstOrNull()
}
override fun traverseDirectoriesInPackage(
packageFqName: FqName,
acceptedRootTypes: Set<JavaRoot.RootType>,
continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean
) = lock.read {
indices.forEach { it.traverseDirectoriesInPackage(packageFqName, acceptedRootTypes, continueSearch) }
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.index
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import java.util.*
interface JvmDependenciesIndex {
val indexedRoots: Sequence<JavaRoot>
fun <T : Any> findClass(
classId: ClassId,
acceptedRootTypes: Set<JavaRoot.RootType> = JavaRoot.SourceAndBinary,
findClassGivenDirectory: (VirtualFile, JavaRoot.RootType) -> T?
): T?
fun traverseDirectoriesInPackage(
packageFqName: FqName,
acceptedRootTypes: Set<JavaRoot.RootType> = JavaRoot.SourceAndBinary,
continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean
)
}
data class JavaRoot(val file: VirtualFile, val type: RootType, val prefixFqName: FqName? = null) {
enum class RootType {
SOURCE,
BINARY,
BINARY_SIG
}
companion object RootTypes {
val OnlyBinary: Set<RootType> = EnumSet.of(RootType.BINARY, RootType.BINARY_SIG)
val OnlySource: Set<RootType> = EnumSet.of(RootType.SOURCE)
val SourceAndBinary: Set<RootType> = EnumSet.of(RootType.BINARY, RootType.BINARY_SIG, RootType.SOURCE)
}
}
@@ -0,0 +1,251 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.index
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import gnu.trove.THashMap
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.utils.IntArrayList
import java.util.*
// speeds up finding files/classes in classpath/java source roots
// NOT THREADSAFE, needs to be adapted/removed if we want compiler to be multithreaded
// the main idea of this class is for each package to store roots which contains it to avoid excessive file system traversal
class JvmDependenciesIndexImpl(_roots: List<JavaRoot>) : JvmDependenciesIndex {
//these fields are computed based on _roots passed to constructor which are filled in later
private val roots: List<JavaRoot> by lazy { _roots.toList() }
private val maxIndex: Int
get() = roots.size
// each "Cache" object corresponds to a package
private class Cache {
private val innerPackageCaches = HashMap<String, Cache>()
operator fun get(name: String) = innerPackageCaches.getOrPut(name, ::Cache)
// indices of roots that are known to contain this package
// if this list contains [1, 3, 5] then roots with indices 1, 3 and 5 are known to contain this package, 2 and 4 are known not to (no information about roots 6 or higher)
// if this list contains maxIndex that means that all roots containing this package are known
@Suppress("DEPRECATION") // TODO: fix deprecation
val rootIndices = com.intellij.util.containers.IntArrayList(2)
}
// root "Cache" object corresponds to DefaultPackage which exists in every root. Roots with non-default fqname are also listed here but
// they will be ignored on requests with invalid fqname prefix.
private val rootCache: Cache by lazy {
Cache().apply {
roots.indices.forEach(rootIndices::add)
rootIndices.add(maxIndex)
rootIndices.trimToSize()
}
}
// holds the request and the result last time we searched for class
// helps improve several scenarios, LazyJavaResolverContext.findClassInJava being the most important
private var lastClassSearch: Pair<FindClassRequest, SearchResult>? = null
override val indexedRoots by lazy { roots.asSequence() }
private val packageCache: Array<out MutableMap<String, VirtualFile?>> by lazy {
Array(roots.size) { THashMap<String, VirtualFile?>() }
}
override fun traverseDirectoriesInPackage(
packageFqName: FqName,
acceptedRootTypes: Set<JavaRoot.RootType>,
continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean
) {
search(TraverseRequest(packageFqName, acceptedRootTypes)) { dir, rootType ->
if (continueSearch(dir, rootType)) null else Unit
}
}
// findClassGivenDirectory MUST check whether the class with this classId exists in given package
override fun <T : Any> findClass(
classId: ClassId,
acceptedRootTypes: Set<JavaRoot.RootType>,
findClassGivenDirectory: (VirtualFile, JavaRoot.RootType) -> T?
): T? {
// make a decision based on information saved from last class search
if (lastClassSearch?.first?.classId != classId) {
return search(FindClassRequest(classId, acceptedRootTypes), findClassGivenDirectory)
}
val (cachedRequest, cachedResult) = lastClassSearch!!
return when (cachedResult) {
is SearchResult.NotFound -> {
val limitedRootTypes = acceptedRootTypes - cachedRequest.acceptedRootTypes
if (limitedRootTypes.isEmpty()) {
null
} else {
search(FindClassRequest(classId, limitedRootTypes), findClassGivenDirectory)
}
}
is SearchResult.Found -> {
if (cachedRequest.acceptedRootTypes == acceptedRootTypes) {
findClassGivenDirectory(cachedResult.packageDirectory, cachedResult.root.type)
} else {
search(FindClassRequest(classId, acceptedRootTypes), findClassGivenDirectory)
}
}
}
}
private fun <T : Any> search(request: SearchRequest, handler: (VirtualFile, JavaRoot.RootType) -> T?): T? {
// a list of package sub names, ["org", "jb", "kotlin"]
val packagesPath = request.packageFqName.pathSegments().map { it.identifier }
// a list of caches corresponding to packages, [default, "org", "org.jb", "org.jb.kotlin"]
val caches = cachesPath(packagesPath)
var processedRootsUpTo = -1
// traverse caches starting from last, which contains most specific information
// NOTE: indices manipulation instead of using caches.reversed() is here for performance reasons
for (cacheIndex in caches.lastIndex downTo 0) {
val cacheRootIndices = caches[cacheIndex].rootIndices
for (i in 0 until cacheRootIndices.size()) {
val rootIndex = cacheRootIndices[i]
if (rootIndex <= processedRootsUpTo) continue // roots with those indices have been processed by now
val directoryInRoot = travelPath(rootIndex, request.packageFqName, packagesPath, cacheIndex, caches) ?: continue
val root = roots[rootIndex]
if (root.type in request.acceptedRootTypes) {
val result = handler(directoryInRoot, root.type)
if (result != null) {
if (request is FindClassRequest) {
lastClassSearch = Pair(request, SearchResult.Found(directoryInRoot, root))
}
return result
}
}
}
processedRootsUpTo = if (cacheRootIndices.isEmpty) processedRootsUpTo else cacheRootIndices[cacheRootIndices.size() - 1]
}
if (request is FindClassRequest) {
lastClassSearch = Pair(request, SearchResult.NotFound)
}
return null
}
// try to find a target directory corresponding to package represented by packagesPath in a given root represented by index
// possibly filling "Cache" objects with new information
private fun travelPath(
rootIndex: Int,
packageFqName: FqName,
packagesPath: List<String>,
fillCachesAfter: Int,
cachesPath: List<Cache>
): VirtualFile? {
if (rootIndex >= maxIndex) {
for (i in (fillCachesAfter + 1) until cachesPath.size) {
// we all know roots that contain this package by now
cachesPath[i].rootIndices.add(maxIndex)
cachesPath[i].rootIndices.trimToSize()
}
return null
}
return packageCache[rootIndex].getOrPut(packageFqName.asString()) {
doTravelPath(rootIndex, packagesPath, fillCachesAfter, cachesPath)
}
}
private fun doTravelPath(rootIndex: Int, packagesPath: List<String>, fillCachesAfter: Int, cachesPath: List<Cache>): VirtualFile? {
val pathRoot = roots[rootIndex]
val prefixPathSegments = pathRoot.prefixFqName?.pathSegments()
var currentFile = pathRoot.file
for (pathIndex in packagesPath.indices) {
val subPackageName = packagesPath[pathIndex]
if (prefixPathSegments != null && pathIndex < prefixPathSegments.size) {
// Traverse prefix first instead of traversing real directories
if (prefixPathSegments[pathIndex].identifier != subPackageName) {
return null
}
} else {
currentFile = currentFile.findChildPackage(subPackageName, pathRoot.type) ?: return null
}
val correspondingCacheIndex = pathIndex + 1
if (correspondingCacheIndex > fillCachesAfter) {
// subPackageName exists in this root
cachesPath[correspondingCacheIndex].rootIndices.add(rootIndex)
}
}
return currentFile
}
private fun VirtualFile.findChildPackage(subPackageName: String, rootType: JavaRoot.RootType): VirtualFile? {
val childDirectory = findChild(subPackageName) ?: return null
val fileExtension = when (rootType) {
JavaRoot.RootType.BINARY -> JavaClassFileType.INSTANCE.defaultExtension
JavaRoot.RootType.BINARY_SIG -> "sig"
JavaRoot.RootType.SOURCE -> JavaFileType.INSTANCE.defaultExtension
}
// If in addition to a directory "foo" there's a class file "foo.class" AND there are no classes anywhere in the directory "foo",
// then we ignore the directory and let the resolution choose the class "foo" instead.
if (findChild("$subPackageName.$fileExtension")?.isDirectory == false) {
if (VfsUtilCore.processFilesRecursively(childDirectory) { file -> file.extension != fileExtension }) {
return null
}
}
return childDirectory
}
private fun cachesPath(path: List<String>): List<Cache> {
val caches = ArrayList<Cache>(path.size + 1)
caches.add(rootCache)
var currentCache = rootCache
for (subPackageName in path) {
currentCache = currentCache[subPackageName]
caches.add(currentCache)
}
return caches
}
private data class FindClassRequest(val classId: ClassId, override val acceptedRootTypes: Set<JavaRoot.RootType>) : SearchRequest {
override val packageFqName: FqName
get() = classId.packageFqName
}
private data class TraverseRequest(
override val packageFqName: FqName,
override val acceptedRootTypes: Set<JavaRoot.RootType>
) : SearchRequest
private interface SearchRequest {
val packageFqName: FqName
val acceptedRootTypes: Set<JavaRoot.RootType>
}
private sealed class SearchResult {
class Found(val packageDirectory: VirtualFile, val root: JavaRoot) : SearchResult()
object NotFound : SearchResult()
}
}
@@ -0,0 +1,125 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.index
import com.intellij.lang.java.lexer.JavaLexer
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.PsiKeyword
import com.intellij.psi.impl.source.tree.ElementType
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
class SingleJavaFileRootsIndex(private val roots: List<JavaRoot>) {
init {
for ((file) in roots) {
assert(!file.isDirectory) { "Should not be a directory: $file" }
}
}
private val classIdsInRoots = ArrayList<List<ClassId>>(roots.size)
fun findJavaSourceClass(classId: ClassId): VirtualFile? =
roots.indices
.find { index -> classId in getClassIdsForRootAt(index) }
?.let { index -> roots[index].file }
fun findJavaSourceClasses(packageFqName: FqName): List<ClassId> =
roots.indices.flatMap(this::getClassIdsForRootAt).filter { root -> root.packageFqName == packageFqName }
private fun getClassIdsForRootAt(index: Int): List<ClassId> {
for (i in classIdsInRoots.size..index) {
classIdsInRoots.add(JavaSourceClassIdReader(roots[i].file).readClassIds())
}
return classIdsInRoots[index]
}
/**
* Given a .java file, [readClassIds] uses lexer to determine which classes are declared in that file
*/
private class JavaSourceClassIdReader(file: VirtualFile) {
private val lexer = JavaLexer(LanguageLevel.HIGHEST).apply {
start(String(file.contentsToByteArray()))
}
private var braceBalance = 0
private var parenthesisBalance = 0
private fun at(type: IElementType): Boolean = lexer.tokenType == type
private fun end(): Boolean = lexer.tokenType == null
private fun advance() {
when {
at(ElementType.LBRACE) -> braceBalance++
at(ElementType.RBRACE) -> braceBalance--
at(ElementType.LPARENTH) -> parenthesisBalance++
at(ElementType.RPARENTH) -> parenthesisBalance--
}
lexer.advance()
}
private fun tokenText(): String = lexer.tokenText
private fun atClass(): Boolean =
braceBalance == 0 && parenthesisBalance == 0 && (lexer.tokenType in CLASS_KEYWORDS || atRecord())
private fun atRecord(): Boolean {
// Note that the soft keyword "record" is lexed as IDENTIFIER instead of RECORD_KEYWORD.
// This is kind of a sloppy way to parse a soft keyword, but we only do it at the top level, where it seems to work fine.
return at(ElementType.IDENTIFIER) && tokenText() == PsiKeyword.RECORD
}
fun readClassIds(): List<ClassId> {
var packageFqName = FqName.ROOT
while (!end() && !at(ElementType.PACKAGE_KEYWORD) && !atClass()) {
advance()
}
if (at(ElementType.PACKAGE_KEYWORD)) {
val packageName = StringBuilder()
while (!end() && !at(ElementType.SEMICOLON)) {
if (at(ElementType.IDENTIFIER) || at(ElementType.DOT)) {
packageName.append(tokenText())
}
advance()
}
packageFqName = FqName(packageName.toString())
}
val result = ArrayList<ClassId>(1)
while (true) {
while (!end() && !atClass()) {
advance()
}
if (end()) break
advance()
while (!end() && !at(ElementType.IDENTIFIER)) {
advance()
}
if (end()) break
result.add(ClassId(packageFqName, Name.identifier(tokenText())))
}
return result
}
companion object {
private val CLASS_KEYWORDS = setOf(ElementType.CLASS_KEYWORD, ElementType.INTERFACE_KEYWORD, ElementType.ENUM_KEYWORD)
}
}
}
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.javac
import com.sun.tools.javac.util.Context
import com.sun.tools.javac.util.Log
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import java.io.PrintWriter
import java.io.Writer
class JavacLogger(
context: Context,
errorWriter: PrintWriter,
warningWriter: PrintWriter,
infoWriter: PrintWriter
) : Log(context, errorWriter, warningWriter, infoWriter) {
init {
context.put(Log.outKey, infoWriter)
}
override fun printLines(kind: WriterKind, message: String, vararg args: Any?) {}
companion object {
fun preRegister(context: Context, messageCollector: MessageCollector) {
context.put(Log.logKey, Context.Factory<Log> {
JavacLogger(
it,
PrintWriter(MessageCollectorAdapter(messageCollector, CompilerMessageSeverity.ERROR)),
PrintWriter(MessageCollectorAdapter(messageCollector, CompilerMessageSeverity.WARNING)),
PrintWriter(MessageCollectorAdapter(messageCollector, CompilerMessageSeverity.INFO))
)
})
}
}
}
private class MessageCollectorAdapter(
private val messageCollector: MessageCollector,
private val severity: CompilerMessageSeverity
) : Writer() {
override fun write(buffer: CharArray, offset: Int, length: Int) {
if (length == 1 && buffer[0] == '\n') return
messageCollector.report(severity, String(buffer, offset, length))
}
override fun flush() {
(messageCollector as? GroupingMessageCollector)?.flush()
}
override fun close() = flush()
}
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.javac
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.findFacadeClass
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.javac.JavacWrapperKotlinResolver
import org.jetbrains.kotlin.javac.resolve.MockKotlinField
import org.jetbrains.kotlin.load.java.structure.JavaField
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
class JavacWrapperKotlinResolverImpl(private val lightClassGenerationSupport: LightClassGenerationSupport) : JavacWrapperKotlinResolver {
private val supersCache = hashMapOf<KtClassOrObject, List<ClassId>>()
override fun resolveSupertypes(classOrObject: KtClassOrObject): List<ClassId> {
val cachedItem = supersCache[classOrObject]
if (cachedItem != null) {
return cachedItem
}
val classDescriptor =
lightClassGenerationSupport.analyze(classOrObject).get(BindingContext.CLASS, classOrObject) ?: return emptyList()
val classIds = classDescriptor.defaultType.constructor.supertypes
.mapNotNull { (it.constructor.declarationDescriptor as? ClassDescriptor)?.classId }
supersCache[classOrObject] = classIds
return classIds
}
override fun findField(classOrObject: KtClassOrObject, name: String): JavaField? {
val lightClass = classOrObject.toLightClass() ?: return null
return lightClass.allFields.find { it.name == name }?.let(::MockKotlinField)
}
override fun findField(ktFile: KtFile?, name: String): JavaField? {
val lightClass = ktFile?.findFacadeClass() ?: return null
return lightClass.allFields.find { it.name == name }?.let(::MockKotlinField)
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.javac
import com.intellij.mock.MockProject
import com.sun.tools.javac.util.Context
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
object JavacWrapperRegistrar {
private const val JAVAC_CONTEXT_CLASS = "com.sun.tools.javac.util.Context"
fun registerJavac(
project: MockProject,
configuration: CompilerConfiguration,
javaFiles: List<File>,
kotlinFiles: List<KtFile>,
arguments: Array<String>?,
bootClasspath: List<File>?,
sourcePath: List<File>?,
lightClassGenerationSupport: LightClassGenerationSupport,
packagePartsProviders: List<JvmPackagePartProvider>
): Boolean {
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
try {
Class.forName(JAVAC_CONTEXT_CLASS)
} catch (e: ClassNotFoundException) {
messageCollector.report(ERROR, "'$JAVAC_CONTEXT_CLASS' class can't be found ('tools.jar' is not found)")
return false
}
val context = Context()
JavacLogger.preRegister(context, messageCollector)
val jvmClasspathRoots = configuration.jvmClasspathRoots
val outputDirectory = configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)
val compileJava = configuration.getBoolean(JVMConfigurationKeys.COMPILE_JAVA)
val kotlinSupertypesResolver = JavacWrapperKotlinResolverImpl(lightClassGenerationSupport)
val javacWrapper = JavacWrapper(
javaFiles, kotlinFiles, arguments, jvmClasspathRoots, bootClasspath, sourcePath,
kotlinSupertypesResolver, packagePartsProviders, compileJava, outputDirectory, context
)
project.registerService(JavacWrapper::class.java, javacWrapper)
return true
}
}
@@ -0,0 +1,226 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiJavaModule
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.io.URLUtil
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo
import java.io.File
class CliJavaModuleFinder(
private val jdkHome: File?,
private val messageCollector: MessageCollector?,
private val javaFileManager: KotlinCliJavaFileManager,
project: Project,
private val jdkRelease: Int?
) : JavaModuleFinder {
private val jrtFileSystemRoot = jdkHome?.path?.let { path ->
VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL)?.findFileByPath(path + URLUtil.JAR_SEPARATOR)
}
private val modulesRoot = jrtFileSystemRoot?.findChild("modules")
private val userModules = linkedMapOf<String, JavaModule>()
private val allScope = GlobalSearchScope.allScope(project)
private val ctSymFile: VirtualFile? by lazy {
if (jdkHome == null) return@lazy reportError("JDK_HOME path is not specified in compiler configuration")
val jdkRootFile = StandardFileSystems.local().findFileByPath(jdkHome.path)
?: return@lazy reportError("Can't create virtual file for JDK root under ${jdkHome.path}")
val lib = jdkRootFile.findChild("lib") ?: return@lazy reportError("Can't find `lib` folder under JDK root: ${jdkHome.path}")
val ctSym = lib.findChild("ct.sym")
?: return@lazy reportError(
"This JDK does not have the 'ct.sym' file required for the '-Xjdk-release=$jdkRelease' option: ${jdkHome.path}"
)
ctSym
}
private val ctSymRootFolder: VirtualFile? by lazy {
if (ctSymFile != null) {
StandardFileSystems.jar()?.findFileByPath(ctSymFile?.path + URLUtil.JAR_SEPARATOR)
?: reportError("Can't open `ct.sym` as jar file, file path: ${ctSymFile?.path} ")
} else {
null
}
}
private val compilationJdkVersion by lazy {
// Observe all JDK codes from folder name chars in ct.sym file,
// there should be maximal one corresponding to used compilation JDK
ctSymRootFolder?.children?.maxOf {
if (it.name == "META-INF") -1
else it.name.substringBeforeLast("-modules").maxOf { char ->
char.toString().toIntOrNull(36) ?: -1
}
} ?: -1
}
val ctSymModules by lazy {
collectModuleRoots()
}
private val isCompilationJDK12OrLater
get() = compilationJdkVersion >= 12
private val useLastJdkApi: Boolean
get() = jdkRelease == null || jdkRelease == compilationJdkVersion
fun addUserModule(module: JavaModule) {
userModules.putIfAbsent(module.name, module)
}
val allObservableModules: Sequence<JavaModule>
get() = systemModules + userModules.values
//Cache system modules for JDK 9-11 to preserve virtual files as one folder could be mapped to several modules
private val systemModulesCache = mutableMapOf<String, JavaModule.Explicit>()
val systemModules: Sequence<JavaModule.Explicit>
get() = if (useLastJdkApi) modulesRoot?.children.orEmpty().asSequence().mapNotNull(this::findSystemModule) else
ctSymModules.values.asSequence().mapNotNull { findSystemModule(it, true) }
override fun findModule(name: String): JavaModule? =
when {
useLastJdkApi -> modulesRoot?.findChild(name)?.let(this::findSystemModule)
else -> ctSymModules[name]?.let { findSystemModule(it, true) }
} ?: userModules[name]
private fun findSystemModule(moduleRoot: VirtualFile, useSig: Boolean = false): JavaModule.Explicit? {
val file = moduleRoot.findChild(if (useSig) PsiJavaModule.MODULE_INFO_CLASS + ".sig" else PsiJavaModule.MODULE_INFO_CLS_FILE)
?: return null
val moduleInfo = JavaModuleInfo.read(file, javaFileManager, allScope) ?: return null
return systemModulesCache.getOrPut(moduleInfo.moduleName) {
JavaModule.Explicit(
moduleInfo,
when {
useLastJdkApi -> listOf(JavaModule.Root(moduleRoot, isBinary = true, isBinarySignature = useSig))
useSig -> createModuleFromSignature(moduleInfo)
else -> error("Can't find ${moduleRoot.path} module")
},
file, !useLastJdkApi && useSig
)
}
}
private fun createModuleFromSignature(moduleInfo: JavaModuleInfo): List<JavaModule.Root> {
return listOf(createModuleFromSignature(!isCompilationJDK12OrLater, isCompilationJDK12OrLater, moduleInfo))
}
private fun createModuleFromSignature(
filterPackages: Boolean,
filterModules: Boolean,
moduleInfo: JavaModuleInfo
): JavaModule.Root {
val packageParts =
if (!filterPackages) emptyMap()
else hashMapOf<String, Boolean>().also { parts ->
moduleInfo.exports.forEach {
for (part in generateSequence(it.packageFqName) { part -> if (!part.isRoot) part.parent() else null }) {
parts[part.asString()] = false
}
}
//Do it separately to avoid reset to false
moduleInfo.exports.forEach {
parts[it.packageFqName.asString()] = true
}
}
val moduleFolders =
if (filterModules)
listFoldersForRelease.filter { virtualFile -> virtualFile.name == moduleInfo.moduleName }
else
listFoldersForRelease
return JavaModule.Root(
CtSymDirectoryContainer(
ctSymRootFolder ?: ctSymFile,
moduleFolders,
packageParts,
"",
moduleInfo.moduleName,
skipPackageCheck = !filterPackages
),
isBinary = true,
isBinarySignature = true
)
}
private fun codeFor(release: Int): String = release.toString(36).toUpperCase()
private fun matchesRelease(fileName: String, release: Int) =
!fileName.contains("-") && fileName.contains(codeFor(release)) // skip `*-modules`
val nonModuleRoot: JavaModule.Root by lazy {
createModuleFromSignature(false, false, JavaModuleInfo("*", emptyList(), emptyList(), emptyList()))
}
private val listFoldersForRelease: List<VirtualFile> by lazy {
if (ctSymRootFolder == null) emptyList()
else ctSymRootFolder!!.children.filter { matchesRelease(it.name, jdkRelease!!) }.flatMap {
if (isCompilationJDK12OrLater)
it.children.toList()
else {
listOf(it)
}
}.apply {
if (isEmpty()) reportError("'-Xjdk-release=${jdkRelease}' option is not supported by used JDK: ${jdkHome?.path}")
}
}
private fun collectModuleRoots(): Map<String, VirtualFile> {
val result = mutableMapOf<String, VirtualFile>()
if (isCompilationJDK12OrLater) {
listFoldersForRelease.forEach { modulesRoot ->
modulesRoot.findChild("module-info.sig")?.let {
result[modulesRoot.name] = modulesRoot
}
}
} else {
if (this.jdkRelease!! > 8 && ctSymRootFolder != null) {
ctSymRootFolder!!.findChild(codeFor(jdkRelease) + if (!isCompilationJDK12OrLater) "-modules" else "")?.apply {
children.forEach {
result[it.name] = it
}
} ?: reportError("Can't find modules signatures in `ct.sym` file for `-Xjdk-release=$jdkRelease` in ${ctSymFile!!.path}")
}
}
return result
}
private fun reportError(message: String): VirtualFile? {
messageCollector?.report(CompilerMessageSeverity.ERROR, message)
return null
}
}
@@ -0,0 +1,98 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
import java.util.concurrent.ConcurrentHashMap
class CliJavaModuleResolver(
private val moduleGraph: JavaModuleGraph,
private val userModules: List<JavaModule>,
private val systemModules: List<JavaModule.Explicit>,
private val project: Project
) : JavaModuleResolver {
init {
assert(userModules.count(JavaModule::isSourceModule) <= 1) {
"Modules computed by ClasspathRootsResolver cannot have more than one source module: $userModules"
}
}
private val virtualFileFinder by lazy { VirtualFileFinder.getInstance(project) }
override fun getAnnotationsForModuleOwnerOfClass(classId: ClassId): List<JavaAnnotation>? {
val virtualFile = virtualFileFinder.findSourceOrBinaryVirtualFile(classId) ?: return null
return (findJavaModule(virtualFile) as? JavaModule.Explicit)?.moduleInfo?.annotations
}
private val sourceModule: JavaModule? = userModules.firstOrNull(JavaModule::isSourceModule)
private fun findJavaModule(file: VirtualFile): JavaModule? {
if (file.fileSystem.protocol == StandardFileSystems.JRT_PROTOCOL || file.extension == "sig") {
return systemModules.firstOrNull { module -> file in module }
}
return when (file.fileType) {
KotlinFileType.INSTANCE, JavaFileType.INSTANCE -> sourceModule
JavaClassFileType.INSTANCE -> userModules.firstOrNull { module -> file in module }
else -> null
}
}
private operator fun JavaModule.contains(file: VirtualFile): Boolean =
moduleRoots.any { (root, isBinary) -> isBinary && VfsUtilCore.isAncestor(root, file, false) }
override fun checkAccessibility(
fileFromOurModule: VirtualFile?, referencedFile: VirtualFile, referencedPackage: FqName?
): JavaModuleResolver.AccessError? {
val ourModule = fileFromOurModule?.let(this::findJavaModule)
val theirModule = this.findJavaModule(referencedFile)
if (ourModule?.name == theirModule?.name) return null
if (theirModule == null) {
return JavaModuleResolver.AccessError.ModuleDoesNotReadUnnamedModule
}
if (ourModule != null && !moduleGraph.reads(ourModule.name, theirModule.name)) {
return JavaModuleResolver.AccessError.ModuleDoesNotReadModule(theirModule.name)
}
val fqName = referencedPackage ?: return null
if (!theirModule.exports(fqName) && (ourModule == null || !theirModule.exportsTo(fqName, ourModule.name))) {
return JavaModuleResolver.AccessError.ModuleDoesNotExportPackage(theirModule.name)
}
return null
}
companion object {
private const val MODULE_ANNOTATIONS_CACHE_SIZE = 10000
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.openapi.vfs.DeprecatedVirtualFileSystem
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.io.URLUtil
import java.io.File
import java.net.URI
import java.net.URLClassLoader
import java.nio.file.FileSystems
// There's JrtFileSystem in idea-full which we can't use in the compiler because it depends on NewVirtualFileSystem, absent in intellij-core
class CoreJrtFileSystem : DeprecatedVirtualFileSystem() {
private val roots =
ConcurrentFactoryMap.createMap<String, CoreJrtVirtualFile?> { jdkHomePath ->
val jdkHome = File(jdkHomePath)
val rootUri = URI.create(StandardFileSystems.JRT_PROTOCOL + ":/")
val jrtFsJar = loadJrtFsJar(jdkHome) ?: return@createMap null
/*
This ClassLoader actually lives as long as current thread due to ThreadLocal leak in jrt-fs,
See https://bugs.openjdk.java.net/browse/JDK-8260621
So that cache allows us to avoid creating too many classloaders for same JDK and reduce severity of that leak
*/
val classLoader = globalJrtFsClassLoaderCache.computeIfAbsent(jrtFsJar) {
URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
}
val fileSystem = FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader)
CoreJrtVirtualFile(this, jdkHomePath, fileSystem.getPath(""), parent = null)
}
override fun getProtocol(): String = StandardFileSystems.JRT_PROTOCOL
override fun findFileByPath(path: String): VirtualFile? {
val (jdkHomePath, pathInImage) = splitPath(path)
val root = roots[jdkHomePath] ?: return null
if (pathInImage.isEmpty()) return root
return root.findFileByRelativePath(pathInImage)
}
override fun refresh(asynchronous: Boolean) {}
override fun refreshAndFindFileByPath(path: String): VirtualFile? = findFileByPath(path)
fun clearRoots() {
roots.clear()
}
companion object {
private fun loadJrtFsJar(jdkHome: File): File? =
File(jdkHome, "lib/jrt-fs.jar").takeIf(File::exists)
fun isModularJdk(jdkHome: File): Boolean =
loadJrtFsJar(jdkHome) != null
fun splitPath(path: String): Pair<String, String> {
val separator = path.indexOf(URLUtil.JAR_SEPARATOR)
if (separator < 0) {
throw IllegalArgumentException("Path in CoreJrtFileSystem must contain a separator: $path")
}
val localPath = path.substring(0, separator)
val pathInJar = path.substring(separator + URLUtil.JAR_SEPARATOR.length)
return Pair(localPath, pathInJar)
}
private val globalJrtFsClassLoaderCache = ContainerUtil.createConcurrentWeakValueMap<File, URLClassLoader>()
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.util.io.URLUtil
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
internal class CoreJrtVirtualFile(
private val virtualFileSystem: CoreJrtFileSystem,
private val jdkHomePath: String,
private val path: Path,
private val parent: CoreJrtVirtualFile?,
) : VirtualFile() {
// TODO: catch IOException?
private val attributes: BasicFileAttributes get() = Files.readAttributes(path, BasicFileAttributes::class.java)
override fun getFileSystem(): VirtualFileSystem = virtualFileSystem
override fun getName(): String =
path.fileName.toString()
override fun getPath(): String =
FileUtil.toSystemIndependentName(jdkHomePath + URLUtil.JAR_SEPARATOR + path)
override fun isWritable(): Boolean = false
override fun isDirectory(): Boolean = Files.isDirectory(path)
override fun isValid(): Boolean = true
override fun getParent(): VirtualFile? = parent
private val myChildren by lazy { computeChildren() }
override fun getChildren(): Array<out VirtualFile> = myChildren
private fun computeChildren(): Array<out VirtualFile> {
val paths = try {
Files.newDirectoryStream(path).use(Iterable<Path>::toList)
} catch (e: IOException) {
emptyList<Path>()
}
return when {
paths.isEmpty() -> EMPTY_ARRAY
else -> paths.map { path -> CoreJrtVirtualFile(virtualFileSystem, jdkHomePath, path, parent = this) }.toTypedArray()
}
}
override fun getOutputStream(requestor: Any, newModificationStamp: Long, newTimeStamp: Long): OutputStream =
throw UnsupportedOperationException()
override fun contentsToByteArray(): ByteArray =
Files.readAllBytes(path)
override fun getTimeStamp(): Long =
attributes.lastModifiedTime().toMillis()
override fun getLength(): Long = attributes.size()
override fun refresh(asynchronous: Boolean, recursive: Boolean, postRunnable: Runnable?) {}
override fun getInputStream(): InputStream =
VfsUtilCore.inputStreamSkippingBOM(Files.newInputStream(path).buffered(), this)
override fun getModificationStamp(): Long = 0
override fun equals(other: Any?): Boolean =
other is CoreJrtVirtualFile && path == other.path && fileSystem == other.fileSystem
override fun hashCode(): Int =
path.hashCode()
}
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.vfs.VirtualFile
import java.io.InputStream
class CtSymClassVirtualFile(
private val parent: VirtualFile?,
private val file: VirtualFile
): VirtualFile() {
override fun getName() = file.name
override fun getFileSystem() = file.fileSystem
override fun getPath() = file.path
override fun isWritable() = file.isWritable
override fun isDirectory() = false
override fun isValid() = file.isValid
override fun getParent(): VirtualFile? = parent
override fun getChildren(): Array<VirtualFile>? = null
override fun getOutputStream(p0: Any?, p1: Long, p2: Long) = file.getOutputStream(p0, p1, p2)
override fun contentsToByteArray(): ByteArray = file.contentsToByteArray()
override fun getTimeStamp() = file.timeStamp
override fun getLength() = file.length
override fun refresh(p0: Boolean, p1: Boolean, p2: Runnable?) = file.refresh(p0, p1, p2)
override fun getInputStream(): InputStream? = file.inputStream
override fun getFileType(): FileType = JavaClassFileType.INSTANCE
override fun getModificationStamp(): Long = file.modificationStamp
}
@@ -0,0 +1,85 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.openapi.vfs.VirtualFile
import java.io.InputStream
class CtSymDirectoryContainer(
private val parent: VirtualFile?,
private val rootOrPackageParts: List<VirtualFile>,
private val packages: Map<String, Boolean>,
private val currentPackage: String,
private val moduleRoot: String,
private val skipPackageCheck: Boolean
) : VirtualFile() {
private val _path by lazy { parent?.path + currentPackage + "/" }
private val _fileSystem by lazy { parent!!.fileSystem }
private val _isValid by lazy { rootOrPackageParts.all { it.isValid } }
private val _children by lazy {
val children = rootOrPackageParts.flatMap { it.children?.toList() ?: emptyList() }
if (children.isEmpty()) return@lazy emptyArray()
val isExported = skipPackageCheck || packages.getOrDefault(currentPackage, false)
val containers = mutableMapOf<String, Pair<CtSymDirectoryContainer, MutableList<VirtualFile>>>()
children.mapNotNull { child ->
when {
child.isDirectory -> {
val childPackage = if (currentPackage.isEmpty()) child.name else currentPackage + "." + child.name
var addingEntry: CtSymDirectoryContainer? = null
if (skipPackageCheck || packages.contains(childPackage)) {
containers.getOrPut(childPackage) {
val list = mutableListOf<VirtualFile>()
CtSymDirectoryContainer(
this,
list,
packages,
childPackage,
moduleRoot,
skipPackageCheck
).also { addingEntry = it } to list
}.also { it.second.add(child) }
}
addingEntry
}
isExported -> CtSymClassVirtualFile(this, child)
else -> null
}
}.toTypedArray()
}
override fun getName() = currentPackage.substringAfterLast(".")
override fun getFileSystem() = _fileSystem
override fun getPath() = _path
override fun isWritable() = false
override fun isDirectory() = true
override fun isValid() = _isValid
override fun getParent(): VirtualFile? = parent
override fun getChildren(): Array<VirtualFile>? = _children
override fun getOutputStream(p0: Any?, p1: Long, p2: Long) = error("not supported")
override fun contentsToByteArray(): ByteArray = error("not supported")
override fun getTimeStamp() = error("not supported")
override fun getLength() = error("not supported")
override fun refresh(p0: Boolean, p1: Boolean, p2: Runnable?) {}
override fun getInputStream(): InputStream? = error("not supported")
}
@@ -0,0 +1,100 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder
import org.jetbrains.kotlin.storage.LockBasedStorageManager
class JavaModuleGraph(finder: JavaModuleFinder) {
private val module: (String) -> JavaModule? =
LockBasedStorageManager.NO_LOCKS.createMemoizedFunctionWithNullableValues(finder::findModule)
fun getAllDependencies(moduleNames: List<String>): LinkedHashSet<String> {
val visited = LinkedHashSet(moduleNames)
// Every module implicitly depends on java.base
visited += "java.base"
fun dfs(moduleName: String): Boolean {
// Automatic modules have no transitive exports, so we only consider explicit modules here
val moduleInfo = (module(moduleName) as? JavaModule.Explicit)?.moduleInfo ?: return false
for ((dependencyModuleName, isTransitive) in moduleInfo.requires) {
if (isTransitive && visited.add(dependencyModuleName)) {
dfs(dependencyModuleName)
}
}
return true
}
for (moduleName in moduleNames) {
val module = module(moduleName) ?: continue
when (module) {
is JavaModule.Automatic -> {
// Do nothing; all automatic modules should be added to compilation roots at call site as per java.lang.module javadoc
}
is JavaModule.Explicit -> {
for ((dependencyModuleName, isTransitive) in module.moduleInfo.requires) {
if (visited.add(dependencyModuleName)) {
val moduleExists = dfs(dependencyModuleName)
//ct.sym can miss some internal modules from non-transitive dependencies
if (!moduleExists && !isTransitive && module.isJdkModuleFromCtSym) visited.remove(dependencyModuleName)
}
}
}
else -> error("Unknown module: $module (${module.javaClass})")
}
}
return visited
}
fun reads(moduleName: String, dependencyName: String): Boolean {
if (moduleName == dependencyName || dependencyName == "java.base") return true
val visited = linkedSetOf<String>()
fun dfs(name: String): Boolean {
if (!visited.add(name)) return false
val module = module(name) ?: return false
when (module) {
is JavaModule.Automatic -> return true
is JavaModule.Explicit -> {
for ((dependencyModuleName, isTransitive) in module.moduleInfo.requires) {
if (dependencyModuleName == dependencyName) return true
if (isTransitive && dfs(dependencyName)) return true
}
return false
}
else -> error("Unsupported module type: $module")
}
}
val module = module(moduleName) ?: return false
when (module) {
is JavaModule.Automatic -> return true
is JavaModule.Explicit -> {
for ((dependencyModuleName) in module.moduleInfo.requires) {
if (dfs(dependencyModuleName)) return true
}
}
}
return dfs(moduleName)
}
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.openapi.util.SystemInfo
fun isAtLeastJava9(): Boolean {
return SystemInfo.isJavaVersionAtLeast(9, 0, 0)
}
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.cfg.ControlFlowInformationProviderImpl
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useInstance
object CompilerEnvironment : TargetEnvironment("Compiler") {
override fun configure(container: StorageComponentContainer) {
configureCompilerEnvironment(container)
container.useInstance(ControlFlowInformationProviderImpl.Factory)
}
}