Move K/N decompilers and stub builders to kotlin.git

KTIJ-26526
This commit is contained in:
Pavel Kirpichenkov
2023-08-03 19:28:09 +03:00
committed by Space Team
parent c5637ccc7e
commit d7356afe1e
16 changed files with 700 additions and 0 deletions
@@ -10,6 +10,9 @@ dependencies {
implementation(project(":analysis:light-classes-base"))
implementation(project(":analysis:decompiled:decompiler-to-stubs"))
implementation(project(":analysis:decompiled:decompiler-to-file-stubs"))
implementation(project(":kotlin-util-klib-metadata"))
implementation(project(":js:js.serializer"))
implementation(project(":kotlin-util-klib-metadata"))
implementation(intellijCore())
testImplementation(projectTests(":compiler:tests-common"))
@@ -0,0 +1,50 @@
// Copyright 2000-2022 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.analysis.decompiler.psi
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.PathUtil
import org.jetbrains.kotlin.konan.file.File as KFile
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
object CachingIdeKlibMetadataLoader : PackageAccessHandler {
override fun loadModuleHeader(library: KotlinLibrary): KlibMetadataProtoBuf.Header {
val virtualFile = getVirtualFile(library, library.moduleHeaderFile)
return virtualFile?.let { cache.getCachedModuleHeader(virtualFile) } ?: KlibMetadataProtoBuf.Header.getDefaultInstance()
}
override fun loadPackageFragment(library: KotlinLibrary, packageFqName: String, partName: String): ProtoBuf.PackageFragment {
val virtualFile = getVirtualFile(library, library.packageFragmentFile(packageFqName, partName))
return virtualFile?.let { cache.getCachedPackageFragment(virtualFile) } ?: ProtoBuf.PackageFragment.getDefaultInstance()
}
private fun getVirtualFile(library: KotlinLibrary, file: KFile): VirtualFile? =
if (library.isZipped) asJarFileSystemFile(library.libraryFile, file) else asLocalFile(file)
private fun asJarFileSystemFile(jarFile: KFile, localFile: KFile): VirtualFile? {
val fullPath = jarFile.absolutePath + "!" + PathUtil.toSystemIndependentName(localFile.path)
return StandardFileSystems.jar().findFileByPath(fullPath)
}
private fun asLocalFile(localFile: KFile): VirtualFile? {
val fullPath = localFile.absolutePath
return StandardFileSystems.local().findFileByPath(fullPath)
}
private val cache
get() = KlibLoadingMetadataCache.getInstance()
private val KotlinLibrary.moduleHeaderFile
get() = (this as KotlinLibraryImpl).metadata.access.layout.moduleHeaderFile
private fun KotlinLibrary.packageFragmentFile(packageFqName: String, partName: String) =
(this as KotlinLibraryImpl).metadata.access.layout.packageFragmentFile(packageFqName, partName)
private val KotlinLibrary.isZipped
get() = (this as KotlinLibraryImpl).base.access.layout.isZipped
}
@@ -0,0 +1,77 @@
/*
* 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.analysis.decompiler.psi
import com.intellij.openapi.fileTypes.FileType
import org.jetbrains.kotlin.analysis.decompiler.psi.text.DecompiledText
import org.jetbrains.kotlin.analysis.decompiler.psi.text.buildDecompiledText
import org.jetbrains.kotlin.analysis.decompiler.psi.text.defaultDecompilerRendererOptions
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
import org.jetbrains.kotlin.utils.addIfNotNull
abstract class Fe10KlibMetadataDecompiler<out V : BinaryVersion>(
fileType: FileType,
serializerProtocol: () -> SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer,
expectedBinaryVersion: () -> V,
invalidBinaryVersion: () -> V,
stubVersion: Int
) : KlibMetadataDecompiler<V>(
fileType,
serializerProtocol,
flexibleTypeDeserializer,
expectedBinaryVersion,
invalidBinaryVersion
) {
private val renderer: DescriptorRenderer by lazy {
DescriptorRenderer.withOptions { defaultDecompilerRendererOptions() }
}
override val metadataStubBuilder: KlibMetadataStubBuilder by lazy {
Fe10KlibMetadataStubBuilder(stubVersion, fileType, serializerProtocol, ::readFileSafely)
}
override fun getDecompiledText(
file: FileWithMetadata.Compatible,
serializerProtocol: SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer
): DecompiledText {
return decompiledText(file, serializerProtocol, flexibleTypeDeserializer, renderer)
}
}
/**
* This function is extracted for [Fe10KlibMetadataDecompiler], [Fe10KlibMetadataStubBuilder] and [K2KlibMetadataDecompiler].
* TODO: K2 shouldn't use descriptor renderer for building decompiled text.
* Note that decompiled text is not used for building stubs in K2.
* That's why in K2 it is important to preserve declaration order during deserialization to not get PSI vs. stubs mismatch.
*/
internal fun decompiledText(
file: FileWithMetadata.Compatible,
serializerProtocol: SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer,
renderer: DescriptorRenderer,
deserializationConfiguration: DeserializationConfiguration = DeserializationConfiguration.Default
): DecompiledText {
val packageFqName = file.packageFqName
val resolver = KlibMetadataDeserializerForDecompiler(
packageFqName, file.proto, file.nameResolver,
serializerProtocol, flexibleTypeDeserializer,
deserializationConfiguration,
)
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)
}
@@ -0,0 +1,43 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.analysis.decompiler.psi
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.project.Project
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.indexing.FileContent
import org.jetbrains.kotlin.analysis.decompiler.psi.text.defaultDecompilerRendererOptions
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.stubs.elements.KtFileElementType
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.js.DynamicTypeDeserializer
open class Fe10KlibMetadataStubBuilder(
version: Int,
fileType: FileType,
private val serializerProtocol: () -> SerializerExtensionProtocol,
readFile: (VirtualFile) -> FileWithMetadata?
) : KlibMetadataStubBuilder(version, fileType, readFile) {
private val renderer: DescriptorRenderer = DescriptorRenderer.withOptions { defaultDecompilerRendererOptions() }
override fun buildMetadataFileStub(fileWithMetadata: FileWithMetadata.Compatible, fileContent: FileContent): PsiFileStub<*> {
val ktFileText = decompiledText(fileWithMetadata, serializerProtocol(), DynamicTypeDeserializer, renderer)
return createFileStub(fileContent.project, ktFileText.text)
}
private 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 KtFileElementType.INSTANCE.builder.buildStubTree(file) as PsiFileStub<*>
}
}
@@ -0,0 +1,35 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.analysis.decompiler.psi
import com.intellij.openapi.vfs.VirtualFile
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.serialization.deserialization.ClassDeserializer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
sealed class FileWithMetadata {
class Incompatible(val version: BinaryVersion) : FileWithMetadata()
open class Compatible(val proto: ProtoBuf.PackageFragment, val version: BinaryVersion) : 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
}
}
companion object {
fun forPackageFragment(packageFragment: VirtualFile): FileWithMetadata? {
val klibMetadataLoadingCache = KlibLoadingMetadataCache.getInstance()
val (fragment, version) = klibMetadataLoadingCache.getCachedPackageFragmentWithVersion(packageFragment)
if (fragment == null || version == null) return null
return Compatible(fragment, version) //todo: check version compatibility
}
}
}
@@ -0,0 +1,50 @@
/*
* 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.analysis.decompiler.psi
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.analysis.decompiler.psi.text.DecompiledText
import org.jetbrains.kotlin.analysis.decompiler.psi.text.defaultDecompilerRendererOptions
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer
abstract class K2KlibMetadataDecompiler<out V : BinaryVersion>(
private val fileType: FileType,
private val serializerProtocol: () -> SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer,
expectedBinaryVersion: () -> V,
invalidBinaryVersion: () -> V,
private val stubVersion: Int,
) : KlibMetadataDecompiler<V>(fileType, serializerProtocol, flexibleTypeDeserializer, expectedBinaryVersion, invalidBinaryVersion) {
private val renderer: DescriptorRenderer by lazy {
DescriptorRenderer.withOptions { defaultDecompilerRendererOptions() }
}
override val metadataStubBuilder: KlibMetadataStubBuilder by lazy {
K2KlibMetadataStubBuilder(stubVersion, fileType, serializerProtocol, ::readFileSafely)
}
override fun doReadFile(file: VirtualFile): FileWithMetadata? {
return FileWithMetadata.forPackageFragment(file)
}
override fun getDecompiledText(
file: FileWithMetadata.Compatible,
serializerProtocol: SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer
): DecompiledText {
val deserializationConfiguration = object : DeserializationConfiguration {
// K2 stubs are built from protobuf directly, declarations there are sorted
// Breaking the order causes psi vs stub mismatch
override val preserveDeclarationsOrdering: Boolean get() = true
}
return decompiledText(file, serializerProtocol, flexibleTypeDeserializer, renderer, deserializationConfiguration)
}
}
@@ -0,0 +1,54 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.analysis.decompiler.psi
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.stubs.PsiFileStub
import com.intellij.util.indexing.FileContent
import org.jetbrains.kotlin.analysis.decompiler.stub.ClsStubBuilderComponents
import org.jetbrains.kotlin.analysis.decompiler.stub.createClassStub
import org.jetbrains.kotlin.analysis.decompiler.stub.createFileStub
import org.jetbrains.kotlin.analysis.decompiler.stub.createPackageDeclarationsStubs
import org.jetbrains.kotlin.analysis.decompiler.stub.file.AnnotationLoaderForStubBuilderImpl
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.ProtoBasedClassDataFinder
import org.jetbrains.kotlin.serialization.deserialization.ProtoContainer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
class K2KlibMetadataStubBuilder(
private val version: Int,
fileType: FileType,
private val serializerProtocol: () -> SerializerExtensionProtocol,
readFile: (VirtualFile) -> FileWithMetadata?,
) : KlibMetadataStubBuilder(version, fileType, readFile) {
override fun getStubVersion(): Int = version
override fun buildMetadataFileStub(fileWithMetadata: FileWithMetadata.Compatible, fileContent: FileContent): PsiFileStub<*> {
val packageProto = fileWithMetadata.proto.`package`
val packageFqName = fileWithMetadata.packageFqName
val nameResolver = fileWithMetadata.nameResolver
val protocol = serializerProtocol()
val components = ClsStubBuilderComponents(
ProtoBasedClassDataFinder(fileWithMetadata.proto, nameResolver, fileWithMetadata.version),
AnnotationLoaderForStubBuilderImpl(protocol),
fileContent.file,
protocol
)
val context = components.createContext(nameResolver, packageFqName, TypeTable(packageProto.typeTable))
val fileStub = createFileStub(packageFqName, isScript = false)
createPackageDeclarationsStubs(
fileStub, context,
ProtoContainer.Package(packageFqName, context.nameResolver, context.typeTable, source = null),
packageProto
)
for (classProto in fileWithMetadata.classesToDecompile) {
createClassStub(
fileStub, classProto, nameResolver, nameResolver.getClassId(classProto.fqName), source = null, context = context
)
}
return fileStub
}
}
@@ -0,0 +1,17 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.analysis.decompiler.psi
import org.jetbrains.kotlin.library.metadata.KlibMetadataSerializerProtocol
import org.jetbrains.kotlin.library.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.psi.stubs.KotlinStubVersions
import org.jetbrains.kotlin.serialization.js.DynamicTypeDeserializer
class K2KotlinNativeMetadataDecompiler : K2KlibMetadataDecompiler<KlibMetadataVersion>(
KlibMetaFileType,
{ KlibMetadataSerializerProtocol },
DynamicTypeDeserializer,
{ KlibMetadataVersion.INSTANCE },
{ KlibMetadataVersion.INVALID_VERSION },
KlibMetaFileType.STUB_VERSION + KotlinStubVersions.BUILTIN_STUB_VERSION
)
@@ -0,0 +1,13 @@
// Copyright 2000-2022 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.analysis.decompiler.psi
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinDecompiledFileViewProvider
import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtDecompiledFile
import org.jetbrains.kotlin.analysis.decompiler.psi.text.DecompiledText
class KlibDecompiledFile(
provider: KotlinDecompiledFileViewProvider,
text: (VirtualFile) -> DecompiledText
) : KtDecompiledFile(provider, text)
@@ -0,0 +1,131 @@
// Copyright 2000-2022 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.analysis.decompiler.psi
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.library.KLIB_MANIFEST_FILE_NAME
import org.jetbrains.kotlin.library.KLIB_METADATA_FILE_EXTENSION
import org.jetbrains.kotlin.library.KLIB_MODULE_METADATA_FILE_NAME
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.library.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.library.metadata.parseModuleHeader
import org.jetbrains.kotlin.library.metadata.parsePackageFragment
import org.jetbrains.kotlin.library.readKonanLibraryVersioning
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import java.io.IOException
import java.util.*
@Service
class KlibLoadingMetadataCache {
// 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 = ContainerUtil.createConcurrentWeakValueMap<CacheKey, CacheValue<ProtoBuf.PackageFragment>>()
private val moduleHeaderCache = ContainerUtil.createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataProtoBuf.Header>>()
private val libraryMetadataVersionCache = ContainerUtil.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
}
fun getCachedPackageFragmentWithVersion(packageFragmentFile: VirtualFile): Pair<ProtoBuf.PackageFragment?, KlibMetadataVersion?> {
val packageFragment = getCachedPackageFragment(packageFragmentFile) ?: return null to null
val version = getCachedMetadataVersion(getKlibLibraryRootForPackageFragment(packageFragmentFile))
return packageFragment to version
}
private fun getCachedMetadataVersion(libraryRoot: VirtualFile): KlibMetadataVersion? {
val manifestFile = libraryRoot.findChild(KLIB_MANIFEST_FILE_NAME) ?: return null
val metadataVersion = libraryMetadataVersionCache.computeIfAbsent(
CacheKey(manifestFile)
) {
CacheValue(computeLibraryMetadataVersion(manifestFile))
}.value
return metadataVersion
}
private fun getKlibLibraryRootForPackageFragment(packageFragmentFile: VirtualFile): VirtualFile {
return packageFragmentFile.parent.parent.parent
}
private fun isMetadataCompatible(libraryRoot: VirtualFile): Boolean {
val metadataVersion = getCachedMetadataVersion(libraryRoot) ?: return false
return metadataVersion.isCompatibleWithCurrentCompilerVersion()
}
private fun computePackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? {
if (!isMetadataCompatible(getKlibLibraryRootForPackageFragment(packageFragmentFile)))
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
}
companion object {
@JvmStatic
fun getInstance(): KlibLoadingMetadataCache =
ApplicationManager.getApplication().getService(KlibLoadingMetadataCache::class.java)
}
}
@@ -0,0 +1,24 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.analysis.decompiler.psi
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.library.KLIB_METADATA_FILE_EXTENSION
object KlibMetaFileType : FileType {
override fun getName() = "KNM"
override fun getDescription(): String {
return KotlinLabelProviderService.getService()?.getLabelForKlibMetaFileType()
?: DEFAULT_DESCRIPTION
}
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
private const val DEFAULT_DESCRIPTION = "Klib Metadata"
}
@@ -0,0 +1,71 @@
/*
* 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.analysis.decompiler.psi
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.psi.compiled.ClassFileDecompilers
import org.jetbrains.kotlin.analysis.decompiler.psi.text.DecompiledText
import org.jetbrains.kotlin.analysis.decompiler.psi.text.createIncompatibleAbiVersionDecompiledText
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer
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
) : ClassFileDecompilers.Full() {
protected abstract val metadataStubBuilder: KlibMetadataStubBuilder
protected abstract fun doReadFile(file: VirtualFile): FileWithMetadata?
protected abstract fun getDecompiledText(
file: FileWithMetadata.Compatible,
serializerProtocol: SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer
): DecompiledText
override fun accepts(file: VirtualFile) = FileTypeRegistry.getInstance().isFileOfType(file, fileType)
override fun getStubBuilder() = metadataStubBuilder
override fun createFileViewProvider(file: VirtualFile, manager: PsiManager, physical: Boolean) =
KotlinDecompiledFileViewProvider(manager, file, physical) { provider ->
KlibDecompiledFile(
provider,
::buildDecompiledText
)
}
protected 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(FileTypeRegistry.getInstance().isFileOfType(virtualFile, fileType)) { "Unexpected file type ${virtualFile.fileType}" }
return when (val file = readFileSafely(virtualFile)) {
is FileWithMetadata.Incompatible -> createIncompatibleAbiVersionDecompiledText(expectedBinaryVersion(), file.version)
is FileWithMetadata.Compatible -> getDecompiledText(file, serializerProtocol(), flexibleTypeDeserializer)
null -> createIncompatibleAbiVersionDecompiledText(expectedBinaryVersion(), invalidBinaryVersion())
}
}
}
@@ -0,0 +1,77 @@
/*
* 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.analysis.decompiler.psi
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.kotlin.analysis.decompiler.psi.DeserializerForDecompilerBase
import org.jetbrains.kotlin.analysis.decompiler.psi.LoggingErrorReporter
import org.jetbrains.kotlin.analysis.decompiler.psi.ResolveEverythingToKotlinAnyLocalClassifierResolver
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.incremental.components.LookupTracker
import org.jetbrains.kotlin.library.metadata.KlibMetadataClassDataFinder
import org.jetbrains.kotlin.library.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.EnumEntriesDeserializationSupportImpl
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,
deserializationConfiguration: DeserializationConfiguration = DeserializationConfiguration.Default,
) : DeserializerForDecompilerBase(packageFqName) {
override val builtIns: KotlinBuiltIns get() = DefaultBuiltIns.Instance
override val deserializationComponents: DeserializationComponents
init {
val notFoundClasses = NotFoundClasses(storageManager, moduleDescriptor)
deserializationComponents = DeserializationComponents(
storageManager, moduleDescriptor, deserializationConfiguration,
KlibMetadataClassDataFinder(proto, nameResolver),
AnnotationAndConstantLoaderImpl(moduleDescriptor, notFoundClasses, serializerProtocol), packageFragmentProvider,
ResolveEverythingToKotlinAnyLocalClassifierResolver(builtIns), LoggingErrorReporter(LOG),
LookupTracker.DO_NOTHING, flexibleTypeDeserializer, emptyList(), notFoundClasses, ContractDeserializer.DEFAULT,
extensionRegistryLite = serializerProtocol.extensionRegistry,
samConversionResolver = SamConversionResolverImpl(storageManager, samWithReceiverResolvers = emptyList()),
enumEntriesDeserializationSupport = EnumEntriesDeserializationSupportImpl(moduleDescriptor.platform),
)
}
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,
classNames = { emptyList() },
debugName = "scope of dummyPackageFragment $facadeFqName in ${deserializationComponents.moduleDescriptor} " +
"@KlibMetadataDeserializerForDecompiler"
)
return membersScope.getContributedDescriptors().toList()
}
companion object {
private val LOG = Logger.getInstance(KlibMetadataDeserializerForDecompiler::class.java)
}
}
@@ -0,0 +1,33 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.analysis.decompiler.psi
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.FileTypeRegistry
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.analysis.decompiler.stub.createIncompatibleAbiVersionFileStub
abstract class KlibMetadataStubBuilder(
private val version: Int,
private val fileType: FileType,
private val readFile: (VirtualFile) -> FileWithMetadata?
) : ClsStubBuilder() {
override fun getStubVersion() = ClassFileStubBuilder.STUB_VERSION + version
protected abstract fun buildMetadataFileStub(fileWithMetadata: FileWithMetadata.Compatible, fileContent: FileContent): PsiFileStub<*>
override fun buildFileStub(content: FileContent): PsiFileStub<*>? {
val virtualFile = content.file
assert(FileTypeRegistry.getInstance().isFileOfType(virtualFile, fileType)) { "Unexpected file type ${virtualFile.fileType}" }
val fileWithMetadata = readFile(virtualFile) ?: return null
return when (fileWithMetadata) {
is FileWithMetadata.Incompatible -> createIncompatibleAbiVersionFileStub()
is FileWithMetadata.Compatible -> buildMetadataFileStub(fileWithMetadata, content)
}
}
}
@@ -9,6 +9,7 @@ import com.intellij.openapi.application.ApplicationManager
abstract class KotlinLabelProviderService {
abstract fun getLabelForBuiltInFileType(): String
abstract fun getLabelForKlibMetaFileType(): String
companion object {
fun getService(): KotlinLabelProviderService? =
@@ -0,0 +1,21 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.analysis.decompiler.psi
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.library.metadata.KlibMetadataSerializerProtocol
import org.jetbrains.kotlin.library.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.psi.stubs.KotlinStubVersions
import org.jetbrains.kotlin.serialization.js.DynamicTypeDeserializer
class KotlinNativeMetadataDecompiler : Fe10KlibMetadataDecompiler<KlibMetadataVersion>(
KlibMetaFileType,
{ KlibMetadataSerializerProtocol },
DynamicTypeDeserializer,
{ KlibMetadataVersion.INSTANCE },
{ KlibMetadataVersion.INVALID_VERSION },
KlibMetaFileType.STUB_VERSION + KotlinStubVersions.BUILTIN_STUB_VERSION
) {
override fun doReadFile(file: VirtualFile): FileWithMetadata? {
return FileWithMetadata.forPackageFragment(file)
}
}