KT-45777: Remove proto-based approach to compute Java class snapshots

as the ASM-based approach is better, and we have switched to the
ASM-based approach for a while now.
This commit is contained in:
Hung Nguyen
2022-01-11 19:30:32 +00:00
committed by nataliya.valtman
parent f819a10968
commit 1cb509d529
11 changed files with 96 additions and 596 deletions
@@ -1,224 +0,0 @@
/*
* 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.incremental
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.SourceFile
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.load.java.JavaClassFinder
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.sources.JavaSourceElementFactory
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaElement
import org.jetbrains.kotlin.load.java.structure.JavaPackage
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
import org.jetbrains.kotlin.load.kotlin.DeserializationComponentsForJava.Companion.createModuleData
import org.jetbrains.kotlin.load.kotlin.KotlinClassFinder
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.serialization.DescriptorSerializer
import org.jetbrains.kotlin.serialization.deserialization.ErrorReporter
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInsResourceLoader
import java.io.InputStream
/** Creates [JavaClassDescriptor]s of Java classes. */
object JavaClassDescriptorCreator {
/**
* Creates [JavaClassDescriptor]s of the given Java classes, or returns `null` if it can't be created for some reason.
*
* Note that creating a [JavaClassDescriptor] for a nested class will require accessing the outer class (and possibly vice versa).
* Therefore, outer classes and nested classes must be passed together in one invocation of this method.
*/
fun create(classIds: List<ClassId>, classesContents: List<ByteArray>): List<JavaClassDescriptor?> {
val binaryJavaClasses = classIds.mapIndexed { index, classId ->
createBinaryJavaClass(classId, classesContents[index])
}
val moduleDescriptor = createModuleData(
kotlinClassFinder = NoOpKotlinClassFinder,
jvmBuiltInsKotlinClassFinder = JvmBuiltInsKotlinClassFinder(),
javaClassFinder = BinaryJavaClassFinder(binaryJavaClasses),
moduleName = JavaClassDescriptorCreator::class.java.simpleName,
errorReporter = ThrowImmediatelyErrorReporter,
javaSourceElementFactory = NoSourceJavaSourceElementFactory
).deserializationComponentsForJava.components.moduleDescriptor
return classIds.map { classId ->
moduleDescriptor.findClassAcrossModuleDependencies(classId) as? JavaClassDescriptor
}
}
}
private fun createBinaryJavaClass(classId: ClassId, classContents: ByteArray): BinaryJavaClass {
val context = ClassifierResolutionContext {
null // TODO: Implement this?
}
val outerClass: JavaClass? = null // TODO: Compute outer class?
val innerClassFinder: ((Name) -> JavaClass?) = { _ ->
null // TODO: Implement this?
}
return try {
BinaryJavaClass(
virtualFile = null,
fqName = classId.asSingleFqName(),
context = context,
signatureParser = BinaryClassSignatureParser(),
access = 0,
outerClass = outerClass,
classContent = classContents,
innerClassFinder = innerClassFinder
)
} catch (e: NoSuchMethodError) {
// When running unit tests, the above call currently fails as JavaClassDescriptorCreator and BinaryJavaClass are located in
// different jars (kotlin-build-common.jar and kotlin-compiler-embeddable.jar), and in the second jar, `com.intellij.*` classes are
// repackaged as `org.jetbrains.kotlin.com.intellij.*`. The `virtualFile` argument above (even though it is null) has type
// `com.intellij.openapi.vfs.VirtualFile`, which has been repackaged, so the method signatures won't match at run time.
// In integrations tests or regular builds, JavaClassDescriptorCreator and BinaryJavaClass are both located in
// kotlin-compiler-embeddable.jar with all references renamed together, so there are no issues.
// We use reflection here as a workaround for unit tests.
val binaryJavaClass = BinaryJavaClass::class.java
val constructor = binaryJavaClass.constructors.sortedBy { it.parameters.size }[0]
return constructor.newInstance(
/* virtualFile */ null,
/* fqName */ classId.asSingleFqName(),
/* context */ context,
/* signatureParser */ BinaryClassSignatureParser(),
/* access */ 0,
/* outerClass */ outerClass,
/* classContent */ classContents,
/* innerClassFinder */ innerClassFinder
) as BinaryJavaClass
}
}
/**
* [JavaClassFinder] that returns results based on the given [BinaryJavaClass] list.
*
* Note that some returned results are fake/empty (similar to ReflectJavaClassFinder).
* TODO: Revise and see if there are any correctness issues.
*/
private class BinaryJavaClassFinder(binaryJavaClasses: List<BinaryJavaClass>) : JavaClassFinder {
private val nameToJavaClass: Map<FqName, BinaryJavaClass> = binaryJavaClasses.associateBy { it.fqName }
override fun findClass(request: JavaClassFinder.Request): JavaClass? {
return nameToJavaClass[request.classId.asSingleFqName()]
}
override fun findPackage(fqName: FqName, mayHaveAnnotations: Boolean): JavaPackage {
return object : JavaPackage {
override val fqName: FqName
get() = fqName
override val subPackages: Collection<JavaPackage>
get() = emptyList()
override val annotations: Collection<JavaAnnotation>
get() = emptyList()
override val isDeprecatedInJavaDoc: Boolean
get() = false
override fun getClasses(nameFilter: (Name) -> Boolean): Collection<JavaClass> = emptyList()
override fun findAnnotation(fqName: FqName): JavaAnnotation? = null
}
}
override fun knownClassNamesInPackage(packageFqName: FqName): Set<String>? = null
}
/** [KotlinClassFinder] that returns no results (e.g., when we know that the classes are not Kotlin classes). */
private object NoOpKotlinClassFinder : KotlinClassFinder {
override fun findKotlinClassOrContent(classId: ClassId): KotlinClassFinder.Result? = null
override fun findKotlinClassOrContent(javaClass: JavaClass): KotlinClassFinder.Result? = null
override fun findMetadata(classId: ClassId): InputStream? = null
override fun hasMetadataPackage(fqName: FqName): Boolean = false
override fun findBuiltInsData(packageFqName: FqName): InputStream? = null
}
/** [KotlinClassFinder] for Kotlin JVM built-in classes. */
private class JvmBuiltInsKotlinClassFinder : KotlinClassFinder {
override fun findKotlinClassOrContent(classId: ClassId): KotlinClassFinder.Result? = null
override fun findKotlinClassOrContent(javaClass: JavaClass): KotlinClassFinder.Result? = null
override fun findMetadata(classId: ClassId): InputStream? = null
override fun hasMetadataPackage(fqName: FqName): Boolean = false
private val builtInsResourceLoader by lazy { BuiltInsResourceLoader() }
override fun findBuiltInsData(packageFqName: FqName): InputStream? {
// Same as ReflectKotlinClassFinder.findBuiltInsData
return if (packageFqName.startsWith(StandardNames.BUILT_INS_PACKAGE_NAME)) {
builtInsResourceLoader.loadResource(BuiltInSerializerProtocol.getBuiltInsFilePath(packageFqName))
} else null
}
}
/** [ErrorReporter] that throws errors as soon as they are reported. */
private object ThrowImmediatelyErrorReporter : ErrorReporter {
override fun reportIncompleteHierarchy(descriptor: ClassDescriptor, unresolvedSuperClasses: MutableList<String>) {
// BinaryJavaClassFinder doesn't have the whole classpath so it is unable to resolve classes such as java.lang.Object.
// Ignore this error for now.
// TODO: Revisit later to see how we can address this or if we can safely ignore this error.
}
override fun reportCannotInferVisibility(descriptor: CallableMemberDescriptor) {
error("Cannot infer visibility for $descriptor")
}
}
/** [JavaSourceElementFactory] that doesn't provide a source file for a [JavaElement] (e.g., because the source file is not available). */
private object NoSourceJavaSourceElementFactory : JavaSourceElementFactory {
private class NoSourceJavaElement(override val javaElement: JavaElement) : JavaSourceElement {
override fun getContainingFile(): SourceFile = SourceFile.NO_SOURCE_FILE
}
override fun source(javaElement: JavaElement): JavaSourceElement = NoSourceJavaElement(javaElement)
}
/** Similar to [JavaClassDescriptor.convertToProto] but without an associated source file. */
fun JavaClassDescriptor.toSerializedJavaClass(): SerializedJavaClass {
val extension = JavaClassesSerializerExtension()
val descriptorSerializer = try {
DescriptorSerializer.create(
descriptor = this,
extension = extension,
parentSerializer = null,
project = null
)
} catch (e: NoSuchMethodError) {
// When running unit tests, the above call currently fails as the `project` argument above has type
// `com.intellij.openapi.project.Project`, which is repackaged as `org.jetbrains.kotlin.com.intellij.openapi.project.Project` in
// kotlin-compiler-embeddable.jar (see the comment at the createBinaryJavaClass method for more context).
// We use reflection here as a workaround for unit tests.
val createMethod = DescriptorSerializer::class.java.methods.single { it.name == "create" }
createMethod.invoke(
/* static method */ null,
/* descriptor */ this,
/* extension */ extension,
/* parentSerializer */ null,
/* project */ null
) as DescriptorSerializer
}
val classProto = descriptorSerializer.classProto(this).build()
val (stringTable, qualifiedNameTable) = extension.stringTable.buildProto()
return SerializedJavaClass(classProto, stringTable, qualifiedNameTable)
}
@@ -14,12 +14,9 @@ import org.jetbrains.kotlin.incremental.classpathDiff.ImpactAnalysis.computeImpa
import org.jetbrains.kotlin.incremental.storage.FileToCanonicalPathConverter
import org.jetbrains.kotlin.incremental.storage.ListExternalizer
import org.jetbrains.kotlin.incremental.storage.loadFromFile
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
import org.jetbrains.kotlin.metadata.deserialization.supertypes
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.serialization.deserialization.getClassId
import java.io.File
import java.util.*
@@ -92,34 +89,38 @@ object ClasspathChangesComputer {
previousClassSnapshots: List<ClassSnapshot>,
metrics: BuildMetricsReporter
): ChangeSet {
val asmBasedSnapshotPredicate: (ClassSnapshot) -> Boolean = {
val isKotlinClass: (ClassSnapshot) -> Boolean = {
when (it) {
is RegularJavaClassSnapshot -> true
is KotlinClassSnapshot, is ProtoBasedJavaClassSnapshot -> false
else -> error("Unexpected type (it should have been handled earlier): ${it.javaClass.name}")
is KotlinClassSnapshot -> true
is JavaClassSnapshot -> false
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${it.javaClass.name}")
}
}
val (currentAsmBasedSnapshots, currentProtoBasedSnapshots) = currentClassSnapshots.partition(asmBasedSnapshotPredicate)
val (previousAsmBasedSnapshots, previousProtoBasedSnapshots) = previousClassSnapshots.partition(asmBasedSnapshotPredicate)
val (currentKotlinClassSnapshots, currentJavaClassSnapshots) = currentClassSnapshots.partition(isKotlinClass)
val (previousKotlinClassSnapshots, previousJavaClassSnapshots) = previousClassSnapshots.partition(isKotlinClass)
@Suppress("UNCHECKED_CAST")
val kotlinClassChanges = metrics.measure(BuildTime.COMPUTE_KOTLIN_CLASS_CHANGES) {
computeChangesForProtoBasedSnapshots(currentProtoBasedSnapshots, previousProtoBasedSnapshots)
computeKotlinClassChanges(
currentKotlinClassSnapshots as List<KotlinClassSnapshot>,
previousKotlinClassSnapshots as List<KotlinClassSnapshot>
)
}
@Suppress("UNCHECKED_CAST")
val javaClassChanges = metrics.measure(BuildTime.COMPUTE_JAVA_CLASS_CHANGES) {
JavaClassChangesComputer.compute(
currentAsmBasedSnapshots as List<RegularJavaClassSnapshot>,
previousAsmBasedSnapshots as List<RegularJavaClassSnapshot>
currentJavaClassSnapshots as List<JavaClassSnapshot>,
previousJavaClassSnapshots as List<JavaClassSnapshot>
)
}
return kotlinClassChanges + javaClassChanges
}
private fun computeChangesForProtoBasedSnapshots(
currentClassSnapshots: List<ClassSnapshot>,
previousClassSnapshots: List<ClassSnapshot>
private fun computeKotlinClassChanges(
currentClassSnapshots: List<KotlinClassSnapshot>,
previousClassSnapshots: List<KotlinClassSnapshot>
): ChangeSet {
val workingDir =
FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true)
@@ -132,28 +133,13 @@ object ClasspathChangesComputer {
// - The ChangesCollector result will contain symbols in the previous classes (we actually don't need them, but it's part of the
// API's effects).
val unusedChangesCollector = ChangesCollector()
for (previousSnapshot in previousClassSnapshots) {
when (previousSnapshot) {
is KotlinClassSnapshot -> {
incrementalJvmCache.saveClassToCache(
kotlinClassInfo = previousSnapshot.classInfo,
sourceFiles = null,
changesCollector = unusedChangesCollector
)
incrementalJvmCache.markDirty(previousSnapshot.classInfo.className)
}
is ProtoBasedJavaClassSnapshot -> {
incrementalJvmCache.saveJavaClassProto(
source = null,
serializedJavaClass = previousSnapshot.serializedJavaClass,
collector = unusedChangesCollector
)
incrementalJvmCache.markDirty(JvmClassName.byClassId(previousSnapshot.serializedJavaClass.classId))
}
is RegularJavaClassSnapshot, is InaccessibleClassSnapshot, is ContentHashJavaClassSnapshot -> {
error("Unexpected type (it should have been handled earlier): ${previousSnapshot.javaClass.name}")
}
}
previousClassSnapshots.forEach {
incrementalJvmCache.saveClassToCache(
kotlinClassInfo = it.classInfo,
sourceFiles = null,
changesCollector = unusedChangesCollector
)
incrementalJvmCache.markDirty(it.classInfo.className)
}
// Step 2:
@@ -164,26 +150,12 @@ object ClasspathChangesComputer {
// - The intermediate ChangesCollector result will contain symbols in added classes and changed (added/modified/removed) symbols
// in modified classes. We will collect symbols in removed classes in step 3.
val changesCollector = ChangesCollector()
for (currentSnapshot in currentClassSnapshots) {
when (currentSnapshot) {
is KotlinClassSnapshot -> {
incrementalJvmCache.saveClassToCache(
kotlinClassInfo = currentSnapshot.classInfo,
sourceFiles = null,
changesCollector = changesCollector
)
}
is ProtoBasedJavaClassSnapshot -> {
incrementalJvmCache.saveJavaClassProto(
source = null,
serializedJavaClass = currentSnapshot.serializedJavaClass,
collector = changesCollector
)
}
is RegularJavaClassSnapshot, is InaccessibleClassSnapshot, is ContentHashJavaClassSnapshot -> {
error("Unexpected type (it should have been handled earlier): ${currentSnapshot.javaClass.name}")
}
}
currentClassSnapshots.forEach {
incrementalJvmCache.saveClassToCache(
kotlinClassInfo = it.classInfo,
sourceFiles = null,
changesCollector = changesCollector
)
}
// Step 3:
@@ -303,11 +275,8 @@ internal object ImpactAnalysis {
internal fun ClassSnapshot.getClassId(): ClassId {
return when (this) {
is KotlinClassSnapshot -> classInfo.classId
is RegularJavaClassSnapshot -> classId
is ProtoBasedJavaClassSnapshot -> serializedJavaClass.classId
is InaccessibleClassSnapshot, is ContentHashJavaClassSnapshot -> {
error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
}
is JavaClassSnapshot -> classId
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
}
}
@@ -320,13 +289,7 @@ internal fun ClassSnapshot.getClassId(): ClassId {
internal fun ClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List<ClassId> {
return when (this) {
is KotlinClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
is RegularJavaClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
is ProtoBasedJavaClassSnapshot -> {
val (proto, nameResolver) = serializedJavaClass.toProtoData()
proto.supertypes(TypeTable(proto.typeTable)).map { nameResolver.getClassId(it.className) }
}
is InaccessibleClassSnapshot, is ContentHashJavaClassSnapshot -> {
error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
}
is JavaClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
}
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.incremental.classpathDiff
import org.jetbrains.kotlin.incremental.KotlinClassInfo
import org.jetbrains.kotlin.incremental.SerializedJavaClass
import org.jetbrains.kotlin.incremental.md5
import org.jetbrains.kotlin.incremental.storage.toByteArray
import org.jetbrains.kotlin.name.ClassId
@@ -72,10 +71,7 @@ class KotlinClassSnapshot(
}
/** [ClassSnapshot] of a Java class. */
sealed class JavaClassSnapshot : ClassSnapshot()
/** [JavaClassSnapshot] of a typical Java class. */
class RegularJavaClassSnapshot(
class JavaClassSnapshot(
/** [ClassId] of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */
val classId: ClassId,
@@ -92,7 +88,7 @@ class RegularJavaClassSnapshot(
/** [AbiSnapshot]s of the class's methods. */
val methodsAbi: List<AbiSnapshot>
) : JavaClassSnapshot() {
) : ClassSnapshot() {
val className by lazy {
JvmClassName.byClassId(classId).also {
@@ -123,9 +119,6 @@ class AbiSnapshotForTests(
) : AbiSnapshot(name, abiHash)
/** [JavaClassSnapshot] of a typical Java class which uses protos internally. */
class ProtoBasedJavaClassSnapshot(val serializedJavaClass: SerializedJavaClass) : JavaClassSnapshot()
/**
* [ClassSnapshot] of an inaccessible class.
*
@@ -133,10 +126,3 @@ class ProtoBasedJavaClassSnapshot(val serializedJavaClass: SerializedJavaClass)
* will not require recompilation of other source files.
*/
object InaccessibleClassSnapshot : ClassSnapshot()
/**
* [JavaClassSnapshot] of a Java class where a proper snapshot can't be created for some reason, so we use the hash of the class contents as
* the snapshot instead, so that at least it's still correct when used as an input of the `KotlinCompile` task (when the class contents have
* changed, this snapshot will also change).
*/
class ContentHashJavaClassSnapshot(val contentHash: Long) : JavaClassSnapshot()
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.incremental.classpathDiff
import com.intellij.util.io.DataExternalizer
import org.jetbrains.kotlin.incremental.JavaClassProtoMapValueExternalizer
import org.jetbrains.kotlin.incremental.KotlinClassInfo
import org.jetbrains.kotlin.incremental.storage.*
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -166,27 +165,6 @@ object PackageMemberExternalizer : DataExternalizer<PackageMember> {
object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
override fun save(output: DataOutput, snapshot: JavaClassSnapshot) {
output.writeString(snapshot.javaClass.name)
when (snapshot) {
is RegularJavaClassSnapshot -> RegularJavaClassSnapshotExternalizer.save(output, snapshot)
is ProtoBasedJavaClassSnapshot -> ProtoBasedJavaClassSnapshotExternalizer.save(output, snapshot)
is ContentHashJavaClassSnapshot -> ContentHashJavaClassSnapshotExternalizer.save(output, snapshot)
}
}
override fun read(input: DataInput): JavaClassSnapshot {
return when (val className = input.readString()) {
RegularJavaClassSnapshot::class.java.name -> RegularJavaClassSnapshotExternalizer.read(input)
ProtoBasedJavaClassSnapshot::class.java.name -> ProtoBasedJavaClassSnapshotExternalizer.read(input)
ContentHashJavaClassSnapshot::class.java.name -> ContentHashJavaClassSnapshotExternalizer.read(input)
else -> error("Unrecognized class name: $className")
}
}
}
object RegularJavaClassSnapshotExternalizer : DataExternalizer<RegularJavaClassSnapshot> {
override fun save(output: DataOutput, snapshot: RegularJavaClassSnapshot) {
ClassIdExternalizer.save(output, snapshot.classId)
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
AbiSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers)
@@ -194,8 +172,8 @@ object RegularJavaClassSnapshotExternalizer : DataExternalizer<RegularJavaClassS
ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.methodsAbi)
}
override fun read(input: DataInput): RegularJavaClassSnapshot {
return RegularJavaClassSnapshot(
override fun read(input: DataInput): JavaClassSnapshot {
return JavaClassSnapshot(
classId = ClassIdExternalizer.read(input),
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input),
classAbiExcludingMembers = AbiSnapshotExternalizer.read(input),
@@ -217,17 +195,6 @@ object AbiSnapshotExternalizer : DataExternalizer<AbiSnapshot> {
}
}
object ProtoBasedJavaClassSnapshotExternalizer : DataExternalizer<ProtoBasedJavaClassSnapshot> {
override fun save(output: DataOutput, snapshot: ProtoBasedJavaClassSnapshot) {
JavaClassProtoMapValueExternalizer.save(output, snapshot.serializedJavaClass)
}
override fun read(input: DataInput): ProtoBasedJavaClassSnapshot {
return ProtoBasedJavaClassSnapshot(serializedJavaClass = JavaClassProtoMapValueExternalizer.read(input))
}
}
object InaccessibleClassSnapshotExternalizer : DataExternalizer<InaccessibleClassSnapshot> {
override fun save(output: DataOutput, snapshot: InaccessibleClassSnapshot) {
@@ -238,14 +205,3 @@ object InaccessibleClassSnapshotExternalizer : DataExternalizer<InaccessibleClas
return InaccessibleClassSnapshot
}
}
object ContentHashJavaClassSnapshotExternalizer : DataExternalizer<ContentHashJavaClassSnapshot> {
override fun save(output: DataOutput, snapshot: ContentHashJavaClassSnapshot) {
LongExternalizer.save(output, snapshot.contentHash)
}
override fun read(input: DataInput): ContentHashJavaClassSnapshot {
return ContentHashJavaClassSnapshot(contentHash = LongExternalizer.read(input))
}
}
@@ -7,10 +7,8 @@ package org.jetbrains.kotlin.incremental.classpathDiff
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.ChangesCollector.Companion.getNonPrivateMemberNames
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.utils.DFS
import java.io.File
import java.util.zip.ZipInputStream
@@ -20,145 +18,53 @@ object ClasspathEntrySnapshotter {
private val DEFAULT_CLASS_FILTER = { unixStyleRelativePath: String, isDirectory: Boolean ->
!isDirectory
&& unixStyleRelativePath.endsWith(".class", ignoreCase = true)
&& !unixStyleRelativePath.endsWith("module-info.class", ignoreCase = true)
&& !unixStyleRelativePath.startsWith("meta-inf", ignoreCase = true)
&& !unixStyleRelativePath.startsWith("meta-inf/", ignoreCase = true)
&& !unixStyleRelativePath.equals("module-info.class", ignoreCase = true)
}
fun snapshot(classpathEntry: File, protoBased: Boolean? = null): ClasspathEntrySnapshot {
val classes =
DirectoryOrJarContentsReader.read(classpathEntry, DEFAULT_CLASS_FILTER)
.map { (unixStyleRelativePath, contents) ->
ClassFileWithContents(ClassFile(classpathEntry, unixStyleRelativePath), contents)
}
fun snapshot(classpathEntry: File): ClasspathEntrySnapshot {
val classes = DirectoryOrJarContentsReader
.read(classpathEntry, DEFAULT_CLASS_FILTER)
.map { (unixStyleRelativePath, contents) ->
ClassFileWithContents(ClassFile(classpathEntry, unixStyleRelativePath), contents)
}
val snapshots = try {
ClassSnapshotter.snapshot(classes, protoBased).map { it.withHash }
} catch (e: Throwable) {
if ((protoBased ?: protoBasedDefaultValue) && isKnownProblematicClasspathEntry(classpathEntry)) {
classes.map { ContentHashJavaClassSnapshot(it.contents.md5()).withHash }
} else throw e
}
val snapshots = ClassSnapshotter.snapshot(classes).map { it.withHash }
val relativePathsToSnapshotsMap = classes.map { it.classFile.unixStyleRelativePath }.zipToMap(snapshots)
val relativePathsToSnapshotsMap = classes.map { it.classFile.unixStyleRelativePath }.zip(snapshots).toMap(LinkedHashMap())
return ClasspathEntrySnapshot(relativePathsToSnapshotsMap)
}
/** Returns `true` if it is known that the snapshot of the given classpath entry can't be created for some reason. */
private fun isKnownProblematicClasspathEntry(classpathEntry: File): Boolean {
if (classpathEntry.name.startsWith("tools-jar-api")) {
// [FAULTY JAR] kotlin/dependencies/tools-jar-api/build/libs/tools-jar-api-1.6.255-SNAPSHOT.jar contains class
// com/sun/tools/javac/comp/Infer$GraphStrategy$NodeNotFoundException, but doesn't contain its outer class
// com/sun/tools/javac/comp/Infer$GraphStrategy.
// This happens with a few other similar classes in this jar.
// Therefore, this is a faulty jar, and our snapshotting logic cannot process it.
return true
}
if (classpathEntry.name.startsWith("platform-impl")) {
// ~/.gradle/kotlin-build-dependencies/repo/kotlin.build/ideaIC/203.8084.24/artifacts/lib/platform-impl.jar contains class
// com/intellij/application/options/codeStyle/OptionTableWithPreviewPanel.IntOption. When processing that class,
// BinaryJavaAnnotation$Companion.computeTargetType$resolution_common_jvm in Annotations.kt requires the targetType to be
// JavaClassifierType, but the actual type is PlainJavaPrimitiveType.
// TODO: It's likely that this requirement is incorrect, but let's fix it later.
return true
}
return false
}
}
/** Creates [ClassSnapshot]s of classes. */
object ClassSnapshotter {
/** Creates [ClassSnapshot]s of the given classes. */
fun snapshot(
classes: List<ClassFileWithContents>,
protoBased: Boolean? = null,
includeDebugInfoInSnapshot: Boolean? = null
): List<ClassSnapshot> {
// Find inaccessible classes first, their snapshots will be `InaccessibleClassSnapshot`s.
fun snapshot(classes: List<ClassFileWithContents>, includeDebugInfoInJavaSnapshot: Boolean? = null): List<ClassSnapshot> {
// Find inaccessible classes first
val classesInfo: List<BasicClassInfo> = classes.map { it.classInfo }
val inaccessibleClassesInfo: Set<BasicClassInfo> = getInaccessibleClasses(classesInfo).toSet()
// Snapshot the remaining accessible classes
val accessibleClasses: List<ClassFileWithContents> = classes.filter { it.classInfo !in inaccessibleClassesInfo }
val accessibleSnapshots: List<ClassSnapshot> = doSnapshot(accessibleClasses, protoBased, includeDebugInfoInSnapshot)
val accessibleClassSnapshots: Map<ClassFileWithContents, ClassSnapshot> = accessibleClasses.zipToMap(accessibleSnapshots)
return classes.map { accessibleClassSnapshots[it] ?: InaccessibleClassSnapshot }
}
private fun doSnapshot(
classes: List<ClassFileWithContents>,
protoBased: Boolean? = null,
includeDebugInfoInSnapshot: Boolean? = null
): List<ClassSnapshot> {
// Snapshot Kotlin classes first
val kotlinSnapshots: List<KotlinClassSnapshot?> = classes.map { clazz ->
trySnapshotKotlinClass(clazz)
}
val kotlinClassSnapshots: Map<ClassFileWithContents, KotlinClassSnapshot?> = classes.zipToMap(kotlinSnapshots)
// Snapshot the remaining Java classes
val javaClasses: List<ClassFileWithContents> = classes.filter { kotlinClassSnapshots[it] == null }
val javaSnapshots: List<JavaClassSnapshot> = snapshotJavaClasses(javaClasses, protoBased, includeDebugInfoInSnapshot)
val javaClassSnapshots: Map<ClassFileWithContents, JavaClassSnapshot> = javaClasses.zipToMap(javaSnapshots)
return classes.map { kotlinClassSnapshots[it] ?: javaClassSnapshots[it]!! }
}
/** Creates [KotlinClassSnapshot] of the given class, or returns `null` if the class is not a Kotlin class. */
private fun trySnapshotKotlinClass(classFile: ClassFileWithContents): KotlinClassSnapshot? {
return if (classFile.classInfo.isKotlinClass) {
val kotlinClassInfo =
KotlinClassInfo.createFrom(classFile.classInfo.classId, classFile.classInfo.kotlinClassHeader!!, classFile.contents)
val packageMembers = when (kotlinClassInfo.classKind) {
CLASS, MULTIFILE_CLASS -> null // See `KotlinClassSnapshot.packageMembers`'s kdoc
else -> (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMemberNames().map {
PackageMember(kotlinClassInfo.classId.packageFqName, it)
}
}
KotlinClassSnapshot(kotlinClassInfo, classFile.classInfo.supertypes, packageMembers)
} else null
}
/** Creates [JavaClassSnapshot]s of the given Java classes. */
private fun snapshotJavaClasses(
classes: List<ClassFileWithContents>,
protoBased: Boolean? = null,
includeDebugInfoInSnapshot: Boolean? = null
): List<JavaClassSnapshot> {
return if (protoBased ?: protoBasedDefaultValue) {
snapshotJavaClassesProtoBased(classes)
} else {
classes.map { JavaClassSnapshotter.snapshot(it, includeDebugInfoInSnapshot) }
}
}
private fun snapshotJavaClassesProtoBased(classFilesWithContents: List<ClassFileWithContents>): List<JavaClassSnapshot> {
val classIds = classFilesWithContents.map { it.classInfo.classId }
val classesContents = classFilesWithContents.map { it.contents }
val descriptors: List<JavaClassDescriptor?> = JavaClassDescriptorCreator.create(classIds, classesContents)
val snapshots: List<JavaClassSnapshot> = descriptors.mapIndexed { index, descriptor ->
val classFileWithContents = classFilesWithContents[index]
if (descriptor != null) {
try {
ProtoBasedJavaClassSnapshot(descriptor.toSerializedJavaClass())
} catch (e: Throwable) {
if (isKnownExceptionWhenReadingDescriptor(e)) {
ContentHashJavaClassSnapshot(classFileWithContents.contents.md5())
} else throw e
}
} else {
if (isKnownProblematicClass(classFileWithContents.classFile)) {
ContentHashJavaClassSnapshot(classFileWithContents.contents.md5())
} else {
error(
"Failed to create JavaClassDescriptor for class '${classFileWithContents.classFile.unixStyleRelativePath}'" +
" in '${classFileWithContents.classFile.classRoot.path}'"
)
}
return classes.map {
when {
it.classInfo in inaccessibleClassesInfo -> InaccessibleClassSnapshot
it.classInfo.isKotlinClass -> snapshotKotlinClass(it)
else -> JavaClassSnapshotter.snapshot(it, includeDebugInfoInJavaSnapshot)
}
}
return snapshots
}
/** Creates [KotlinClassSnapshot] of the given Kotlin class (the caller must ensure that the given class is a Kotlin class). */
private fun snapshotKotlinClass(classFile: ClassFileWithContents): KotlinClassSnapshot {
val kotlinClassInfo =
KotlinClassInfo.createFrom(classFile.classInfo.classId, classFile.classInfo.kotlinClassHeader!!, classFile.contents)
val packageMembers = when (kotlinClassInfo.classKind) {
CLASS, MULTIFILE_CLASS -> null // See `KotlinClassSnapshot.packageMembers`'s kdoc
else -> (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMemberNames().map {
PackageMember(kotlinClassInfo.classId.packageFqName, it)
}
}
return KotlinClassSnapshot(kotlinClassInfo, classFile.classInfo.supertypes, packageMembers)
}
/**
@@ -208,57 +114,6 @@ object ClassSnapshotter {
return classesInfo.filter { it.isTransitivelyInaccessible() }
}
/** Returns `true` if it is known that the given exception can be thrown when calling [JavaClassDescriptor.toSerializedJavaClass]. */
private fun isKnownExceptionWhenReadingDescriptor(throwable: Throwable): Boolean {
// When building the Kotlin repo with `./gradlew publish -Pbootstrap.local=true
// -Pbootstrap.local.path=/path/to/kotlin/build/repo -Pkotlin.incremental.useClasspathSnapshot=true`, the build can fail with:
// org.gradle.api.internal.artifacts.transform.TransformException: Execution failed for ClasspathEntrySnapshotTransform: ~/.gradle/wrapper/dists/gradle-6.9-bin/2ecsmyp3bolyybemj56vfn4mt/gradle-6.9/lib/kotlin-reflect-1.4.20.jar
// Caused by: java.lang.IncompatibleClassChangeError: Expected static method 'java.lang.Object org.jetbrains.kotlin.utils.DFS.dfsFromNode(java.lang.Object, org.jetbrains.kotlin.utils.DFS$Neighbors, org.jetbrains.kotlin.utils.DFS$Visited, org.jetbrains.kotlin.utils.DFS$NodeHandler)'
// at org.jetbrains.kotlin.builtins.FunctionTypesKt.isTypeOrSubtypeOf(functionTypes.kt:31)
// (... at JavaClassDescriptor.toSerializedJavaClass)
// The reason is that:
// - org/jetbrains/kotlin/builtins/FunctionTypesKt.class is located in ~/.gradle/caches/jars-8/66425fb82fd14126e9aa07dcd3100b42/kotlin-compiler-embeddable-1.6.255-20210909.213620-55.jar
// - org/jetbrains/kotlin/utils/DFS.class is located in ~/.gradle/caches/jars-8/c716e2e2d26b16f6f1462e59ba44cf3b/buildSrc.jar
// And somehow the two classes are incompatible (probably similar to the NoSuchMethodError documented at JavaClassDescriptorCreatorKt.createBinaryJavaClass).
// This happens to a few other jars inside gradle-6.9/lib.
// However, outside the Kotlin repo build, we don't have this issue (org/jetbrains/kotlin/builtins/FunctionTypesKt.class and
// org/jetbrains/kotlin/utils/DFS.class will be located in the same kotlin-compiler-embeddable.jar).
// Therefore, we special-case the Kotlin repo build below.
// TODO: See how we can address this issue.
return (throwable is IncompatibleClassChangeError &&
DFS::class.java.classLoader.getResource(DFS::class.java.name.replace('.', '/') + ".class")
?.path?.contains("buildSrc.jar") == true
)
}
/** Returns `true` if it is known that the snapshot of the given class can't be created for some reason. */
private fun isKnownProblematicClass(classFile: ClassFile): Boolean {
if (classFile.classRoot.name.startsWith("groovy")
&& classFile.unixStyleRelativePath.endsWith("\$CollectorHelper.class")
) {
// [FAULTY JAR] In groovy-all-1.3-2.5.12.jar and groovy-2.5.11.jar, the bytecode of
// groovy/cli/OptionField\$CollectorHelper.class indicates that its outer class is groovy/cli/OptionField, but the bytecode of
// groovy/cli/OptionField.class does not list any nested classes.
// This happens with a few other CollectorHelper classes in these jars.
// Therefore, these are faulty jars, and our snapshotting logic cannot process it.
return true
}
if (classFile.classRoot.name.startsWith("gradle-api")
&& classFile.unixStyleRelativePath.startsWith("org/gradle/internal/impldep/META-INF/versions")
) {
// [FAULTY JAR] gradle-api-6.9.jar has the following entries:
// - org/gradle/internal/impldep/org/junit/platform/commons/util/ModuleUtils.class
// - org/gradle/internal/impldep/META-INF/versions/9/org/junit/platform/commons/util/ModulesUtils.class
// - org/gradle/internal/impldep/META-INF/versions/9/org/junit/platform/commons/util/ModuleUtils$ModuleReferenceScanner.class
// The META-INF directories are located not at the top level (which is not expected), and those directories escaped our filter
// which filters out top-level META-INF directories. We then failed to snapshot ModuleUtils$ModuleReferenceScanner.class as
// there are 2 versions of ModuleUtils.class, and the one outside the META-INF directory doesn't have any nested classes.
// Therefore, this is a faulty jar, and our snapshotting logic cannot process it.
return true
}
return false
}
}
/** Utility to read the contents of a directory or jar. */
@@ -317,20 +172,3 @@ private object DirectoryOrJarContentsReader {
return relativePathsToContents.toSortedMap().toMap(LinkedHashMap())
}
}
/**
* Combines two lists of the same size into a map.
*
* This method is more efficient than calling `[Iterable.zip].toMap()` as it doesn't create short-lived intermediate [Pair]s as done by
* [Iterable.zip].
*/
private fun <K, V> List<K>.zipToMap(other: List<V>): LinkedHashMap<K, V> {
check(this.size == other.size)
val map = LinkedHashMap<K, V>(size)
indices.forEach { index ->
map[this[index]] = other[index]
}
return map
}
private const val protoBasedDefaultValue = false
@@ -18,11 +18,11 @@ object JavaClassChangesComputer {
* Each list must not contain duplicate classes (having the same [JvmClassName]/[ClassId]).
*/
fun compute(
currentJavaClassSnapshots: List<RegularJavaClassSnapshot>,
previousJavaClassSnapshots: List<RegularJavaClassSnapshot>
currentJavaClassSnapshots: List<JavaClassSnapshot>,
previousJavaClassSnapshots: List<JavaClassSnapshot>
): ChangeSet {
val currentClasses: Map<ClassId, RegularJavaClassSnapshot> = currentJavaClassSnapshots.associateBy { it.classId }
val previousClasses: Map<ClassId, RegularJavaClassSnapshot> = previousJavaClassSnapshots.associateBy { it.classId }
val currentClasses: Map<ClassId, JavaClassSnapshot> = currentJavaClassSnapshots.associateBy { it.classId }
val previousClasses: Map<ClassId, JavaClassSnapshot> = previousJavaClassSnapshots.associateBy { it.classId }
// Note: Added classes can also impact recompilation.
// For example, suppose a source file uses `SomeClass` through `*` imports:
@@ -50,8 +50,8 @@ object JavaClassChangesComputer {
* The two classes must have the same [ClassId].
*/
private fun collectClassChanges(
currentClassSnapshot: RegularJavaClassSnapshot,
previousClassSnapshot: RegularJavaClassSnapshot,
currentClassSnapshot: JavaClassSnapshot,
previousClassSnapshot: JavaClassSnapshot,
changes: ChangeSet.Collector
) {
val classId = currentClassSnapshot.classId.also { check(it == previousClassSnapshot.classId) }
@@ -52,7 +52,7 @@ object JavaClassSnapshotter {
abiClass.methods.clear()
val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
return RegularJavaClassSnapshot(
return JavaClassSnapshot(
classFile.classInfo.classId, classFile.classInfo.supertypes,
classAbiExcludingMembers, fieldsAbi, methodsAbi
)
@@ -13,8 +13,6 @@ import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommo
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.File
import kotlin.test.fail
@@ -185,18 +183,11 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
}
}
@RunWith(Parameterized::class)
class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : ClasspathChangesComputerTest() {
companion object {
@Parameterized.Parameters(name = "protoBased={0}")
@JvmStatic
fun parameters() = listOf(true, false)
}
class JavaOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
@Test
override fun testAbiVersusNonAbiChanges() {
val changes = computeClasspathChanges(File(testDataDir, "testAbiVersusNonAbiChanges/src/java"), tmpDir, protoBased)
val changes = computeClasspathChanges(File(testDataDir, "testAbiVersusNonAbiChanges/src/java"), tmpDir)
Changes(
lookupSymbols = setOf(
LookupSymbol(name = "publicFieldChangedType", scope = "com.example.SomeClass"),
@@ -209,7 +200,7 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
@Test
override fun testModifiedAddedRemovedElements() {
val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/java"), tmpDir, protoBased)
val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/java"), tmpDir)
Changes(
lookupSymbols = setOf(
// ModifiedClassUnchangedMembers
@@ -241,7 +232,7 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
@Test
override fun testImpactAnalysis() {
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir, protoBased)
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir)
Changes(
lookupSymbols = setOf(
LookupSymbol(name = "changedField", scope = "com.example.ChangedSuperClass"),
@@ -305,20 +296,20 @@ class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon()
}
}
private fun computeClasspathChanges(classpathSourceDir: File, tmpDir: TemporaryFolder, protoBased: Boolean = false): Changes {
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
private fun computeClasspathChanges(classpathSourceDir: File, tmpDir: TemporaryFolder): Changes {
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
return computeClasspathChanges(currentSnapshot, previousSnapshot)
}
private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder, protoBased: Boolean): ClasspathSnapshot {
private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder): ClasspathSnapshot {
val classpath = mutableListOf<File>()
val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir ->
val classFiles = compileAll(classpathEntrySourceDir, classpath, tmpDir)
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
val relativePaths = classFiles.map { it.unixStyleRelativePath }
val classSnapshots = classFiles.snapshot(protoBased).map { it.withHash }
val classSnapshots = classFiles.snapshot().map { it.withHash }
ClasspathEntrySnapshot(
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
)
@@ -164,11 +164,11 @@ abstract class ClasspathSnapshotTestCommon {
fun ClassFile.readBytes() = asFile().readBytes()
fun ClassFile.snapshot(protoBased: Boolean? = null): ClassSnapshot = listOf(this).snapshot(protoBased).single()
fun ClassFile.snapshot(): ClassSnapshot = listOf(this).snapshot().single()
fun List<ClassFile>.snapshot(protoBased: Boolean? = null): List<ClassSnapshot> {
fun List<ClassFile>.snapshot(): List<ClassSnapshot> {
val classFilesWithContents = this.map { ClassFileWithContents(it, it.readBytes()) }
return ClassSnapshotter.snapshot(classFilesWithContents, protoBased)
return ClassSnapshotter.snapshot(classFilesWithContents)
}
}
}
@@ -34,7 +34,7 @@ abstract class ClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
@Test
fun `test ClassSnapshotter's result against expected snapshot`() {
val classSnapshot = sourceFile.compileSingle().let {
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInSnapshot = true)
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInJavaSnapshot = true)
}.single()
assertEquals(expectedSnapshotFile.readText(), classSnapshot.toGson())
@@ -32,15 +32,13 @@ import java.text.CharacterIterator
import java.text.StringCharacterIterator
class BinaryJavaClass(
/** If virtualFile is not available, provide classContent & innerClassFinder below. */
override val virtualFile: VirtualFile? = null,
override val virtualFile: VirtualFile,
override val fqName: FqName,
internal val context: ClassifierResolutionContext,
private val signatureParser: BinaryClassSignatureParser,
override var access: Int = 0,
override val outerClass: JavaClass?,
classContent: ByteArray? = null,
private val innerClassFinder: ((Name) -> JavaClass?)? = null
classContent: ByteArray? = null
) : ClassVisitor(ASM_API_VERSION_FOR_CLASS_READING), VirtualFileBoundJavaClass, BinaryJavaModifierListOwner, MutableJavaAnnotationOwner {
override val annotations: MutableCollection<JavaAnnotation> = SmartList()
@@ -113,17 +111,13 @@ class BinaryJavaClass(
}
init {
if (virtualFile == null) {
checkNotNull(classContent) { "classContent must be provided when virtualFile is not available" }
checkNotNull(innerClassFinder) { "innerClassFinder must be provided when virtualFile is not available" }
}
try {
ClassReader(classContent ?: virtualFile!!.contentsToByteArray()).accept(
ClassReader(classContent ?: virtualFile.contentsToByteArray()).accept(
this,
ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES
)
} catch (e: Throwable) {
throw IllegalStateException("Could not read class " + (virtualFile ?: ""), e)
throw IllegalStateException("Could not read class: $virtualFile", e)
}
}
@@ -253,15 +247,11 @@ class BinaryJavaClass(
fun findInnerClass(name: Name, classFileContent: ByteArray?): JavaClass? {
val access = ownInnerClassNameToAccess[name] ?: return null
return if (virtualFile != null) {
virtualFile.parent.findChild("${virtualFile.nameWithoutExtension}$$name.class")?.let {
BinaryJavaClass(
it, fqName.child(name), context.copyForMember(), signatureParser, access, this,
classFileContent
)
}
} else {
innerClassFinder!!(name)
return virtualFile.parent.findChild("${virtualFile.nameWithoutExtension}$$name.class")?.let {
BinaryJavaClass(
it, fqName.child(name), context.copyForMember(), signatureParser, access, this,
classFileContent
)
}
}