Skip to content

Commit 297eb95

Browse files
committed
Extract common parts of built-ins and JS decompilers
1 parent b52b90c commit 297eb95

File tree

7 files changed

+184
-202
lines changed

7 files changed

+184
-202
lines changed

core/descriptors/src/org/jetbrains/kotlin/builtins/BuiltInsBinaryVersion.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class BuiltInsBinaryVersion(vararg numbers: Int) : BinaryVersion(*numbers) {
3131
@JvmField
3232
val INSTANCE = BuiltInsBinaryVersion(1, 0, 0)
3333

34+
@JvmField
35+
val INVALID_VERSION = BuiltInsBinaryVersion()
36+
3437
fun readFrom(stream: InputStream): BuiltInsBinaryVersion {
3538
val dataInput = DataInputStream(stream)
3639
return BuiltInsBinaryVersion(*(1..dataInput.readInt()).map { dataInput.readInt() }.toIntArray())

idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/builtIns/KotlinBuiltInDecompiler.kt

Lines changed: 24 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -18,130 +18,59 @@ package org.jetbrains.kotlin.idea.decompiler.builtIns
1818

1919
import com.intellij.ide.highlighter.JavaClassFileType
2020
import com.intellij.openapi.vfs.VirtualFile
21-
import com.intellij.psi.FileViewProvider
22-
import com.intellij.psi.PsiManager
23-
import com.intellij.psi.compiled.ClassFileDecompilers
2421
import org.jetbrains.annotations.TestOnly
2522
import org.jetbrains.kotlin.builtins.BuiltInSerializerProtocol
2623
import org.jetbrains.kotlin.builtins.BuiltInsBinaryVersion
27-
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
28-
import org.jetbrains.kotlin.idea.decompiler.KotlinDecompiledFileViewProvider
29-
import org.jetbrains.kotlin.idea.decompiler.KtDecompiledFile
30-
import org.jetbrains.kotlin.idea.decompiler.common.KotlinMetadataDeserializerForDecompiler
31-
import org.jetbrains.kotlin.idea.decompiler.common.createIncompatibleAbiVersionDecompiledText
32-
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledText
33-
import org.jetbrains.kotlin.idea.decompiler.textBuilder.buildDecompiledText
34-
import org.jetbrains.kotlin.idea.decompiler.textBuilder.defaultDecompilerRendererOptions
24+
import org.jetbrains.kotlin.idea.decompiler.common.FileWithMetadata
25+
import org.jetbrains.kotlin.idea.decompiler.common.KotlinMetadataDecompiler
3526
import org.jetbrains.kotlin.name.ClassId
36-
import org.jetbrains.kotlin.renderer.DescriptorRenderer
3727
import org.jetbrains.kotlin.resolve.TargetPlatform
3828
import org.jetbrains.kotlin.serialization.ProtoBuf
39-
import org.jetbrains.kotlin.serialization.builtins.BuiltInsProtoBuf
40-
import org.jetbrains.kotlin.serialization.deserialization.ClassDeserializer
4129
import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer
4230
import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment
43-
import org.jetbrains.kotlin.serialization.deserialization.NameResolverImpl
44-
import org.jetbrains.kotlin.utils.addIfNotNull
4531
import java.io.ByteArrayInputStream
4632

47-
class KotlinBuiltInDecompiler : ClassFileDecompilers.Full() {
48-
private val stubBuilder = KotlinBuiltInStubBuilder()
49-
50-
override fun accepts(file: VirtualFile): Boolean {
51-
return file.fileType == KotlinBuiltInFileType
52-
}
53-
54-
override fun getStubBuilder() = stubBuilder
55-
56-
override fun createFileViewProvider(file: VirtualFile, manager: PsiManager, physical: Boolean): FileViewProvider {
57-
return KotlinDecompiledFileViewProvider(manager, file, physical) { provider ->
58-
if (BuiltInDefinitionFile.read(provider.virtualFile) == null) {
59-
null
60-
}
61-
else {
62-
KtDecompiledFile(provider) { file ->
63-
buildDecompiledTextForBuiltIns(file)
64-
}
65-
}
66-
}
33+
class KotlinBuiltInDecompiler : KotlinMetadataDecompiler<BuiltInsBinaryVersion>(
34+
KotlinBuiltInFileType, KotlinBuiltInStubBuilder(), TargetPlatform.Default, BuiltInSerializerProtocol,
35+
FlexibleTypeDeserializer.ThrowException, BuiltInsBinaryVersion.INSTANCE, BuiltInsBinaryVersion.INVALID_VERSION
36+
) {
37+
override fun readFile(bytes: ByteArray, file: VirtualFile): FileWithMetadata? {
38+
return BuiltInDefinitionFile.read(bytes, file)
6739
}
6840
}
6941

70-
private val decompilerRendererForBuiltIns = DescriptorRenderer.withOptions { defaultDecompilerRendererOptions() }
71-
72-
fun buildDecompiledTextForBuiltIns(builtInFile: VirtualFile): DecompiledText {
73-
if (builtInFile.fileType != KotlinBuiltInFileType) {
74-
error("Unexpected file type ${builtInFile.fileType}")
75-
}
76-
77-
val file = BuiltInDefinitionFile.read(builtInFile)
78-
?: error("Unexpectedly empty built-in file: $builtInFile")
79-
80-
when (file) {
81-
is BuiltInDefinitionFile.Incompatible -> {
82-
return createIncompatibleAbiVersionDecompiledText(BuiltInsBinaryVersion.INSTANCE, file.version)
83-
}
84-
is BuiltInDefinitionFile.Compatible -> {
85-
val packageFqName = file.packageFqName
86-
val resolver = KotlinMetadataDeserializerForDecompiler(
87-
packageFqName, file.proto, file.nameResolver,
88-
TargetPlatform.Default, BuiltInSerializerProtocol, FlexibleTypeDeserializer.ThrowException
89-
)
90-
val declarations = arrayListOf<DeclarationDescriptor>()
91-
declarations.addAll(resolver.resolveDeclarationsInFacade(packageFqName))
92-
for (classProto in file.classesToDecompile) {
93-
val classId = file.nameResolver.getClassId(classProto.fqName)
94-
declarations.addIfNotNull(resolver.resolveTopLevelClass(classId))
42+
class BuiltInDefinitionFile(
43+
proto: ProtoBuf.PackageFragment,
44+
val packageDirectory: VirtualFile,
45+
val isMetadata: Boolean
46+
) : FileWithMetadata.Compatible(proto, BuiltInSerializerProtocol) {
47+
override val classesToDecompile: List<ProtoBuf.Class>
48+
get() = super.classesToDecompile.let { classes ->
49+
if (isMetadata || !FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES) classes
50+
else classes.filter { classProto ->
51+
shouldDecompileBuiltInClass(nameResolver.getClassId(classProto.fqName))
9552
}
96-
return buildDecompiledText(packageFqName, declarations, decompilerRendererForBuiltIns)
9753
}
98-
}
99-
}
100-
101-
sealed class BuiltInDefinitionFile {
102-
class Incompatible(val version: BuiltInsBinaryVersion) : BuiltInDefinitionFile()
10354

104-
class Compatible(val proto: ProtoBuf.PackageFragment,
105-
val packageDirectory: VirtualFile,
106-
val isMetadata: Boolean) : BuiltInDefinitionFile() {
107-
val nameResolver = NameResolverImpl(proto.strings, proto.qualifiedNames)
108-
val packageFqName = nameResolver.getPackageFqName(proto.`package`.getExtension(BuiltInsProtoBuf.packageFqName))
109-
110-
val classesToDecompile =
111-
proto.class_List.filter { proto ->
112-
val classId = nameResolver.getClassId(proto.fqName)
113-
!classId.isNestedClass && classId !in ClassDeserializer.BLACK_LIST
114-
}.let { classes ->
115-
if (isMetadata || !FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES) classes
116-
else classes.filter { classProto ->
117-
shouldDecompileBuiltInClass(nameResolver.getClassId(classProto.fqName))
118-
}
119-
}
120-
121-
private fun shouldDecompileBuiltInClass(classId: ClassId): Boolean {
122-
val realJvmClassFileName = classId.shortClassName.asString() + "." + JavaClassFileType.INSTANCE.defaultExtension
123-
return packageDirectory.findChild(realJvmClassFileName) == null
124-
}
55+
private fun shouldDecompileBuiltInClass(classId: ClassId): Boolean {
56+
val realJvmClassFileName = classId.shortClassName.asString() + "." + JavaClassFileType.INSTANCE.defaultExtension
57+
return packageDirectory.findChild(realJvmClassFileName) == null
12558
}
12659

12760
companion object {
12861
var FILTER_OUT_CLASSES_EXISTING_AS_JVM_CLASS_FILES = true
12962
@TestOnly set
13063

131-
fun read(file: VirtualFile): BuiltInDefinitionFile? {
132-
return read(file.contentsToByteArray(), file)
133-
}
134-
135-
fun read(contents: ByteArray, file: VirtualFile): BuiltInDefinitionFile? {
64+
fun read(contents: ByteArray, file: VirtualFile): FileWithMetadata? {
13665
val stream = ByteArrayInputStream(contents)
13766

13867
val version = BuiltInsBinaryVersion.readFrom(stream)
13968
if (!version.isCompatible()) {
140-
return BuiltInDefinitionFile.Incompatible(version)
69+
return FileWithMetadata.Incompatible(version)
14170
}
14271

14372
val proto = ProtoBuf.PackageFragment.parseFrom(stream, BuiltInSerializerProtocol.extensionRegistry)
144-
val result = BuiltInDefinitionFile.Compatible(proto, file.parent, file.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION)
73+
val result = BuiltInDefinitionFile(proto, file.parent, file.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION)
14574
val packageProto = result.proto.`package`
14675
if (result.classesToDecompile.isEmpty() &&
14776
packageProto.typeAliasCount == 0 && packageProto.functionCount == 0 && packageProto.propertyCount == 0) {

idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/builtIns/KotlinBuiltInStubBuilder.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import com.intellij.psi.impl.compiled.ClassFileStubBuilder
2121
import com.intellij.psi.stubs.PsiFileStub
2222
import com.intellij.util.indexing.FileContent
2323
import org.jetbrains.kotlin.builtins.BuiltInSerializerProtocol
24-
import org.jetbrains.kotlin.serialization.deserialization.ProtoBasedClassDataFinder
2524
import org.jetbrains.kotlin.idea.decompiler.common.AnnotationLoaderForStubBuilderImpl
25+
import org.jetbrains.kotlin.idea.decompiler.common.FileWithMetadata
2626
import org.jetbrains.kotlin.idea.decompiler.stubBuilder.*
2727
import org.jetbrains.kotlin.psi.stubs.KotlinStubVersions
28+
import org.jetbrains.kotlin.serialization.deserialization.ProtoBasedClassDataFinder
2829
import org.jetbrains.kotlin.serialization.deserialization.ProtoContainer
2930
import org.jetbrains.kotlin.serialization.deserialization.TypeTable
3031

@@ -37,10 +38,10 @@ class KotlinBuiltInStubBuilder : ClsStubBuilder() {
3738
val file = BuiltInDefinitionFile.read(content.content, virtualFile) ?: return null
3839

3940
when (file) {
40-
is BuiltInDefinitionFile.Incompatible -> {
41+
is FileWithMetadata.Incompatible -> {
4142
return createIncompatibleAbiVersionFileStub()
4243
}
43-
is BuiltInDefinitionFile.Compatible -> {
44+
is FileWithMetadata.Compatible -> {
4445
val packageProto = file.proto.`package`
4546
val packageFqName = file.packageFqName
4647
val nameResolver = file.nameResolver
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright 2010-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.decompiler.common
18+
19+
import com.intellij.openapi.fileTypes.FileType
20+
import com.intellij.openapi.vfs.VirtualFile
21+
import com.intellij.psi.FileViewProvider
22+
import com.intellij.psi.PsiManager
23+
import com.intellij.psi.compiled.ClassFileDecompilers
24+
import com.intellij.psi.compiled.ClsStubBuilder
25+
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
26+
import org.jetbrains.kotlin.idea.decompiler.KotlinDecompiledFileViewProvider
27+
import org.jetbrains.kotlin.idea.decompiler.KtDecompiledFile
28+
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledText
29+
import org.jetbrains.kotlin.idea.decompiler.textBuilder.buildDecompiledText
30+
import org.jetbrains.kotlin.idea.decompiler.textBuilder.defaultDecompilerRendererOptions
31+
import org.jetbrains.kotlin.renderer.DescriptorRenderer
32+
import org.jetbrains.kotlin.resolve.TargetPlatform
33+
import org.jetbrains.kotlin.serialization.ProtoBuf
34+
import org.jetbrains.kotlin.serialization.SerializerExtensionProtocol
35+
import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion
36+
import org.jetbrains.kotlin.serialization.deserialization.ClassDeserializer
37+
import org.jetbrains.kotlin.serialization.deserialization.FlexibleTypeDeserializer
38+
import org.jetbrains.kotlin.serialization.deserialization.NameResolverImpl
39+
import org.jetbrains.kotlin.utils.addIfNotNull
40+
import java.io.IOException
41+
42+
abstract class KotlinMetadataDecompiler<out V : BinaryVersion>(
43+
private val fileType: FileType,
44+
private val stubBuilder: ClsStubBuilder,
45+
private val targetPlatform: TargetPlatform,
46+
private val serializerProtocol: SerializerExtensionProtocol,
47+
private val flexibleTypeDeserializer: FlexibleTypeDeserializer,
48+
private val expectedBinaryVersion: V,
49+
private val invalidBinaryVersion: V
50+
) : ClassFileDecompilers.Full() {
51+
private val renderer = DescriptorRenderer.withOptions { defaultDecompilerRendererOptions() }
52+
53+
abstract fun readFile(bytes: ByteArray, file: VirtualFile): FileWithMetadata?
54+
55+
override fun accepts(file: VirtualFile) = file.fileType == fileType
56+
57+
override fun getStubBuilder() = stubBuilder
58+
59+
override fun createFileViewProvider(file: VirtualFile, manager: PsiManager, physical: Boolean): FileViewProvider {
60+
return KotlinDecompiledFileViewProvider(manager, file, physical) { provider ->
61+
if (readFile(provider.virtualFile) == null) {
62+
null
63+
}
64+
else {
65+
KtDecompiledFile(provider, this::buildDecompiledText)
66+
}
67+
}
68+
}
69+
70+
private fun readFile(file: VirtualFile): FileWithMetadata? {
71+
if (!file.isValid) return null
72+
73+
return try {
74+
readFile(file.contentsToByteArray(), file)
75+
}
76+
catch (e: IOException) {
77+
// This is needed because sometimes we're given VirtualFile instances that point to non-existent .jar entries.
78+
// Such files are valid (isValid() returns true), but an attempt to read their contents results in a FileNotFoundException.
79+
// Note that although calling "refresh()" instead of catching an exception would seem more correct here,
80+
// it's not always allowed and also is likely to degrade performance
81+
null
82+
}
83+
}
84+
85+
private fun buildDecompiledText(virtualFile: VirtualFile): DecompiledText {
86+
if (virtualFile.fileType != fileType) {
87+
error("Unexpected file type ${virtualFile.fileType}")
88+
}
89+
90+
val file = readFile(virtualFile)
91+
92+
return when (file) {
93+
null -> {
94+
createIncompatibleAbiVersionDecompiledText(expectedBinaryVersion, invalidBinaryVersion)
95+
}
96+
is FileWithMetadata.Incompatible -> {
97+
createIncompatibleAbiVersionDecompiledText(expectedBinaryVersion, file.version)
98+
}
99+
is FileWithMetadata.Compatible -> {
100+
val packageFqName = file.packageFqName
101+
val resolver = KotlinMetadataDeserializerForDecompiler(
102+
packageFqName, file.proto, file.nameResolver,
103+
targetPlatform, serializerProtocol, flexibleTypeDeserializer
104+
)
105+
val declarations = arrayListOf<DeclarationDescriptor>()
106+
declarations.addAll(resolver.resolveDeclarationsInFacade(packageFqName))
107+
for (classProto in file.classesToDecompile) {
108+
val classId = file.nameResolver.getClassId(classProto.fqName)
109+
declarations.addIfNotNull(resolver.resolveTopLevelClass(classId))
110+
}
111+
buildDecompiledText(packageFqName, declarations, renderer)
112+
}
113+
}
114+
}
115+
}
116+
117+
sealed class FileWithMetadata {
118+
class Incompatible(val version: BinaryVersion) : FileWithMetadata()
119+
120+
open class Compatible(
121+
val proto: ProtoBuf.PackageFragment,
122+
serializerProtocol: SerializerExtensionProtocol
123+
) : FileWithMetadata() {
124+
val nameResolver = NameResolverImpl(proto.strings, proto.qualifiedNames)
125+
val packageFqName = nameResolver.getPackageFqName(proto.`package`.getExtension(serializerProtocol.packageFqName))
126+
127+
open val classesToDecompile: List<ProtoBuf.Class> =
128+
proto.class_List.filter { proto ->
129+
val classId = nameResolver.getClassId(proto.fqName)
130+
!classId.isNestedClass && classId !in ClassDeserializer.BLACK_LIST
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)