diff --git a/analysis/decompiled/decompiler-to-psi/build.gradle.kts b/analysis/decompiled/decompiler-to-psi/build.gradle.kts index 9af5ea02b46..05187cb05b1 100644 --- a/analysis/decompiled/decompiler-to-psi/build.gradle.kts +++ b/analysis/decompiled/decompiler-to-psi/build.gradle.kts @@ -8,10 +8,6 @@ dependencies { 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()) diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByDescriptorIndexer.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByDescriptorIndexer.kt new file mode 100644 index 00000000000..fe69bbade0b --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByDescriptorIndexer.kt @@ -0,0 +1,109 @@ +/* + * 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.diagnostic.Logger +import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtDecompiledFile +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.builtins.jvm.JvmBuiltInsSignatures +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtDeclarationContainer +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass + + +object ByDescriptorIndexer : DecompiledTextIndexer { + override fun indexDescriptor(descriptor: DeclarationDescriptor): Collection { + return listOf(descriptor.toStringKey()) + } + + internal fun getDeclarationForDescriptor(descriptor: DeclarationDescriptor, file: KtDecompiledFile): KtDeclaration? { + val original = descriptor.original + + if (original is TypeAliasConstructorDescriptor) { + return getDeclarationForDescriptor(original.typeAliasDescriptor, file) + } + + if (original is ValueParameterDescriptor) { + val callable = original.containingDeclaration + val callableDeclaration = getDeclarationForDescriptor(callable, file) as? KtCallableDeclaration ?: return null + if (original.index >= callableDeclaration.valueParameters.size) { + LOG.error( + "Parameter count mismatch for ${DescriptorRenderer.DEBUG_TEXT.render(callable)}[${original.index}] vs " + + callableDeclaration.valueParameterList?.text + ) + return null + } + return callableDeclaration.valueParameters[original.index] + } + + if (original is ConstructorDescriptor && original.isPrimary) { + val classOrObject = getDeclarationForDescriptor(original.containingDeclaration, file) as? KtClassOrObject + return classOrObject?.primaryConstructor ?: classOrObject + } + + val descriptorKey = original.toStringKey() + + if (!file.isContentsLoaded && original is MemberDescriptor) { + val hasDeclarationByKey = file.hasDeclarationWithKey(this, descriptorKey) || (getBuiltinsDescriptorKey(descriptor)?.let { + file.hasDeclarationWithKey( + this, + it + ) + } ?: false) + if (hasDeclarationByKey) { + val declarationContainer: KtDeclarationContainer? = when { + DescriptorUtils.isTopLevelDeclaration(original) -> file + original.containingDeclaration is ClassDescriptor -> + getDeclarationForDescriptor(original.containingDeclaration as ClassDescriptor, file) as? KtClassOrObject + else -> null + } + + if (declarationContainer != null) { + val descriptorName = original.name.asString() + val singleOrNull = declarationContainer.declarations.singleOrNull { it.name == descriptorName } + if (singleOrNull != null) { + return singleOrNull + } + } + } + } + + return file.getDeclaration(this, descriptorKey) ?: run { + return getBuiltinsDescriptorKey(descriptor)?.let { file.getDeclaration(this, it) } + } + } + + fun getBuiltinsDescriptorKey(descriptor: DeclarationDescriptor): String? { + if (descriptor !is ClassDescriptor) return null + + val classFqName = descriptor.fqNameUnsafe + if (!JvmBuiltInsSignatures.isSerializableInJava(classFqName)) return null + + val builtInDescriptor = + DefaultBuiltIns.Instance.builtInsModule.resolveTopLevelClass(classFqName.toSafe(), NoLookupLocation.FROM_IDE) + return builtInDescriptor?.toStringKey() + } + + private fun DeclarationDescriptor.toStringKey(): String { + return descriptorRendererForKeys.render(this) + } + + private val descriptorRendererForKeys = DescriptorRenderer.withOptions { + defaultDecompilerRendererOptions() + withDefinedIn = true + renderUnabbreviatedType = false + } + + private val LOG = Logger.getInstance(this::class.java) +} diff --git a/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByJvmSignatureIndexer.kt b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByJvmSignatureIndexer.kt new file mode 100644 index 00000000000..c4c64b7282b --- /dev/null +++ b/analysis/decompiled/decompiler-to-psi/src/org/jetbrains/kotlin/analysis/decompiler/psi/text/ByJvmSignatureIndexer.kt @@ -0,0 +1,184 @@ +/* + * 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.psi.PsiMember +import com.intellij.psi.impl.compiled.SignatureParsing +import com.intellij.util.cls.ClsFormatException +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.load.kotlin.MemberSignature +import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull +import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue +import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension +import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.resolve.jvm.annotations.findJvmOverloadsAnnotation +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassConstructorDescriptor +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor +import java.text.CharacterIterator +import java.text.StringCharacterIterator + +private object ByJvmSignatureIndexer : DecompiledTextIndexer { + override fun indexDescriptor(descriptor: DeclarationDescriptor): Collection { + val signatures = arrayListOf() + fun save(id: List, signature: MemberSignature) { + signatures.add(ClassNameAndSignature(id, signature)) + } + + fun ClassDescriptor.apply() { + when (kind) { + ClassKind.ENUM_ENTRY -> { + val enumClass = containingDeclaration as ClassDescriptor + val signature = MemberSignature.fromFieldNameAndDesc(name.asString(), enumClass.desc()) + save(enumClass.relativeClassName(), signature) + } + ClassKind.OBJECT -> { + val instanceFieldSignature = MemberSignature.fromFieldNameAndDesc(JvmAbi.INSTANCE_FIELD, desc()) + save(relativeClassName(), instanceFieldSignature) + if (isCompanionObject) { + val signature = MemberSignature.fromFieldNameAndDesc(name.asString(), desc()) + save((containingDeclaration as? ClassDescriptor)?.relativeClassName().orEmpty(), signature) + } + } + else -> { + } + } + } + + fun DeserializedClassConstructorDescriptor.apply() { + JvmProtoBufUtil.getJvmConstructorSignature(proto, nameResolver, typeTable)?.let { + val id = (containingDeclaration as? ClassDescriptor)?.relativeClassName().orEmpty() + val signature = MemberSignature.fromJvmMemberSignature(it) + save(id, signature) + } + } + + fun DeserializedSimpleFunctionDescriptor.apply() { + JvmProtoBufUtil.getJvmMethodSignature(proto, nameResolver, typeTable)?.let { + val id = (containingDeclaration as? ClassDescriptor)?.relativeClassName().orEmpty() + + val signature = MemberSignature.fromJvmMemberSignature(it) + save(id, signature) + + if (findJvmOverloadsAnnotation() == null) return + + val extensionShift = if (isExtension) 1 else 0 + + val omittedList = mutableListOf() + valueParameters.asReversed().forEach { parameter -> + if (parameter.hasDefaultValue()) { + omittedList.add(parameter.index + extensionShift) + val newDescriptor = excludeParametersFromDescriptor(it.desc, omittedList) + if (newDescriptor != null) { + val overloadedSignature = MemberSignature.fromMethodNameAndDesc(it.name, newDescriptor) + save(id, overloadedSignature) + } + } + } + } + } + + fun DeserializedPropertyDescriptor.apply() { + val className = (containingDeclaration as? ClassDescriptor)?.relativeClassName().orEmpty() + val signature = proto.getExtensionOrNull(JvmProtoBuf.propertySignature) + if (signature != null) { + val fieldSignature = JvmProtoBufUtil.getJvmFieldSignature(proto, nameResolver, typeTable) + if (fieldSignature != null) { + save(className, MemberSignature.fromJvmMemberSignature(fieldSignature)) + } + if (signature.hasGetter()) { + save(className, MemberSignature.fromMethod(nameResolver, signature.getter)) + } + if (signature.hasSetter()) { + save(className, MemberSignature.fromMethod(nameResolver, signature.setter)) + } + } + } + + when (descriptor) { + is ClassDescriptor -> descriptor.apply() + is DeserializedClassConstructorDescriptor -> descriptor.apply() + is DeserializedSimpleFunctionDescriptor -> descriptor.apply() + is DeserializedPropertyDescriptor -> descriptor.apply() + } + + return signatures + } +} + +private fun excludeParametersFromDescriptor(descriptor: String, omittedParameters: List): String? { + + fun tryParseParametersAndReturnType(): Pair, String>? { + val iterator = StringCharacterIterator(descriptor) + + fun parseTypeString(): String? { + val begin = iterator.index + try { + SignatureParsing.parseTypeString(iterator) { it } + } catch (e: ClsFormatException) { + return null + } + val end = iterator.index + return descriptor.substring(begin, end) + } + + if (iterator.current() != '(') return null + iterator.next() + + if (iterator.current() == ')') { + iterator.next() + val returnType = parseTypeString() ?: return null + return emptyList() to returnType + } + + val parameterTypes = mutableListOf() + while (iterator.current() != ')' && iterator.current() != CharacterIterator.DONE) { + parameterTypes += parseTypeString() ?: return null + } + + if (iterator.current() != ')') return null + iterator.next() + + val returnType = parseTypeString() ?: return null + return parameterTypes to returnType + } + + val (parameterTypes, returnType) = tryParseParametersAndReturnType() ?: return null + + val parametersList = parameterTypes + .filterIndexed { index, _ -> index !in omittedParameters } + .joinToString("") + + return "($parametersList)$returnType" +} + +private fun ClassDescriptor.desc(): String = "L" + JvmClassName.byClassId(classId!!).internalName + ";" + +fun PsiMember.relativeClassName(): List { + return generateSequence(this.containingClass) { it.containingClass }.toList().dropLast(1).reversed().map { Name.identifier(it.name!!) } +} + +private fun ClassDescriptor.relativeClassName(): List { + return classId!!.relativeClassName.pathSegments().drop(1) +} + +// every member is represented by its jvm signature and relative class name (which is easy to obtain from descriptors or cls psi) + +// relative class name is a path containing inner/nested class names from top level class to the class containing this member (excluding top level class name) +// Examples: for top level function or function in a top level class relativeClassName is empty +// For: class TopLevel { class A { class B { fun f() } } } +// relativeClassName for function 'f' will be [A, B] +data class ClassNameAndSignature(val relativeClassName: List, val memberSignature: MemberSignature) + +// expose with different type +val BySignatureIndexer: DecompiledTextIndexer<*> = ByJvmSignatureIndexer diff --git a/analysis/decompiled/light-classes-for-decompiled-fe10/build.gradle.kts b/analysis/decompiled/light-classes-for-decompiled-fe10/build.gradle.kts new file mode 100644 index 00000000000..7ea4a1b0ca8 --- /dev/null +++ b/analysis/decompiled/light-classes-for-decompiled-fe10/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + kotlin("jvm") + id("jps-compatible") +} + +dependencies { + api(project(":compiler:psi")) + api(project(":analysis:decompiled:decompiler-to-psi")) + api(project(":analysis:decompiled:light-classes-for-decompiled")) + api(project(":compiler:light-classes")) + implementation(intellijCore()) +} + +sourceSets { + "main" { projectDefault() } + "test" { none() } +} diff --git a/analysis/decompiled/light-classes-for-decompiled-fe10/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/fe10/KotlinDeclarationInCompiledFileSearcherFE10Impl.kt b/analysis/decompiled/light-classes-for-decompiled-fe10/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/fe10/KotlinDeclarationInCompiledFileSearcherFE10Impl.kt new file mode 100644 index 00000000000..1b4669d8b70 --- /dev/null +++ b/analysis/decompiled/light-classes-for-decompiled-fe10/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/fe10/KotlinDeclarationInCompiledFileSearcherFE10Impl.kt @@ -0,0 +1,47 @@ +/* + * 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.decompiled.light.classes.fe10 + +import com.intellij.psi.PsiMember +import org.jetbrains.kotlin.analysis.decompiled.light.classes.origin.KotlinDeclarationInCompiledFileSearcher +import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtClsFile +import org.jetbrains.kotlin.analysis.decompiler.psi.text.BySignatureIndexer +import org.jetbrains.kotlin.analysis.decompiler.psi.text.ClassNameAndSignature +import org.jetbrains.kotlin.analysis.decompiler.psi.text.relativeClassName +import org.jetbrains.kotlin.load.kotlin.MemberSignature +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtDeclarationContainer + +class KotlinDeclarationInCompiledFileSearcherFE10Impl: KotlinDeclarationInCompiledFileSearcher() { + override fun findDeclarationInCompiledFile(file: KtClsFile, member: PsiMember, signature: MemberSignature): KtDeclaration? { + val relativeClassName = member.relativeClassName() + val key = ClassNameAndSignature(relativeClassName, signature) + + val memberName = member.name + + if (memberName != null && !file.isContentsLoaded && file.hasDeclarationWithKey(BySignatureIndexer, key)) { + val container: KtDeclarationContainer? = if (relativeClassName.isEmpty()) + file + else { + val topClassOrObject = file.declarations.singleOrNull() as? KtClassOrObject + relativeClassName.fold(topClassOrObject) { classOrObject, name -> + classOrObject?.declarations?.singleOrNull { it.name == name.asString() } as? KtClassOrObject + } + } + + val declaration = container?.declarations?.singleOrNull { + it.name == memberName + } + + if (declaration != null) { + return declaration + } + } + + return file.getDeclaration(BySignatureIndexer, key) + } +} \ No newline at end of file diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/origin/KotlinDeclarationInCompiledFileSearcher.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/origin/KotlinDeclarationInCompiledFileSearcher.kt new file mode 100644 index 00000000000..cc61eebb312 --- /dev/null +++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/origin/KotlinDeclarationInCompiledFileSearcher.kt @@ -0,0 +1,21 @@ +/* + * 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.decompiled.light.classes.origin + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.psi.PsiMember +import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtClsFile +import org.jetbrains.kotlin.load.kotlin.MemberSignature +import org.jetbrains.kotlin.psi.KtDeclaration + +abstract class KotlinDeclarationInCompiledFileSearcher { + abstract fun findDeclarationInCompiledFile(file: KtClsFile, member: PsiMember, signature: MemberSignature): KtDeclaration? + + companion object { + fun getInstance(): KotlinDeclarationInCompiledFileSearcher = + ApplicationManager.getApplication().getService(KotlinDeclarationInCompiledFileSearcher::class.java) + } +} \ No newline at end of file diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/origin/LightMemberOriginForCompiledElement.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/origin/LightMemberOriginForCompiledElement.kt index 105efcc73a0..8b5619bdeb4 100644 --- a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/origin/LightMemberOriginForCompiledElement.kt +++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/origin/LightMemberOriginForCompiledElement.kt @@ -47,7 +47,7 @@ data class LightMemberOriginForCompiledField(val psiField: PsiField, val file: K override val originalElement: KtDeclaration? by lazyPub { val desc = MapPsiToAsmDesc.typeDesc(psiField.type) val signature = MemberSignature.fromFieldNameAndDesc(psiField.name, desc) - findDeclarationInCompiledFile(file, psiField, signature) + KotlinDeclarationInCompiledFileSearcher.getInstance().findDeclarationInCompiledFile(file, psiField, signature) } } @@ -70,6 +70,6 @@ data class LightMemberOriginForCompiledMethod(val psiMethod: PsiMethod, val file val desc = MapPsiToAsmDesc.methodDesc(psiMethod) val name = if (psiMethod.isConstructor) "" else psiMethod.name val signature = MemberSignature.fromMethodNameAndDesc(name, desc) - findDeclarationInCompiledFile(file, psiMethod, signature) + KotlinDeclarationInCompiledFileSearcher.getInstance().findDeclarationInCompiledFile(file, psiMethod, signature) } } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index f59eefbf41b..5bf2dcc31a0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -500,7 +500,8 @@ include ":generators:analysis-api-generator", ":analysis:project-structure", ":analysis:analysis-api-fe10", ":analysis:decompiled:decompiler-to-psi", - ":analysis:decompiled:light-classes-for-decompiled" + ":analysis:decompiled:light-classes-for-decompiled", + ":analysis:decompiled:light-classes-for-decompiled-fe10" if (buildProperties.inJpsBuildIdeaSync) {