Move K/N decompilers and stub builders to kotlin.git
KTIJ-26526
This commit is contained in:
committed by
Space Team
parent
c5637ccc7e
commit
d7356afe1e
@@ -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"))
|
||||
|
||||
+50
@@ -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
|
||||
}
|
||||
+77
@@ -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)
|
||||
}
|
||||
+43
@@ -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<*>
|
||||
}
|
||||
}
|
||||
+35
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -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)
|
||||
}
|
||||
}
|
||||
+54
@@ -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
|
||||
}
|
||||
}
|
||||
+17
@@ -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
|
||||
)
|
||||
+13
@@ -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)
|
||||
+131
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -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"
|
||||
}
|
||||
+71
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -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)
|
||||
}
|
||||
}
|
||||
+33
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -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? =
|
||||
|
||||
+21
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user