Move IDEKotlinBinaryClassCache from intellij

This commit is contained in:
Ilya Kirillov
2021-12-16 18:18:13 +03:00
parent b222bf88a0
commit 127dc7670e
5 changed files with 239 additions and 7 deletions
@@ -0,0 +1,68 @@
/*
* 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.psi.stubs.file.builder
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.ClassFileViewProvider
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.findKotlinClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
object ClsClassFinder {
fun findMultifileClassParts(file: VirtualFile, classId: ClassId, partNames: List<String>): List<KotlinJvmBinaryClass> {
val packageFqName = classId.packageFqName
val partsFinder = DirectoryBasedClassFinder(file.parent!!, packageFqName)
return partNames.mapNotNull {
partsFinder.findKotlinClass(ClassId(packageFqName, Name.identifier(it.substringAfterLast('/'))))
}
}
/**
* Checks if this file is a compiled "internal" Kotlin class, i.e. a Kotlin class (not necessarily ABI-compatible with the current plugin)
* which should NOT be decompiled (and, as a result, shown under the library in the Project view, be searchable via Find class, etc.)
*/
fun isKotlinInternalCompiledFile(file: VirtualFile, fileContent: ByteArray? = null): Boolean {
// Don't crash on invalid files (EA-97751)
if (!file.isValid || fileContent?.size == 0 || !file.exists()) {
return false
}
val clsKotlinBinaryClassCache = ClsKotlinBinaryClassCache.getInstance()
if (!clsKotlinBinaryClassCache.isKotlinJvmCompiledFile(file, fileContent)) {
return false
}
val innerClass =
try {
if (fileContent == null) {
ClassFileViewProvider.isInnerClass(file)
} else {
ClassFileViewProvider.isInnerClass(file, fileContent)
}
} catch (exception: Exception) {
Logger
.getInstance("org.jetbrains.kotlin.analysis.decompiler.stub.file.ClsClassFinder.isKotlinInternalCompiledFile")
.debug(file.path, exception)
return false
}
if (innerClass) {
return true
}
val header = clsKotlinBinaryClassCache.getKotlinBinaryClassHeaderData(file, fileContent) ?: return false
if (header.classId.isLocal) return true
return header.kind == KotlinClassHeader.Kind.SYNTHETIC_CLASS ||
header.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART
}
}
@@ -0,0 +1,132 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.psi.stubs.file.builder
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileWithId
import com.intellij.reference.SoftReference
import org.jetbrains.kotlin.deserialization.common.jvm.impl.KotlinBinaryClassCache
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.name.ClassId
class ClsKotlinBinaryClassCache {
class KotlinBinaryClassHeaderData(
val classId: ClassId,
val kind: KotlinClassHeader.Kind,
val metadataVersion: JvmMetadataVersion,
val partNamesIfMultifileFacade: List<String>,
val packageName: String?
)
data class KotlinBinaryData(val isKotlinBinary: Boolean, val timestamp: Long, val headerData: KotlinBinaryClassHeaderData?)
/**
* Checks if this file is a compiled Kotlin class file (not necessarily ABI-compatible with the current plugin)
*/
fun isKotlinJvmCompiledFile(file: VirtualFile, fileContent: ByteArray? = null): Boolean {
if (file.extension != JavaClassFileType.INSTANCE!!.defaultExtension) {
return false
}
getKotlinBinaryFromCache(file)?.let {
return it.isKotlinBinary
}
return kotlinJvmBinaryClass(file, fileContent) != null
}
fun getKotlinBinaryClass(file: VirtualFile, fileContent: ByteArray? = null): KotlinJvmBinaryClass? {
val cached = getKotlinBinaryFromCache(file)
if (cached != null && !cached.isKotlinBinary) {
return null
}
return kotlinJvmBinaryClass(file, fileContent)
}
private fun kotlinJvmBinaryClass(
file: VirtualFile,
fileContent: ByteArray?
): KotlinJvmBinaryClass? {
val kotlinBinaryClass = KotlinBinaryClassCache.getKotlinBinaryClassOrClassFileContent(file, fileContent)?.toKotlinJvmBinaryClass()
val isKotlinBinaryClass = kotlinBinaryClass != null
if (file is VirtualFileWithId) {
attributeService.writeBooleanAttribute(KOTLIN_IS_COMPILED_FILE_ATTRIBUTE, file, isKotlinBinaryClass)
}
if (isKotlinBinaryClass) {
val headerInfo = createHeaderInfo(kotlinBinaryClass!!)
file.putUserData(KOTLIN_BINARY_DATA_KEY, SoftReference(KotlinBinaryData(isKotlinBinaryClass, file.timeStamp, headerInfo)))
}
return kotlinBinaryClass
}
fun getKotlinBinaryClassHeaderData(file: VirtualFile, fileContent: ByteArray? = null): KotlinBinaryClassHeaderData? {
getKotlinBinaryFromCache(file)?.let { cached ->
if (!cached.isKotlinBinary) {
return null
}
if (cached.headerData != null) {
return cached.headerData
}
}
val kotlinBinaryClass = kotlinJvmBinaryClass(file, fileContent) ?: return null
return createHeaderInfo(kotlinBinaryClass)
}
private val attributeService = ApplicationManager.getApplication().getService(FileAttributeService::class.java)
private fun createHeaderInfo(kotlinBinaryClass: KotlinJvmBinaryClass): KotlinBinaryClassHeaderData {
val classId = kotlinBinaryClass.classId
return kotlinBinaryClass.classHeader.toLightHeader(classId)
}
private fun KotlinClassHeader.toLightHeader(classId: ClassId) =
KotlinBinaryClassHeaderData(
classId, kind, metadataVersion, multifilePartNames, packageName
)
private val KOTLIN_IS_COMPILED_FILE_ATTRIBUTE: String = "kotlin-is-binary-compiled".apply {
attributeService.register(this, 2)
}
private val KOTLIN_BINARY_DATA_KEY = Key.create<SoftReference<KotlinBinaryData>>(KOTLIN_IS_COMPILED_FILE_ATTRIBUTE)
private fun getKotlinBinaryFromCache(file: VirtualFile): KotlinBinaryData? {
val userData = file.getUserData(KOTLIN_BINARY_DATA_KEY)?.get()
if (userData != null && userData.timestamp == file.timeStamp) {
return userData
}
val isKotlinBinaryAttribute = if (file is VirtualFileWithId) {
attributeService.readBooleanAttribute(KOTLIN_IS_COMPILED_FILE_ATTRIBUTE, file)
} else {
null
}
if (isKotlinBinaryAttribute != null) {
val isKotlinBinary = isKotlinBinaryAttribute.value
val kotlinBinaryData = KotlinBinaryData(isKotlinBinary, file.timeStamp, null)
if (isKotlinBinary) {
file.putUserData(KOTLIN_BINARY_DATA_KEY, SoftReference(kotlinBinaryData))
}
return kotlinBinaryData
}
return null
}
companion object {
fun getInstance(): ClsKotlinBinaryClassCache =
ApplicationManager.getApplication().getService(ClsKotlinBinaryClassCache::class.java)
}
}
@@ -9,6 +9,7 @@ import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.classId
import org.jetbrains.kotlin.load.kotlin.KotlinClassFinder
import org.jetbrains.kotlin.load.kotlin.KotlinClassFinder.Result.KotlinClass
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import java.io.InputStream
@@ -26,7 +27,7 @@ class DirectoryBasedClassFinder(
val targetName = classId.relativeClassName.pathSegments().joinToString("$", postfix = ".class")
val virtualFile = packageDirectory.findChild(targetName)
if (virtualFile != null && isKotlinWithCompatibleAbiVersion(virtualFile)) {
return IDEKotlinBinaryClassCache.getInstance().getKotlinBinaryClass(virtualFile)?.let(::KotlinClass)
return ClsKotlinBinaryClassCache.getInstance().getKotlinBinaryClass(virtualFile)?.let(::KotlinClass)
}
return null
}
@@ -45,10 +46,10 @@ class DirectoryBasedClassFinder(
* Checks if this file is a compiled Kotlin class file ABI-compatible with the current plugin
*/
private fun isKotlinWithCompatibleAbiVersion(file: VirtualFile): Boolean {
val ideKotlinBinaryClassCache = IDEKotlinBinaryClassCache.getInstance()
if (!ideKotlinBinaryClassCache.isKotlinJvmCompiledFile(file)) return false
val clsKotlinBinaryClassCache = ClsKotlinBinaryClassCache.getInstance()
if (!clsKotlinBinaryClassCache.isKotlinJvmCompiledFile(file)) return false
val kotlinClass = ideKotlinBinaryClassCache.getKotlinBinaryClassHeaderData(file)
val kotlinClass = clsKotlinBinaryClassCache.getKotlinBinaryClassHeaderData(file)
return kotlinClass != null && kotlinClass.metadataVersion.isCompatible()
}
@@ -0,0 +1,30 @@
/*
* 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.psi.stubs.file.builder
import com.intellij.openapi.vfs.VirtualFile
import java.io.DataInput
import java.io.DataOutput
data class CachedAttributeData<out T>(val value: T, val timeStamp: Long)
interface FileAttributeService {
fun register(id: String, version: Int, fixedSize: Boolean = true) {}
fun <T : Enum<T>> writeEnumAttribute(id: String, file: VirtualFile, value: T): CachedAttributeData<T> =
CachedAttributeData(value, timeStamp = file.timeStamp)
fun <T : Enum<T>> readEnumAttribute(id: String, file: VirtualFile, klass: Class<T>): CachedAttributeData<T>? = null
fun writeBooleanAttribute(id: String, file: VirtualFile, value: Boolean): CachedAttributeData<Boolean> =
CachedAttributeData(value, timeStamp = file.timeStamp)
fun readBooleanAttribute(id: String, file: VirtualFile): CachedAttributeData<Boolean>? = null
fun <T> write(file: VirtualFile, id: String, value: T, writeValueFun: (DataOutput, T) -> Unit): CachedAttributeData<T>
fun <T> read(file: VirtualFile, id: String, readValueFun: (DataInput) -> T): CachedAttributeData<T>?
}
@@ -13,6 +13,7 @@ import com.intellij.psi.stubs.PsiFileStub
import com.intellij.util.indexing.FileContent
import org.jetbrains.kotlin.SpecialJvmAnnotations
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.deserialization.common.jvm.impl.VirtualFileKotlinClass
import org.jetbrains.kotlin.load.kotlin.*
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.metadata.ProtoBuf
@@ -37,7 +38,7 @@ open class KotlinClsStubBuilder : ClsStubBuilder() {
override fun buildFileStub(content: FileContent): PsiFileStub<*>? {
val virtualFile = content.file
if (isKotlinInternalCompiledFile(virtualFile, content.content)) {
if (ClsClassFinder.isKotlinInternalCompiledFile(virtualFile, content.content)) {
return null
}
@@ -51,7 +52,7 @@ open class KotlinClsStubBuilder : ClsStubBuilder() {
}
private fun doBuildFileStub(file: VirtualFile, fileContent: ByteArray): PsiFileStub<KtFile>? {
val kotlinClass: KotlinJvmBinaryClass = IDEKotlinBinaryClassCache.getInstance().getKotlinBinaryClass(file, fileContent)
val kotlinClass = ClsKotlinBinaryClassCache.getInstance().getKotlinBinaryClass(file, fileContent)
?: error("Can't find binary class for Kotlin file: $file")
val header = kotlinClass.classHeader
val classId = kotlinClass.classId
@@ -63,7 +64,7 @@ open class KotlinClsStubBuilder : ClsStubBuilder() {
val components = createStubBuilderComponents(file, packageFqName, fileContent)
if (header.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS) {
val partFiles = findMultifileClassParts(file, classId, header.multifilePartNames)
val partFiles = ClsClassFinder.findMultifileClassParts(file, classId, header.multifilePartNames)
return createMultifileClassStub(header, partFiles, classId.asSingleFqName(), components)
}