From fceb46924eb5614d069f415a9951751ec00d581a Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Fri, 17 Dec 2021 15:46:01 +0300 Subject: [PATCH] Move decompiler from IJ repository This commit just moves files and fixes imports --- .../decompiler-to-psi/build.gradle.kts | 23 +++ .../psi/DeserializerForClassfileDecompiler.kt | 94 ++++++++++ .../psi/DeserializerForDecompilerBase.kt | 62 ++++++ .../psi/KotlinClassFileDecompiler.kt | 33 ++++ .../psi/KotlinDecompiledFileViewProvider.kt | 43 +++++ .../decompiler/psi/LoggingErrorReporter.kt | 18 ++ .../decompiler/psi/ResolverForDecompiler.kt | 14 ++ .../analysis/decompiler/psi/file/KtClsFile.kt | 11 ++ .../decompiler/psi/file/KtDecompiledFile.kt | 42 +++++ .../decompiler/psi/text/DecompiledText.kt | 37 ++++ .../psi/text/buildDecompiledText.kt | 177 ++++++++++++++++++ .../text/buildDecompiledTextForClassFile.kt | 61 ++++++ .../psi/text/incompatibleAbiVersion.kt | 22 +++ settings.gradle | 3 +- 14 files changed, 639 insertions(+), 1 deletion(-) create mode 100644 analysis/decompiled/decompiler-to-psi/build.gradle.kts create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/DeserializerForClassfileDecompiler.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/DeserializerForDecompilerBase.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinClassFileDecompiler.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinDecompiledFileViewProvider.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/LoggingErrorReporter.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/ResolverForDecompiler.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/file/KtClsFile.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/file/KtDecompiledFile.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/DecompiledText.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledText.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledTextForClassFile.kt create mode 100644 analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/incompatibleAbiVersion.kt diff --git a/analysis/decompiled/decompiler-to-psi/build.gradle.kts b/analysis/decompiled/decompiler-to-psi/build.gradle.kts new file mode 100644 index 00000000000..9af5ea02b46 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + kotlin("jvm") + id("jps-compatible") +} + +dependencies { + implementation(project(":compiler:psi")) + implementation(project(":compiler:frontend.java")) + implementation(project(":core:compiler.common")) + implementation(project(":compiler:light-classes")) + implementation(project(":analysis:analysis-api-providers")) + implementation(project(":analysis:analysis-api")) + implementation(project(":analysis:analysis-internal-utils")) + implementation(project(":analysis:project-structure")) + implementation(project(":compiler:psi:cls-psi-stub-builder")) + implementation(project(":compiler:psi:cls-psi-file-stub-builder")) + implementation(intellijCore()) +} + +sourceSets { + "main" { projectDefault() } + "test" { none() } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/DeserializerForClassfileDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/DeserializerForClassfileDecompiler.kt new file mode 100644 index 00000000000..2c3078b8fb7 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/DeserializerForClassfileDecompiler.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.analysis.decompiler.psi + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.contracts.ContractDeserializerImpl +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.NotFoundClasses +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.load.kotlin.* +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.stubs.file.builder.ClsKotlinBinaryClassCache +import org.jetbrains.kotlin.psi.stubs.file.builder.DirectoryBasedClassFinder +import org.jetbrains.kotlin.psi.stubs.file.builder.DirectoryBasedDataFinder +import org.jetbrains.kotlin.resolve.sam.SamConversionResolverImpl +import org.jetbrains.kotlin.serialization.deserialization.DeserializationComponents +import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPackageMemberScope + +fun DeserializerForClassfileDecompiler(classFile: VirtualFile): DeserializerForClassfileDecompiler { + val kotlinClassHeaderInfo = + ClsKotlinBinaryClassCache.getInstance().getKotlinBinaryClassHeaderData(classFile) + ?: error("Decompiled data factory shouldn't be called on an unsupported file: $classFile") + val packageFqName = kotlinClassHeaderInfo.classId.packageFqName + return DeserializerForClassfileDecompiler(classFile.parent!!, packageFqName) +} + +class DeserializerForClassfileDecompiler( + packageDirectory: VirtualFile, + directoryPackageFqName: FqName +) : DeserializerForDecompilerBase(directoryPackageFqName) { + override val builtIns: KotlinBuiltIns get() = DefaultBuiltIns.Instance + + private val classFinder = DirectoryBasedClassFinder(packageDirectory, directoryPackageFqName) + + override val deserializationComponents: DeserializationComponents + + init { + val classDataFinder = DirectoryBasedDataFinder(classFinder, LOG) + val notFoundClasses = NotFoundClasses(storageManager, moduleDescriptor) + val annotationAndConstantLoader = + BinaryClassAnnotationAndConstantLoaderImpl(moduleDescriptor, notFoundClasses, storageManager, classFinder) + + val configuration = object : DeserializationConfiguration { + override val readDeserializedContracts: Boolean = true + override val preserveDeclarationsOrdering: Boolean = true + } + + deserializationComponents = DeserializationComponents( + storageManager, moduleDescriptor, configuration, classDataFinder, annotationAndConstantLoader, + packageFragmentProvider, ResolveEverythingToKotlinAnyLocalClassifierResolver(builtIns), LoggingErrorReporter(LOG), + LookupTracker.DO_NOTHING, JavaFlexibleTypeDeserializer, emptyList(), notFoundClasses, + ContractDeserializerImpl(configuration, storageManager), + extensionRegistryLite = JvmProtoBufUtil.EXTENSION_REGISTRY, + samConversionResolver = SamConversionResolverImpl(storageManager, samWithReceiverResolvers = emptyList()) + ) + } + + override fun resolveDeclarationsInFacade(facadeFqName: FqName): List { + val packageFqName = facadeFqName.parent() + assert(packageFqName == directoryPackageFqName) { + "Was called for $facadeFqName; only members of $directoryPackageFqName package are expected." + } + val binaryClassForPackageClass = classFinder.findKotlinClass(ClassId.topLevel(facadeFqName)) + val header = binaryClassForPackageClass?.classHeader + val annotationData = header?.data + val strings = header?.strings + if (annotationData == null || strings == null) { + LOG.error("Could not read annotation data for $facadeFqName from ${binaryClassForPackageClass?.classId}") + return emptyList() + } + val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(annotationData, strings) + val dummyPackageFragment = createDummyPackageFragment(header.packageName?.let(::FqName) ?: facadeFqName.parent()) + val membersScope = DeserializedPackageMemberScope( + dummyPackageFragment, + packageProto, nameResolver, header.metadataVersion, + JvmPackagePartSource(binaryClassForPackageClass, packageProto, nameResolver), deserializationComponents, + "scope of dummyPackageFragment ${dummyPackageFragment.fqName} in module $moduleDescriptor @DeserializerForClassfileDecompiler" + ) { emptyList() } + return membersScope.getContributedDescriptors().toList() + } + + companion object { + private val LOG = Logger.getInstance(DeserializerForClassfileDecompiler::class.java) + } +} \ No newline at end of file diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/DeserializerForDecompilerBase.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/DeserializerForDecompilerBase.kt new file mode 100644 index 00000000000..7f56cc8a488 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/DeserializerForDecompilerBase.kt @@ -0,0 +1,62 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.analysis.decompiler.psi + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentProvider +import org.jetbrains.kotlin.descriptors.PackageFragmentProviderOptimized +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.serialization.deserialization.DeserializationComponents +import org.jetbrains.kotlin.serialization.deserialization.LocalClassifierTypeSettings +import org.jetbrains.kotlin.storage.LockBasedStorageManager +import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.types.SimpleType + +abstract class DeserializerForDecompilerBase(val directoryPackageFqName: FqName) : ResolverForDecompiler { + protected abstract val deserializationComponents: DeserializationComponents + + protected abstract val builtIns: KotlinBuiltIns + + protected val storageManager: StorageManager = LockBasedStorageManager.NO_LOCKS + + protected val moduleDescriptor: ModuleDescriptorImpl = createDummyModule("module for building decompiled sources") + + protected val packageFragmentProvider: PackageFragmentProvider = object : PackageFragmentProviderOptimized { + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) { + packageFragments.add(createDummyPackageFragment(fqName)) + } + + override fun isEmpty(fqName: FqName): Boolean = false + + @Suppress("OVERRIDE_DEPRECATION") + override fun getPackageFragments(fqName: FqName): List { + return listOf(createDummyPackageFragment(fqName)) + } + + override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection { + throw UnsupportedOperationException("This method is not supposed to be called.") + } + } + + override fun resolveTopLevelClass(classId: ClassId) = deserializationComponents.deserializeClass(classId) + + protected fun createDummyPackageFragment(fqName: FqName): MutablePackageFragmentDescriptor = + MutablePackageFragmentDescriptor(moduleDescriptor, fqName) + + private fun createDummyModule(name: String) = ModuleDescriptorImpl(Name.special("<$name>"), storageManager, builtIns) + + init { + moduleDescriptor.initialize(packageFragmentProvider) + moduleDescriptor.setDependencies(moduleDescriptor, moduleDescriptor.builtIns.builtInsModule) + } +} + +class ResolveEverythingToKotlinAnyLocalClassifierResolver(private val builtIns: KotlinBuiltIns) : LocalClassifierTypeSettings { + override val replacementTypeForLocalClassifiers: SimpleType? + get() = builtIns.anyType +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinClassFileDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinClassFileDecompiler.kt new file mode 100644 index 00000000000..5c1a9f992ef --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinClassFileDecompiler.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.analysis.decompiler.psi + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiManager +import com.intellij.psi.compiled.ClassFileDecompilers +import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtClsFile +import org.jetbrains.kotlin.psi.stubs.file.builder.ClsClassFinder.isKotlinInternalCompiledFile +import org.jetbrains.kotlin.psi.stubs.file.builder.ClsKotlinBinaryClassCache +import org.jetbrains.kotlin.psi.stubs.file.builder.KotlinClsStubBuilder + +class KotlinClassFileDecompiler : ClassFileDecompilers.Full() { + private val stubBuilder = KotlinClsStubBuilder() + + override fun accepts(file: VirtualFile) = ClsKotlinBinaryClassCache.getInstance().isKotlinJvmCompiledFile(file) + + override fun getStubBuilder() = stubBuilder + + override fun createFileViewProvider(file: VirtualFile, manager: PsiManager, physical: Boolean): KotlinDecompiledFileViewProvider { + return KotlinDecompiledFileViewProvider(manager, file, physical) factory@{ provider -> + val virtualFile = provider.virtualFile + + if (isKotlinInternalCompiledFile(virtualFile)) + null + else + KtClsFile(provider) + } + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinDecompiledFileViewProvider.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinDecompiledFileViewProvider.kt new file mode 100644 index 00000000000..44dcb84cf58 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/KotlinDecompiledFileViewProvider.kt @@ -0,0 +1,43 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.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.PsiFile +import com.intellij.psi.PsiInvalidElementAccessException +import com.intellij.psi.PsiManager +import com.intellij.psi.SingleRootFileViewProvider +import com.intellij.psi.impl.DebugUtil +import com.intellij.psi.impl.source.PsiFileImpl +import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtDecompiledFile +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.utils.concurrent.block.LockedClearableLazyValue + +class KotlinDecompiledFileViewProvider( + manager: PsiManager, + file: VirtualFile, + physical: Boolean, + private val factory: (KotlinDecompiledFileViewProvider) -> KtDecompiledFile? +) : SingleRootFileViewProvider(manager, file, physical, KotlinLanguage.INSTANCE) { + val content: LockedClearableLazyValue = LockedClearableLazyValue(Any()) { + val psiFile = createFile(manager.project, file, KotlinFileType.INSTANCE) + val text = psiFile?.text ?: "" + + DebugUtil.performPsiModification("Invalidating throw-away copy of file that was used for getting text") { + (psiFile as? PsiFileImpl)?.markInvalidated() + } + + text + } + + override fun createFile(project: Project, file: VirtualFile, fileType: FileType): PsiFile? { + return factory(this) + } + + override fun createCopy(copy: VirtualFile) = KotlinDecompiledFileViewProvider(manager, copy, false, factory) + + override fun getContents() = content.get() +} \ No newline at end of file diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/LoggingErrorReporter.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/LoggingErrorReporter.kt new file mode 100644 index 00000000000..645ee54c35c --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/LoggingErrorReporter.kt @@ -0,0 +1,18 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.analysis.decompiler.psi + +import com.intellij.openapi.diagnostic.Logger +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.serialization.deserialization.ErrorReporter + +class LoggingErrorReporter(private val log: Logger) : ErrorReporter { + override fun reportIncompleteHierarchy(descriptor: ClassDescriptor, unresolvedSuperClasses: List) { + // This is absolutely fine for the decompiler + } + + override fun reportCannotInferVisibility(descriptor: CallableMemberDescriptor) { + log.error("Could not infer visibility for $descriptor") + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/ResolverForDecompiler.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/ResolverForDecompiler.kt new file mode 100644 index 00000000000..1ff912d8a39 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/ResolverForDecompiler.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.analysis.decompiler.psi + +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.name.ClassId + +interface ResolverForDecompiler { + fun resolveTopLevelClass(classId: ClassId): ClassDescriptor? + + fun resolveDeclarationsInFacade(facadeFqName: FqName): List +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/file/KtClsFile.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/file/KtClsFile.kt new file mode 100644 index 00000000000..f55f77b7122 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/file/KtClsFile.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.analysis.decompiler.psi.file + +import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinDecompiledFileViewProvider +import org.jetbrains.kotlin.analysis.decompiler.psi.text.buildDecompiledTextForClassFile + +class KtClsFile(provider: KotlinDecompiledFileViewProvider) : KtDecompiledFile(provider, ::buildDecompiledTextForClassFile) diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/file/KtDecompiledFile.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/file/KtDecompiledFile.kt new file mode 100644 index 00000000000..635dcb9cb7a --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/file/KtDecompiledFile.kt @@ -0,0 +1,42 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.analysis.decompiler.psi.file + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinDecompiledFileViewProvider +import org.jetbrains.kotlin.analysis.decompiler.psi.text.DecompiledText +import org.jetbrains.kotlin.analysis.decompiler.psi.text.DecompiledTextIndexer +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.utils.concurrent.block.LockedClearableLazyValue + +open class KtDecompiledFile( + private val provider: KotlinDecompiledFileViewProvider, + buildDecompiledText: (VirtualFile) -> DecompiledText +) : KtFile(provider, true) { + + private val decompiledText = LockedClearableLazyValue(Any()) { + buildDecompiledText(provider.virtualFile) + } + + override fun getText(): String? { + return decompiledText.get().text + } + + override fun onContentReload() { + super.onContentReload() + + provider.content.drop() + decompiledText.drop() + } + + fun getDeclaration(indexer: DecompiledTextIndexer, key: T): KtDeclaration? { + val range = decompiledText.get().index.getRange(indexer, key) ?: return null + return PsiTreeUtil.findElementOfClassAtRange(this@KtDecompiledFile, range.startOffset, range.endOffset, KtDeclaration::class.java) + } + + fun hasDeclarationWithKey(indexer: DecompiledTextIndexer, key: T): Boolean { + return decompiledText.get().index.getRange(indexer, key) != null + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/DecompiledText.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/DecompiledText.kt new file mode 100644 index 00000000000..246eb371fd0 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/DecompiledText.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.analysis.decompiler.psi.text + +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.utils.keysToMap + +data class DecompiledText(val text: String, val index: DecompiledTextIndex) + +interface DecompiledTextIndexer { + fun indexDescriptor(descriptor: DeclarationDescriptor): Collection +} + +// In-memory HashMap-based index of decompiled text +// allows navigation features +class DecompiledTextIndex(private val indexers: Collection>) { + private val indexerToMap: Map, MutableMap> = indexers.keysToMap { hashMapOf() } + + fun addToIndex(descriptor: DeclarationDescriptor, textRange: TextRange) { + indexers.forEach { mapper -> + val correspondingMap = indexerToMap.getValue(mapper) + mapper.indexDescriptor(descriptor).forEach { key -> + correspondingMap[key] = textRange + } + } + } + + fun getRange(mapper: DecompiledTextIndexer, key: T): TextRange? = indexerToMap[mapper]?.get(key) + + companion object { + val Empty = DecompiledTextIndex(listOf()) + } +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledText.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledText.kt new file mode 100644 index 00000000000..afcacf18137 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledText.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.analysis.decompiler.psi.text + +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.contracts.description.ContractProviderKey +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.renderer.DescriptorRendererModifier +import org.jetbrains.kotlin.renderer.DescriptorRendererOptions +import org.jetbrains.kotlin.renderer.render +import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver +import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry +import org.jetbrains.kotlin.resolve.descriptorUtil.secondaryConstructors +import org.jetbrains.kotlin.types.isFlexible + +private const val DECOMPILED_CODE_COMMENT = "/* compiled code */" +private const val DECOMPILED_COMMENT_FOR_PARAMETER = "/* = compiled code */" +private const val FLEXIBLE_TYPE_COMMENT = "/* platform type */" +private const val DECOMPILED_CONTRACT_STUB = "contract { /* compiled contract */ }" + +fun DescriptorRendererOptions.defaultDecompilerRendererOptions() { + withDefinedIn = false + classWithPrimaryConstructor = true + secondaryConstructorsAsPrimary = false + modifiers = DescriptorRendererModifier.ALL + excludedTypeAnnotationClasses = emptySet() + alwaysRenderModifiers = true + parameterNamesInFunctionalTypes = false // to support parameters names in decompiled text we need to load annotation arguments +} + +fun buildDecompiledText( + packageFqName: FqName, + descriptors: List, + descriptorRenderer: DescriptorRenderer, + indexers: Collection>, +): DecompiledText { + val builder = StringBuilder() + + fun appendDecompiledTextAndPackageName() { + builder.append("// IntelliJ API Decompiler stub source generated from a class file\n" + "// Implementation of methods is not available") + builder.append("\n\n") + if (!packageFqName.isRoot) { + builder.append("package ").append(packageFqName.render()).append("\n\n") + } + } + + val textIndex = DecompiledTextIndex(indexers) + + fun indexDescriptor(descriptor: DeclarationDescriptor, startOffset: Int, endOffset: Int) { + textIndex.addToIndex(descriptor, TextRange(startOffset, endOffset)) + } + + fun CallableMemberDescriptor.isConsideredSynthetic(): Boolean { + return when (kind) { + CallableMemberDescriptor.Kind.DECLARATION -> false + + CallableMemberDescriptor.Kind.FAKE_OVERRIDE, + CallableMemberDescriptor.Kind.DELEGATION -> true + + CallableMemberDescriptor.Kind.SYNTHESIZED -> { + // Of all synthesized functions, only `component*` functions are rendered (for historical reasons) + !DataClassDescriptorResolver.isComponentLike(name) + } + } + } + + fun appendDescriptor(descriptor: DeclarationDescriptor, indent: String, lastEnumEntry: Boolean? = null) { + val startOffset = builder.length + if (isEnumEntry(descriptor)) { + for (annotation in descriptor.annotations) { + builder.append(descriptorRenderer.renderAnnotation(annotation)) + builder.append(" ") + } + builder.append(descriptor.name.asString().quoteIfNeeded()) + builder.append(if (lastEnumEntry!!) ";" else ",") + } else { + builder.append(descriptorRenderer.render(descriptor).replace("= ...", DECOMPILED_COMMENT_FOR_PARAMETER)) + } + var endOffset = builder.length + + if (descriptor is CallableDescriptor) { + //NOTE: assuming that only return types can be flexible + if (descriptor.returnType!!.isFlexible()) { + builder.append(" ").append(FLEXIBLE_TYPE_COMMENT) + } + } + + if (descriptor is FunctionDescriptor || descriptor is PropertyDescriptor) { + if ((descriptor as MemberDescriptor).modality != Modality.ABSTRACT) { + if (descriptor is FunctionDescriptor) { + with(builder) { + append(" { ") + if (descriptor.getUserData(ContractProviderKey)?.getContractDescription() != null) { + append(DECOMPILED_CONTRACT_STUB).append("; ") + } + append(DECOMPILED_CODE_COMMENT).append(" }") + } + } else { + // descriptor instanceof PropertyDescriptor + builder.append(" ").append(DECOMPILED_CODE_COMMENT) + } + endOffset = builder.length + } + } else if (descriptor is ClassDescriptor && !isEnumEntry(descriptor)) { + builder.append(" {\n") + + val subindent = "$indent " + + var firstPassed = false + fun newlineExceptFirst() { + if (firstPassed) { + builder.append("\n") + } else { + firstPassed = true + } + } + + val allDescriptors = descriptor.secondaryConstructors + descriptor.defaultType.memberScope.getContributedDescriptors() + val (enumEntries, members) = allDescriptors.partition(::isEnumEntry) + + for ((index, enumEntry) in enumEntries.withIndex()) { + newlineExceptFirst() + builder.append(subindent) + appendDescriptor(enumEntry, subindent, index == enumEntries.lastIndex) + } + + val companionObject = descriptor.companionObjectDescriptor + if (companionObject != null) { + newlineExceptFirst() + builder.append(subindent) + appendDescriptor(companionObject, subindent) + } + + for (member in members) { + if (member.containingDeclaration != descriptor) { + continue + } + if (member == companionObject) { + continue + } + if (member is CallableMemberDescriptor && member.isConsideredSynthetic()) { + continue + } + newlineExceptFirst() + builder.append(subindent) + appendDescriptor(member, subindent) + } + + builder.append(indent).append("}") + endOffset = builder.length + } + + builder.append("\n") + indexDescriptor(descriptor, startOffset, endOffset) + + if (descriptor is ClassDescriptor) { + val primaryConstructor = descriptor.unsubstitutedPrimaryConstructor + if (primaryConstructor != null) { + indexDescriptor(primaryConstructor, startOffset, endOffset) + } + } + } + + appendDecompiledTextAndPackageName() + for (member in descriptors) { + appendDescriptor(member, "") + builder.append("\n") + } + + return DecompiledText(builder.toString(), textIndex) +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledTextForClassFile.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledTextForClassFile.kt new file mode 100644 index 00000000000..e3fb82ea371 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/buildDecompiledTextForClassFile.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.analysis.decompiler.psi.text + +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.analysis.decompiler.psi.DeserializerForClassfileDecompiler +import org.jetbrains.kotlin.analysis.decompiler.psi.ResolverForDecompiler +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.stubs.file.builder.ClsClassFinder.findMultifileClassParts +import org.jetbrains.kotlin.psi.stubs.file.builder.ClsKotlinBinaryClassCache +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.types.asFlexibleType +import org.jetbrains.kotlin.types.isFlexible + +fun buildDecompiledTextForClassFile( + classFile: VirtualFile, + resolver: ResolverForDecompiler = DeserializerForClassfileDecompiler(classFile) +): DecompiledText { + val classHeader = + ClsKotlinBinaryClassCache.getInstance().getKotlinBinaryClassHeaderData(classFile) + ?: error("Decompiled data factory shouldn't be called on an unsupported file: $classFile") + + val classId = classHeader.classId + + if (!classHeader.metadataVersion.isCompatible()) { + return createIncompatibleAbiVersionDecompiledText(JvmMetadataVersion.INSTANCE, classHeader.metadataVersion) + } + + fun buildText(declarations: List) = buildDecompiledText( + classHeader.packageName?.let(::FqName) ?: classId.packageFqName, + declarations, decompilerRendererForClassFiles, listOf(ByDescriptorIndexer, BySignatureIndexer) + ) + + return when (classHeader.kind) { + KotlinClassHeader.Kind.FILE_FACADE -> + buildText(resolver.resolveDeclarationsInFacade(classId.asSingleFqName())) + KotlinClassHeader.Kind.CLASS -> { + buildText(listOfNotNull(resolver.resolveTopLevelClass(classId))) + } + KotlinClassHeader.Kind.MULTIFILE_CLASS -> { + val partClasses = findMultifileClassParts(classFile, classId, classHeader.partNamesIfMultifileFacade) + val partMembers = partClasses.flatMap { partClass -> + resolver.resolveDeclarationsInFacade(partClass.classId.asSingleFqName()) + } + buildText(partMembers) + } + else -> + throw UnsupportedOperationException("Unknown header kind: $classHeader, class $classId") + } +} + +private val decompilerRendererForClassFiles = DescriptorRenderer.withOptions { + defaultDecompilerRendererOptions() + typeNormalizer = { type -> if (type.isFlexible()) type.asFlexibleType().lowerBound else type } +} \ No newline at end of file diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/incompatibleAbiVersion.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/incompatibleAbiVersion.kt new file mode 100644 index 00000000000..b36bdd4dab2 --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/incompatibleAbiVersion.kt @@ -0,0 +1,22 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.analysis.decompiler.psi.text + +import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion + +private const val FILE_ABI_VERSION_MARKER: String = "FILE_ABI" +private const val CURRENT_ABI_VERSION_MARKER: String = "CURRENT_ABI" + +const val INCOMPATIBLE_ABI_VERSION_GENERAL_COMMENT: String = + "// This class file was compiled with different version of Kotlin compiler and can't be decompiled." + +private const val INCOMPATIBLE_ABI_VERSION_COMMENT: String = "$INCOMPATIBLE_ABI_VERSION_GENERAL_COMMENT\n" + + "//\n" + + "// Current compiler ABI version is $CURRENT_ABI_VERSION_MARKER\n" + + "// File ABI version is $FILE_ABI_VERSION_MARKER" + +fun createIncompatibleAbiVersionDecompiledText(expectedVersion: V, actualVersion: V): DecompiledText = DecompiledText( + INCOMPATIBLE_ABI_VERSION_COMMENT.replace(CURRENT_ABI_VERSION_MARKER, expectedVersion.toString()) + .replace(FILE_ABI_VERSION_MARKER, actualVersion.toString()), + DecompiledTextIndex.Empty +) diff --git a/settings.gradle b/settings.gradle index ab181baadd4..9fce8fbdf73 100644 --- a/settings.gradle +++ b/settings.gradle @@ -498,7 +498,8 @@ include ":generators:analysis-api-generator", ":analysis:analysis-internal-utils", ":analysis:symbol-light-classes", ":analysis:project-structure", - ":analysis:analysis-api-fe10" + ":analysis:analysis-api-fe10", + ":analysis:decompiled:decompiler-to-psi" if (buildProperties.inJpsBuildIdeaSync) {