Migrate stub and psi builders by classfiles form IJ repository

This commit is contained in:
Ilya Kirillov
2021-12-21 14:16:10 +03:00
parent 0efc819e48
commit aa87524513
10 changed files with 457 additions and 2 deletions
@@ -0,0 +1,73 @@
/*
* 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.stub.file
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.protobuf.MessageLite
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.*
class AnnotationLoaderForStubBuilderImpl(
private val protocol: SerializerExtensionProtocol
) : AnnotationLoader<ClassId> {
override fun loadClassAnnotations(container: ProtoContainer.Class): List<ClassId> =
container.classProto.getExtension(protocol.classAnnotation).orEmpty().map { container.nameResolver.getClassId(it.id) }
override fun loadCallableAnnotations(
container: ProtoContainer,
proto: MessageLite,
kind: AnnotatedCallableKind
): List<ClassId> {
val annotations = when (proto) {
is ProtoBuf.Constructor -> proto.getExtension(protocol.constructorAnnotation)
is ProtoBuf.Function -> proto.getExtension(protocol.functionAnnotation)
is ProtoBuf.Property -> when (kind) {
AnnotatedCallableKind.PROPERTY -> proto.getExtension(protocol.propertyAnnotation)
AnnotatedCallableKind.PROPERTY_GETTER -> proto.getExtension(protocol.propertyGetterAnnotation)
AnnotatedCallableKind.PROPERTY_SETTER -> proto.getExtension(protocol.propertySetterAnnotation)
else -> error("Unsupported callable kind with property proto")
}
else -> error("Unknown message: $proto")
}.orEmpty()
return annotations.map { container.nameResolver.getClassId(it.id) }
}
override fun loadPropertyBackingFieldAnnotations(container: ProtoContainer, proto: ProtoBuf.Property): List<ClassId> =
emptyList()
override fun loadPropertyDelegateFieldAnnotations(container: ProtoContainer, proto: ProtoBuf.Property): List<ClassId> =
emptyList()
override fun loadEnumEntryAnnotations(container: ProtoContainer, proto: ProtoBuf.EnumEntry): List<ClassId> =
proto.getExtension(protocol.enumEntryAnnotation).orEmpty().map { container.nameResolver.getClassId(it.id) }
override fun loadValueParameterAnnotations(
container: ProtoContainer,
callableProto: MessageLite,
kind: AnnotatedCallableKind,
parameterIndex: Int,
proto: ProtoBuf.ValueParameter
): List<ClassId> =
proto.getExtension(protocol.parameterAnnotation).orEmpty().map { container.nameResolver.getClassId(it.id) }
override fun loadExtensionReceiverParameterAnnotations(
container: ProtoContainer,
proto: MessageLite,
kind: AnnotatedCallableKind
): List<ClassId> = emptyList()
override fun loadTypeAnnotations(
proto: ProtoBuf.Type,
nameResolver: NameResolver
): List<ClassId> =
proto.getExtension(protocol.typeAnnotation).orEmpty().map { nameResolver.getClassId(it.id) }
override fun loadTypeParameterAnnotations(proto: ProtoBuf.TypeParameter, nameResolver: NameResolver): List<ClassId> =
proto.getExtension(protocol.typeParameterAnnotation).orEmpty().map { nameResolver.getClassId(it.id) }
}
@@ -0,0 +1,89 @@
/*
* 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.stub.file
import com.intellij.openapi.fileTypes.FileType
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.*
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.metadata.deserialization.NameResolverImpl
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
import org.jetbrains.kotlin.serialization.deserialization.ClassDeserializer
import org.jetbrains.kotlin.serialization.deserialization.ProtoBasedClassDataFinder
import org.jetbrains.kotlin.serialization.deserialization.ProtoContainer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
open class KotlinMetadataStubBuilder(
private val version: Int,
private val fileType: FileType,
private val serializerProtocol: () -> SerializerExtensionProtocol,
private val readFile: (VirtualFile, ByteArray) -> FileWithMetadata?
) : ClsStubBuilder() {
override fun getStubVersion() = ClassFileStubBuilder.STUB_VERSION + version
override fun buildFileStub(content: FileContent): PsiFileStub<*>? {
val virtualFile = content.file
assert(virtualFile.fileType == fileType) { "Unexpected file type ${virtualFile.fileType}" }
val file = readFile(virtualFile, content.content) ?: return null
when (file) {
is FileWithMetadata.Incompatible -> {
return createIncompatibleAbiVersionFileStub()
}
is FileWithMetadata.Compatible -> {
val packageProto = file.proto.`package`
val packageFqName = file.packageFqName
val nameResolver = file.nameResolver
val components = ClsStubBuilderComponents(
ProtoBasedClassDataFinder(file.proto, nameResolver, file.version),
AnnotationLoaderForStubBuilderImpl(serializerProtocol()),
virtualFile
)
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 file.classesToDecompile) {
createClassStub(
fileStub, classProto, nameResolver, nameResolver.getClassId(classProto.fqName), source = null, context = context
)
}
return fileStub
}
}
}
sealed class FileWithMetadata {
class Incompatible(val version: BinaryVersion) : FileWithMetadata()
open class Compatible(
val proto: ProtoBuf.PackageFragment,
val version: BinaryVersion,
serializerProtocol: SerializerExtensionProtocol
) : FileWithMetadata() {
val nameResolver = NameResolverImpl(proto.strings, proto.qualifiedNames)
val packageFqName = FqName(nameResolver.getPackageFqName(proto.`package`.getExtension(serializerProtocol.packageFqName)))
open val classesToDecompile: List<ProtoBuf.Class> =
proto.class_List.filter { proto ->
val classId = nameResolver.getClassId(proto.fqName)
!classId.isNestedClass && classId !in ClassDeserializer.BLACK_LIST
}
}
}
}
@@ -0,0 +1,74 @@
// 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.ide.highlighter.JavaClassFileType
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.analysis.decompiler.stub.file.KotlinMetadataStubBuilder
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.builtins.BuiltInsBinaryVersion
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.stubs.KotlinStubVersions
import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer
import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
import org.jetbrains.kotlin.serialization.deserialization.getClassId
import java.io.ByteArrayInputStream
class KotlinBuiltInDecompiler : KotlinMetadataDecompiler<BuiltInsBinaryVersion>(
KotlinBuiltInFileType, { BuiltInSerializerProtocol },
FlexibleTypeDeserializer.ThrowException, { BuiltInsBinaryVersion.INSTANCE }, { BuiltInsBinaryVersion.INVALID_VERSION },
KotlinStubVersions.BUILTIN_STUB_VERSION
) {
override fun readFile(bytes: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata? {
return BuiltInDefinitionFile.read(bytes, file)
}
}
class BuiltInDefinitionFile(
proto: ProtoBuf.PackageFragment,
version: BuiltInsBinaryVersion,
val packageDirectory: VirtualFile,
val isMetadata: Boolean
) : KotlinMetadataStubBuilder.FileWithMetadata.Compatible(proto, version, BuiltInSerializerProtocol) {
override val classesToDecompile: List<ProtoBuf.Class>
get() = super.classesToDecompile.let { classes ->
if (isMetadata || !FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES) classes
else classes.filter { classProto ->
shouldDecompileBuiltInClass(nameResolver.getClassId(classProto.fqName))
}
}
private fun shouldDecompileBuiltInClass(classId: ClassId): Boolean {
val realJvmClassFileName = classId.shortClassName.asString() + "." + JavaClassFileType.INSTANCE.defaultExtension
return packageDirectory.findChild(realJvmClassFileName) == null
}
companion object {
var FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES = true
@TestOnly set
fun read(contents: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata? {
val stream = ByteArrayInputStream(contents)
val version = BuiltInsBinaryVersion.readFrom(stream)
if (!version.isCompatible()) {
return Incompatible(version)
}
val proto = ProtoBuf.PackageFragment.parseFrom(stream, BuiltInSerializerProtocol.extensionRegistry)
val result =
BuiltInDefinitionFile(proto, version, file.parent, file.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION)
val packageProto = result.proto.`package`
if (result.classesToDecompile.isEmpty() &&
packageProto.typeAliasCount == 0 && packageProto.functionCount == 0 && packageProto.propertyCount == 0
) {
// No declarations to decompile: should skip this file
return null
}
return result
}
}
}
@@ -0,0 +1,29 @@
// 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.vfs.VirtualFile
import org.jetbrains.kotlin.idea.KotlinIconProviderService
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
import javax.swing.Icon
object KotlinBuiltInFileType : FileType {
override fun getName() = "kotlin_builtins"
override fun getDescription(): String =
KotlinLabelProviderService.getService()?.getLabelForBuiltInFileType()
?: DEFAULT_DESCRIPTION
override fun getDefaultExtension() = BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION
override fun getIcon(): Icon = KotlinIconProviderService.getInstance().builtInFileIcon
override fun isBinary() = true
override fun isReadOnly() = true
override fun getCharset(file: VirtualFile, content: ByteArray): String? = null
private const val DEFAULT_DESCRIPTION = "Kotlin built-in declarations"
}
@@ -0,0 +1,17 @@
/*
* 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.application.ApplicationManager
abstract class KotlinLabelProviderService {
abstract fun getLabelForBuiltInFileType(): String
companion object {
fun getService(): KotlinLabelProviderService? =
ApplicationManager.getApplication().getServiceIfCreated(KotlinLabelProviderService::class.java)
}
}
@@ -0,0 +1,100 @@
// 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.vfs.VirtualFile
import com.intellij.psi.FileViewProvider
import com.intellij.psi.PsiManager
import com.intellij.psi.compiled.ClassFileDecompilers
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtDecompiledFile
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.createIncompatibleAbiVersionDecompiledText
import org.jetbrains.kotlin.analysis.decompiler.psi.text.defaultDecompilerRendererOptions
import org.jetbrains.kotlin.analysis.decompiler.stub.file.KotlinMetadataStubBuilder
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.FlexibleTypeDeserializer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.IOException
abstract class KotlinMetadataDecompiler<out V : BinaryVersion>(
private val fileType: FileType,
private val serializerProtocol: () -> SerializerExtensionProtocol,
private val flexibleTypeDeserializer: FlexibleTypeDeserializer,
private val expectedBinaryVersion: () -> V,
private val invalidBinaryVersion: () -> V,
stubVersion: Int
) : ClassFileDecompilers.Full() {
private val metadataStubBuilder: KotlinMetadataStubBuilder =
KotlinMetadataStubBuilder(stubVersion, fileType, serializerProtocol, ::readFileSafely)
private val renderer: DescriptorRenderer by lazy {
DescriptorRenderer.withOptions { defaultDecompilerRendererOptions() }
}
abstract fun readFile(bytes: ByteArray, file: VirtualFile): KotlinMetadataStubBuilder.FileWithMetadata?
override fun accepts(file: VirtualFile) = file.fileType == fileType
override fun getStubBuilder() = metadataStubBuilder
override fun createFileViewProvider(file: VirtualFile, manager: PsiManager, physical: Boolean): FileViewProvider {
return KotlinDecompiledFileViewProvider(manager, file, physical) { provider ->
val virtualFile = provider.virtualFile
readFileSafely(virtualFile)?.let { fileWithMetadata ->
KtDecompiledFile(provider) {
check(it == virtualFile) {
"Unexpected file $it, expected ${virtualFile.fileType}"
}
buildDecompiledText(fileWithMetadata)
}
}
}
}
@TestOnly
fun readFile(file: VirtualFile) = readFileSafely(file)
private fun readFileSafely(file: VirtualFile, content: ByteArray? = null): KotlinMetadataStubBuilder.FileWithMetadata? {
if (!file.isValid) return null
return try {
readFile(content ?: file.contentsToByteArray(false), 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(file: KotlinMetadataStubBuilder.FileWithMetadata): DecompiledText {
return when (file) {
is KotlinMetadataStubBuilder.FileWithMetadata.Incompatible -> {
createIncompatibleAbiVersionDecompiledText(expectedBinaryVersion(), file.version)
}
is KotlinMetadataStubBuilder.FileWithMetadata.Compatible -> {
val packageFqName = file.packageFqName
val resolver = KotlinMetadataDeserializerForDecompiler(
packageFqName, file.proto, file.nameResolver, file.version,
serializerProtocol(), flexibleTypeDeserializer
)
val declarations = arrayListOf<DeclarationDescriptor>()
declarations.addAll(resolver.resolveDeclarationsInFacade(packageFqName))
for (classProto in file.classesToDecompile) {
val classId = file.nameResolver.getClassId(classProto.fqName)
declarations.addIfNotNull(resolver.resolveTopLevelClass(classId))
}
buildDecompiledText(packageFqName, declarations, renderer)
}
}
}
}
@@ -0,0 +1,66 @@
// 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.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.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
import org.jetbrains.kotlin.name.FqName
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 KotlinMetadataDeserializerForDecompiler(
packageFqName: FqName,
private val proto: ProtoBuf.PackageFragment,
private val nameResolver: NameResolver,
private val metadataVersion: BinaryVersion,
serializerProtocol: SerializerExtensionProtocol,
flexibleTypeDeserializer: FlexibleTypeDeserializer
) : DeserializerForDecompilerBase(packageFqName) {
override val builtIns: KotlinBuiltIns get() = DefaultBuiltIns.Instance
override val deserializationComponents: DeserializationComponents
init {
val notFoundClasses = NotFoundClasses(storageManager, moduleDescriptor)
deserializationComponents = DeserializationComponents(
storageManager, moduleDescriptor, DeserializationConfiguration.Default,
ProtoBasedClassDataFinder(proto, nameResolver, metadataVersion),
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())
)
}
override fun resolveDeclarationsInFacade(facadeFqName: FqName): List<DeclarationDescriptor> {
assert(facadeFqName == directoryPackageFqName) {
"Was called for $facadeFqName; only members of $directoryPackageFqName package are expected."
}
val dummyPackageFragment = createDummyPackageFragment(facadeFqName)
val membersScope = DeserializedPackageMemberScope(
dummyPackageFragment, proto.`package`, nameResolver, metadataVersion, containerSource = null,
components = deserializationComponents,
debugName = "scope of dummyPackageFragment ${dummyPackageFragment.fqName} in module " +
"${deserializationComponents.moduleDescriptor} @KotlinMetadataDeserializerForDecompiler"
) { emptyList() }
return membersScope.getContributedDescriptors().toList()
}
companion object {
private val LOG = Logger.getInstance(KotlinMetadataDeserializerForDecompiler::class.java)
}
}
@@ -27,7 +27,7 @@ object ByDescriptorIndexer : DecompiledTextIndexer<String> {
return listOf(descriptor.toStringKey())
}
internal fun getDeclarationForDescriptor(descriptor: DeclarationDescriptor, file: KtDecompiledFile): KtDeclaration? {
fun getDeclarationForDescriptor(descriptor: DeclarationDescriptor, file: KtDecompiledFile): KtDeclaration? {
val original = descriptor.original
if (original is TypeAliasConstructorDescriptor) {
@@ -38,7 +38,7 @@ fun buildDecompiledText(
packageFqName: FqName,
descriptors: List<DeclarationDescriptor>,
descriptorRenderer: DescriptorRenderer,
indexers: Collection<DecompiledTextIndexer<*>>,
indexers: Collection<DecompiledTextIndexer<*>> = listOf(ByDescriptorIndexer),
): DecompiledText {
val builder = StringBuilder()
@@ -13,6 +13,8 @@ import javax.swing.*;
public abstract class KotlinIconProviderService {
public abstract Icon getFileIcon();
public abstract Icon getBuiltInFileIcon();
public abstract Icon getLightVariableIcon(@NotNull PsiModifierListOwner element, int flags);
public static class CompilerKotlinFileIconProviderService extends KotlinIconProviderService {
@@ -21,6 +23,11 @@ public abstract class KotlinIconProviderService {
return null;
}
@Override
public Icon getBuiltInFileIcon() {
return null;
}
@Override
public Icon getLightVariableIcon(@NotNull PsiModifierListOwner element, int flags) {
return null;