Move Klib-related logic from idea-native module

This commit is contained in:
Dmitry Savvinov
2020-03-03 19:55:57 +03:00
committed by Dmitriy Dolovov
parent 417554dca0
commit 26ab9b4fc9
15 changed files with 88 additions and 82 deletions
@@ -5,40 +5,17 @@
package org.jetbrains.kotlin.ide.konan
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.psi.PsiFileFactory
import com.intellij.psi.SingleRootFileViewProvider
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.stubs.PsiFileStub
import com.intellij.testFramework.LightVirtualFile
import com.intellij.util.PathUtil
import org.jetbrains.kotlin.ide.konan.decompiler.KlibLoadingMetadataCache
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.decompiler.textBuilder.LoggingErrorReporter
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.idea.klib.KlibLoadingMetadataCache
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.impl.KotlinLibraryImpl
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.library.metadata.PackageAccessHandler
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
import org.jetbrains.kotlin.konan.file.File as KFile
fun createFileStub(project: Project, text: String): PsiFileStub<*> {
val virtualFile = LightVirtualFile("dummy.kt", KotlinFileType.INSTANCE, text)
virtualFile.language = KotlinLanguage.INSTANCE
SingleRootFileViewProvider.doNotCheckFileSizeLimit(virtualFile)
val psiFileFactory = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl
val file = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, false, false)!!
return KtStubElementTypes.FILE.builder.buildStubTree(file) as PsiFileStub<*>
}
fun createLoggingErrorReporter(log: Logger) = LoggingErrorReporter(log)
internal object CachingIdeKonanLibraryMetadataLoader : PackageAccessHandler {
override fun loadModuleHeader(library: KotlinLibrary): KlibMetadataProtoBuf.Header {
val virtualFile = getVirtualFile(library, library.moduleHeaderFile)
@@ -1,16 +0,0 @@
/*
* 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.ide.konan.decompiler
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.decompiler.KotlinDecompiledFileViewProvider
import org.jetbrains.kotlin.idea.decompiler.KtDecompiledFile
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledText
class KlibDecompiledFile(
provider: KotlinDecompiledFileViewProvider,
text: (VirtualFile) -> DecompiledText
) : KtDecompiledFile(provider, text)
@@ -1,108 +0,0 @@
/*
* 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.ide.konan.decompiler
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.BaseComponent
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ContainerUtil.createConcurrentWeakValueMap
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.library.metadata.parseModuleHeader
import org.jetbrains.kotlin.library.metadata.parsePackageFragment
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import java.io.IOException
import java.util.*
class KlibLoadingMetadataCache : BaseComponent {
companion object {
@JvmStatic
fun getInstance(): KlibLoadingMetadataCache =
ApplicationManager.getApplication().getComponent(KlibLoadingMetadataCache::class.java)
}
// Use special CacheKey class instead of VirtualFile for cache keys. Certain types of VirtualFiles (for example, obtained from JarFileSystem)
// do not compare path (url) and modification stamp in equals() method.
private data class CacheKey(
val url: String,
val modificationStamp: Long
) {
constructor(virtualFile: VirtualFile) : this(virtualFile.url, virtualFile.modificationStamp)
}
// ConcurrentWeakValueHashMap does not allow null values.
private class CacheValue<T : Any>(val value: T?)
private val packageFragmentCache = createConcurrentWeakValueMap<CacheKey, CacheValue<ProtoBuf.PackageFragment>>()
private val moduleHeaderCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataProtoBuf.Header>>()
private val libraryMetadataVersionCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataVersion>>()
fun getCachedPackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? {
check(packageFragmentFile.extension == KLIB_METADATA_FILE_EXTENSION) {
"Not a package metadata file: $packageFragmentFile"
}
return packageFragmentCache.computeIfAbsent(CacheKey(packageFragmentFile)) {
CacheValue(computePackageFragment(packageFragmentFile))
}.value
}
fun getCachedModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? {
check(moduleHeaderFile.name == KLIB_MODULE_METADATA_FILE_NAME) {
"Not a module header file: $moduleHeaderFile"
}
return moduleHeaderCache.computeIfAbsent(CacheKey(moduleHeaderFile)) {
CacheValue(computeModuleHeader(moduleHeaderFile))
}.value
}
private fun isMetadataCompatible(libraryRoot: VirtualFile): Boolean {
val manifestFile = libraryRoot.findChild(KLIB_MANIFEST_FILE_NAME) ?: return false
val metadataVersion = libraryMetadataVersionCache.computeIfAbsent(CacheKey(manifestFile)) {
CacheValue(computeLibraryMetadataVersion(manifestFile))
}.value ?: return false
return metadataVersion.isCompatible()
}
private fun computePackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? {
if (!isMetadataCompatible(packageFragmentFile.parent.parent.parent))
return null
return try {
parsePackageFragment(packageFragmentFile.contentsToByteArray(false))
} catch (_: IOException) {
null
}
}
private fun computeModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? {
if (!isMetadataCompatible(moduleHeaderFile.parent.parent))
return null
return try {
parseModuleHeader(moduleHeaderFile.contentsToByteArray(false))
} catch (_: IOException) {
null
}
}
private fun computeLibraryMetadataVersion(manifestFile: VirtualFile): KlibMetadataVersion? = try {
val versioning = Properties().apply { manifestFile.inputStream.use { load(it) } }.readKonanLibraryVersioning()
versioning.metadataVersion?.let(BinaryVersion.Companion::parseVersionArray)?.let(::KlibMetadataVersion)
} catch (_: IOException) {
// ignore and cache null value
null
} catch (_: IllegalArgumentException) {
// ignore and cache null value
null
}
}
@@ -1,9 +0,0 @@
/*
* 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.ide.konan.decompiler
import org.jetbrains.kotlin.idea.util.KotlinBinaryExtension
class KlibMetaBinary : KotlinBinaryExtension(KlibMetaFileType)
@@ -1,23 +0,0 @@
/*
* 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.ide.konan.decompiler
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.ide.konan.KotlinNativeBundle
import org.jetbrains.kotlin.library.KLIB_METADATA_FILE_EXTENSION
object KlibMetaFileType : FileType {
override fun getName() = "KNM"
override fun getDescription() = KotlinNativeBundle.message("kotlin.native.metadata.short")
override fun getDefaultExtension() = KLIB_METADATA_FILE_EXTENSION
override fun getIcon(): Nothing? = null
override fun isBinary() = true
override fun isReadOnly() = true
override fun getCharset(file: VirtualFile, content: ByteArray): Nothing? = null
const val STUB_VERSION = 2
}
@@ -1,135 +0,0 @@
/*
* 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.ide.konan.decompiler
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.psi.compiled.ClassFileDecompilers
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.decompiler.KotlinDecompiledFileViewProvider
import org.jetbrains.kotlin.idea.decompiler.common.createIncompatibleAbiVersionDecompiledText
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledText
import org.jetbrains.kotlin.idea.decompiler.textBuilder.buildDecompiledText
import org.jetbrains.kotlin.idea.decompiler.textBuilder.defaultDecompilerRendererOptions
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.metadata.deserialization.NameResolverImpl
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.ClassDeserializer
import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.IOException
abstract class KlibMetadataDecompiler<out V : BinaryVersion>(
private val fileType: FileType,
private val serializerProtocol: () -> SerializerExtensionProtocol,
private val flexibleTypeDeserializer: FlexibleTypeDeserializer,
private val expectedBinaryVersion: () -> V,
private val invalidBinaryVersion: () -> V,
stubVersion: Int
) : ClassFileDecompilers.Full() {
private val metadataStubBuilder: KlibMetadataStubBuilder =
KlibMetadataStubBuilder(
stubVersion,
fileType,
serializerProtocol,
::readFileSafely
)
private val renderer: DescriptorRenderer by lazy {
DescriptorRenderer.withOptions { defaultDecompilerRendererOptions() }
}
protected abstract fun doReadFile(file: VirtualFile): FileWithMetadata?
override fun accepts(file: VirtualFile) = file.fileType == fileType
override fun getStubBuilder() = metadataStubBuilder
override fun createFileViewProvider(file: VirtualFile, manager: PsiManager, physical: Boolean) =
KotlinDecompiledFileViewProvider(manager, file, physical) { provider ->
KlibDecompiledFile(
provider,
::buildDecompiledText
)
}
private fun readFileSafely(file: VirtualFile): FileWithMetadata? {
if (!file.isValid) return null
return try {
doReadFile(file)
} catch (e: IOException) {
// This is needed because sometimes we're given VirtualFile instances that point to non-existent .jar entries.
// Such files are valid (isValid() returns true), but an attempt to read their contents results in a FileNotFoundException.
// Note that although calling "refresh()" instead of catching an exception would seem more correct here,
// it's not always allowed and also is likely to degrade performance
null
}
}
private fun buildDecompiledText(virtualFile: VirtualFile): DecompiledText {
assert(virtualFile.fileType == fileType) { "Unexpected file type ${virtualFile.fileType}" }
val file = readFileSafely(virtualFile)
return when (file) {
is FileWithMetadata.Incompatible -> createIncompatibleAbiVersionDecompiledText(expectedBinaryVersion(), file.version)
is FileWithMetadata.Compatible -> decompiledText(
file,
serializerProtocol(),
flexibleTypeDeserializer,
renderer
)
null -> createIncompatibleAbiVersionDecompiledText(expectedBinaryVersion(), invalidBinaryVersion())
}
}
}
sealed class FileWithMetadata {
class Incompatible(val version: BinaryVersion) : FileWithMetadata()
open class Compatible(
val proto: ProtoBuf.PackageFragment,
serializerProtocol: SerializerExtensionProtocol // TODO: Is it required?
) : FileWithMetadata() {
val nameResolver = NameResolverImpl(proto.strings, proto.qualifiedNames)
val packageFqName = FqName(proto.getExtension(KlibMetadataProtoBuf.fqName))
open val classesToDecompile: List<ProtoBuf.Class> =
proto.class_List.filter { proto ->
val classId = nameResolver.getClassId(proto.fqName)
!classId.isNestedClass && classId !in ClassDeserializer.BLACK_LIST
}
}
}
//todo: this function is extracted for KotlinNativeMetadataStubBuilder, that's the difference from Big Kotlin.
fun decompiledText(
file: FileWithMetadata.Compatible,
serializerProtocol: SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer,
renderer: DescriptorRenderer
): DecompiledText {
val packageFqName = file.packageFqName
val resolver = KlibMetadataDeserializerForDecompiler(
packageFqName, file.proto, file.nameResolver,
serializerProtocol, flexibleTypeDeserializer
)
val declarations = arrayListOf<DeclarationDescriptor>()
declarations.addAll(resolver.resolveDeclarationsInFacade(packageFqName))
for (classProto in file.classesToDecompile) {
val classId = file.nameResolver.getClassId(classProto.fqName)
declarations.addIfNotNull(resolver.resolveTopLevelClass(classId))
}
return buildDecompiledText(packageFqName, declarations, renderer)
}
@@ -1,73 +0,0 @@
/*
* 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.ide.konan.decompiler
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.NotFoundClasses
import org.jetbrains.kotlin.ide.konan.createLoggingErrorReporter
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DeserializerForDecompilerBase
import org.jetbrains.kotlin.idea.decompiler.textBuilder.ResolveEverythingToKotlinAnyLocalClassifierResolver
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.library.metadata.KlibMetadataClassDataFinder
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.sam.SamConversionResolverImpl
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.*
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPackageMemberScope
class KlibMetadataDeserializerForDecompiler(
packageFqName: FqName,
private val proto: ProtoBuf.PackageFragment,
private val nameResolver: NameResolver,
serializerProtocol: SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer
) : DeserializerForDecompilerBase(packageFqName) {
override val builtIns: KotlinBuiltIns get() = DefaultBuiltIns.Instance
override val deserializationComponents: DeserializationComponents
init {
val notFoundClasses = NotFoundClasses(storageManager, moduleDescriptor)
deserializationComponents = DeserializationComponents(
storageManager, moduleDescriptor, DeserializationConfiguration.Default,
KlibMetadataClassDataFinder(proto, nameResolver),
AnnotationAndConstantLoaderImpl(moduleDescriptor, notFoundClasses, serializerProtocol), packageFragmentProvider,
ResolveEverythingToKotlinAnyLocalClassifierResolver(builtIns), createLoggingErrorReporter(LOG),
LookupTracker.DO_NOTHING, flexibleTypeDeserializer, emptyList(), notFoundClasses, ContractDeserializer.DEFAULT,
extensionRegistryLite = serializerProtocol.extensionRegistry,
samConversionResolver = SamConversionResolverImpl(storageManager, samWithReceiverResolvers = emptyList())
)
}
override fun resolveDeclarationsInFacade(facadeFqName: FqName): List<DeclarationDescriptor> {
assert(facadeFqName == directoryPackageFqName) {
"Was called for $facadeFqName; only members of $directoryPackageFqName package are expected."
}
val membersScope = DeserializedPackageMemberScope(
createDummyPackageFragment(facadeFqName),
proto.`package`,
nameResolver,
KlibMetadataVersion.INSTANCE,
containerSource = null,
components = deserializationComponents
) { emptyList() }
return membersScope.getContributedDescriptors().toList()
}
companion object {
private val LOG = Logger.getInstance(KlibMetadataDeserializerForDecompiler::class.java)
}
}
@@ -1,50 +0,0 @@
/*
* 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.ide.konan.decompiler
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.compiled.ClsStubBuilder
import com.intellij.psi.impl.compiled.ClassFileStubBuilder
import com.intellij.psi.stubs.PsiFileStub
import com.intellij.util.indexing.FileContent
import org.jetbrains.kotlin.ide.konan.createFileStub
import org.jetbrains.kotlin.idea.decompiler.stubBuilder.createIncompatibleAbiVersionFileStub
import org.jetbrains.kotlin.idea.decompiler.textBuilder.defaultDecompilerRendererOptions
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.js.DynamicTypeDeserializer
open class KlibMetadataStubBuilder(
private val version: Int,
private val fileType: FileType,
private val serializerProtocol: () -> SerializerExtensionProtocol,
private val readFile: (VirtualFile) -> FileWithMetadata?
) : ClsStubBuilder() {
override fun getStubVersion() = ClassFileStubBuilder.STUB_VERSION + version
override fun buildFileStub(content: FileContent): PsiFileStub<*>? {
val virtualFile = content.file
assert(virtualFile.fileType == fileType) { "Unexpected file type ${virtualFile.fileType}" }
val file = readFile(virtualFile) ?: return null
return when (file) {
is FileWithMetadata.Incompatible -> createIncompatibleAbiVersionFileStub()
is FileWithMetadata.Compatible -> { //todo: this part is implemented in our own way
val renderer = DescriptorRenderer.withOptions { defaultDecompilerRendererOptions() }
val ktFileText = decompiledText(
file,
serializerProtocol(),
DynamicTypeDeserializer,
renderer
)
createFileStub(content.project, ktFileText.text)
}
}
}
}
@@ -1,13 +0,0 @@
/*
* 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.ide.konan.decompiler
import com.intellij.openapi.fileTypes.FileTypeConsumer
import com.intellij.openapi.fileTypes.FileTypeFactory
class KotlinNativeMetaFileTypeFactory : FileTypeFactory() {
override fun createFileTypes(consumer: FileTypeConsumer) = consumer.consume(KlibMetaFileType, KlibMetaFileType.defaultExtension)
}
@@ -7,6 +7,10 @@ package org.jetbrains.kotlin.ide.konan.decompiler
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.idea.klib.FileWithMetadata
import org.jetbrains.kotlin.idea.klib.KlibLoadingMetadataCache
import org.jetbrains.kotlin.idea.klib.KlibMetaFileType
import org.jetbrains.kotlin.idea.klib.KlibMetadataDecompiler
import org.jetbrains.kotlin.library.metadata.KlibMetadataSerializerProtocol
import org.jetbrains.kotlin.serialization.js.DynamicTypeDeserializer
@@ -19,7 +23,8 @@ class KotlinNativeMetadataDecompiler : KlibMetadataDecompiler<KlibMetadataVersio
KlibMetaFileType.STUB_VERSION
) {
override fun doReadFile(file: VirtualFile): FileWithMetadata? {
val fragment = KlibLoadingMetadataCache.getInstance().getCachedPackageFragment(file) ?: return null
val fragment = KlibLoadingMetadataCache
.getInstance().getCachedPackageFragment(file) ?: return null
return FileWithMetadata.Compatible(fragment, KlibMetadataSerializerProtocol) //todo: check version compatibility
}
}
@@ -6,31 +6,9 @@
package org.jetbrains.kotlin.ide.konan.index
import com.intellij.util.indexing.FileBasedIndex
import org.jetbrains.kotlin.ide.konan.decompiler.KlibLoadingMetadataCache
import org.jetbrains.kotlin.ide.konan.decompiler.KlibMetaFileType
import org.jetbrains.kotlin.idea.klib.KlibLoadingMetadataCache
import org.jetbrains.kotlin.idea.klib.KlibMetaFileType
import org.jetbrains.kotlin.idea.vfilefinder.KotlinFileIndexBase
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.name.FqName
object KlibMetaFileIndex : KotlinFileIndexBase<KlibMetaFileIndex>(KlibMetaFileIndex::class.java) {
override fun getIndexer() = INDEXER
override fun getInputFilter() = FileBasedIndex.InputFilter { it.fileType === KlibMetaFileType }
override fun getVersion() = VERSION
// This is to express intention to index all Kotlin/Native metadata files irrespectively to file size:
override fun getFileTypesWithSizeLimitNotApplicable() = listOf(KlibMetaFileType)
private const val VERSION = 4
/*todo: check version?!*/
private val INDEXER = indexer { fileContent ->
val fragment = KlibLoadingMetadataCache.getInstance().getCachedPackageFragment(fileContent.file)
if (fragment != null)
FqName(fragment.getExtension(KlibMetadataProtoBuf.fqName))
else
null
}
}