From d7356afe1ef2426c5b4c5a308d2c9ba2e921fcb9 Mon Sep 17 00:00:00 2001 From: Pavel Kirpichenkov Date: Thu, 3 Aug 2023 19:28:09 +0300 Subject: [PATCH] Move K/N decompilers and stub builders to kotlin.git KTIJ-26526 --- .../decompiler-to-psi/build.gradle.kts | 3 + .../psi/CachingIdeKlibMetadataLoader.kt | 50 +++++++ .../psi/Fe10KlibMetadataDecompiler.kt | 77 ++++++++++ .../psi/Fe10KlibMetadataStubBuilder.kt | 43 ++++++ .../decompiler/psi/FileWithMetadata.kt | 35 +++++ .../psi/K2KlibMetadataDecompiler.kt | 50 +++++++ .../psi/K2KlibMetadataStubBuilder.kt | 54 ++++++++ .../psi/K2KotlinNativeMetadataDecompiler.kt | 17 +++ .../decompiler/psi/KlibDecompiledFile.kt | 13 ++ .../psi/KlibLoadingMetadataCache.kt | 131 ++++++++++++++++++ .../decompiler/psi/KlibMetaFileType.kt | 24 ++++ .../decompiler/psi/KlibMetadataDecompiler.kt | 71 ++++++++++ .../KlibMetadataDeserializerForDecompiler.kt | 77 ++++++++++ .../decompiler/psi/KlibMetadataStubBuilder.kt | 33 +++++ .../psi/KotlinLabelProviderService.kt | 1 + .../psi/KotlinNativeMetadataDecompiler.kt | 21 +++ 16 files changed, 700 insertions(+) create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/CachingIdeKlibMetadataLoader.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/Fe10KlibMetadataDecompiler.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/Fe10KlibMetadataStubBuilder.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/FileWithMetadata.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KlibMetadataDecompiler.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KlibMetadataStubBuilder.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KotlinNativeMetadataDecompiler.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibDecompiledFile.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibLoadingMetadataCache.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetaFileType.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataDecompiler.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataDeserializerForDecompiler.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataStubBuilder.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinNativeMetadataDecompiler.kt diff --git a/analysis/decompiled/decompiler-to-psi/build.gradle.kts b/analysis/decompiled/decompiler-to-psi/build.gradle.kts index 165d849db34..9a535f7751d 100644 --- a/analysis/decompiled/decompiler-to-psi/build.gradle.kts +++ b/analysis/decompiled/decompiler-to-psi/build.gradle.kts @@ -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")) diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/CachingIdeKlibMetadataLoader.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/CachingIdeKlibMetadataLoader.kt new file mode 100644 index 00000000000..1bbc20936ed --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/CachingIdeKlibMetadataLoader.kt @@ -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 +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/Fe10KlibMetadataDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/Fe10KlibMetadataDecompiler.kt new file mode 100644 index 00000000000..95c19b8d7a4 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/Fe10KlibMetadataDecompiler.kt @@ -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( + fileType: FileType, + serializerProtocol: () -> SerializerExtensionProtocol, + flexibleTypeDeserializer: FlexibleTypeDeserializer, + expectedBinaryVersion: () -> V, + invalidBinaryVersion: () -> V, + stubVersion: Int +) : KlibMetadataDecompiler( + 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() + 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) +} \ No newline at end of file diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/Fe10KlibMetadataStubBuilder.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/Fe10KlibMetadataStubBuilder.kt new file mode 100644 index 00000000000..1432f79c089 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/Fe10KlibMetadataStubBuilder.kt @@ -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<*> + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/FileWithMetadata.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/FileWithMetadata.kt new file mode 100644 index 00000000000..9bcdb2e14c4 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/FileWithMetadata.kt @@ -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 = + 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 + } + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KlibMetadataDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KlibMetadataDecompiler.kt new file mode 100644 index 00000000000..6e1dd1aa9c5 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KlibMetadataDecompiler.kt @@ -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( + private val fileType: FileType, + private val serializerProtocol: () -> SerializerExtensionProtocol, + flexibleTypeDeserializer: FlexibleTypeDeserializer, + expectedBinaryVersion: () -> V, + invalidBinaryVersion: () -> V, + private val stubVersion: Int, +) : KlibMetadataDecompiler(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) + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KlibMetadataStubBuilder.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KlibMetadataStubBuilder.kt new file mode 100644 index 00000000000..4858ff905df --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KlibMetadataStubBuilder.kt @@ -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 + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KotlinNativeMetadataDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KotlinNativeMetadataDecompiler.kt new file mode 100644 index 00000000000..7f5588596ba --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/K2KotlinNativeMetadataDecompiler.kt @@ -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( + KlibMetaFileType, + { KlibMetadataSerializerProtocol }, + DynamicTypeDeserializer, + { KlibMetadataVersion.INSTANCE }, + { KlibMetadataVersion.INVALID_VERSION }, + KlibMetaFileType.STUB_VERSION + KotlinStubVersions.BUILTIN_STUB_VERSION +) diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibDecompiledFile.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibDecompiledFile.kt new file mode 100644 index 00000000000..26f25b5dfee --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibDecompiledFile.kt @@ -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) diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibLoadingMetadataCache.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibLoadingMetadataCache.kt new file mode 100644 index 00000000000..3961f6571b2 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibLoadingMetadataCache.kt @@ -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(val value: T?) + + private val packageFragmentCache = ContainerUtil.createConcurrentWeakValueMap>() + private val moduleHeaderCache = ContainerUtil.createConcurrentWeakValueMap>() + private val libraryMetadataVersionCache = ContainerUtil.createConcurrentWeakValueMap>() + + 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 { + 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) + } + +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetaFileType.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetaFileType.kt new file mode 100644 index 00000000000..be7114911c6 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetaFileType.kt @@ -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" +} \ No newline at end of file diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataDecompiler.kt new file mode 100644 index 00000000000..acfd5c106d2 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataDecompiler.kt @@ -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( + 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()) + } + } +} \ No newline at end of file diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataDeserializerForDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataDeserializerForDecompiler.kt new file mode 100644 index 00000000000..48286d0e102 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataDeserializerForDecompiler.kt @@ -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 { + 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) + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataStubBuilder.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataStubBuilder.kt new file mode 100644 index 00000000000..84d7ed7bf02 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KlibMetadataStubBuilder.kt @@ -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) + } + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinLabelProviderService.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinLabelProviderService.kt index 17e8e5b3f8f..e15c17bf203 100644 --- a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinLabelProviderService.kt +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinLabelProviderService.kt @@ -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? = diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinNativeMetadataDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinNativeMetadataDecompiler.kt new file mode 100644 index 00000000000..cee9777c52b --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinNativeMetadataDecompiler.kt @@ -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( + 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) + } +}