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:
committed by
nataliya.valtman
parent
f819a10968
commit
1cb509d529
@@ -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)
|
|
||||||
}
|
|
||||||
+33
-70
@@ -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.FileToCanonicalPathConverter
|
||||||
import org.jetbrains.kotlin.incremental.storage.ListExternalizer
|
import org.jetbrains.kotlin.incremental.storage.ListExternalizer
|
||||||
import org.jetbrains.kotlin.incremental.storage.loadFromFile
|
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.ClassId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.getClassId
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -92,34 +89,38 @@ object ClasspathChangesComputer {
|
|||||||
previousClassSnapshots: List<ClassSnapshot>,
|
previousClassSnapshots: List<ClassSnapshot>,
|
||||||
metrics: BuildMetricsReporter
|
metrics: BuildMetricsReporter
|
||||||
): ChangeSet {
|
): ChangeSet {
|
||||||
val asmBasedSnapshotPredicate: (ClassSnapshot) -> Boolean = {
|
val isKotlinClass: (ClassSnapshot) -> Boolean = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is RegularJavaClassSnapshot -> true
|
is KotlinClassSnapshot -> true
|
||||||
is KotlinClassSnapshot, is ProtoBasedJavaClassSnapshot -> false
|
is JavaClassSnapshot -> false
|
||||||
else -> error("Unexpected type (it should have been handled earlier): ${it.javaClass.name}")
|
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${it.javaClass.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val (currentAsmBasedSnapshots, currentProtoBasedSnapshots) = currentClassSnapshots.partition(asmBasedSnapshotPredicate)
|
val (currentKotlinClassSnapshots, currentJavaClassSnapshots) = currentClassSnapshots.partition(isKotlinClass)
|
||||||
val (previousAsmBasedSnapshots, previousProtoBasedSnapshots) = previousClassSnapshots.partition(asmBasedSnapshotPredicate)
|
val (previousKotlinClassSnapshots, previousJavaClassSnapshots) = previousClassSnapshots.partition(isKotlinClass)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
val kotlinClassChanges = metrics.measure(BuildTime.COMPUTE_KOTLIN_CLASS_CHANGES) {
|
val kotlinClassChanges = metrics.measure(BuildTime.COMPUTE_KOTLIN_CLASS_CHANGES) {
|
||||||
computeChangesForProtoBasedSnapshots(currentProtoBasedSnapshots, previousProtoBasedSnapshots)
|
computeKotlinClassChanges(
|
||||||
|
currentKotlinClassSnapshots as List<KotlinClassSnapshot>,
|
||||||
|
previousKotlinClassSnapshots as List<KotlinClassSnapshot>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val javaClassChanges = metrics.measure(BuildTime.COMPUTE_JAVA_CLASS_CHANGES) {
|
val javaClassChanges = metrics.measure(BuildTime.COMPUTE_JAVA_CLASS_CHANGES) {
|
||||||
JavaClassChangesComputer.compute(
|
JavaClassChangesComputer.compute(
|
||||||
currentAsmBasedSnapshots as List<RegularJavaClassSnapshot>,
|
currentJavaClassSnapshots as List<JavaClassSnapshot>,
|
||||||
previousAsmBasedSnapshots as List<RegularJavaClassSnapshot>
|
previousJavaClassSnapshots as List<JavaClassSnapshot>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return kotlinClassChanges + javaClassChanges
|
return kotlinClassChanges + javaClassChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeChangesForProtoBasedSnapshots(
|
private fun computeKotlinClassChanges(
|
||||||
currentClassSnapshots: List<ClassSnapshot>,
|
currentClassSnapshots: List<KotlinClassSnapshot>,
|
||||||
previousClassSnapshots: List<ClassSnapshot>
|
previousClassSnapshots: List<KotlinClassSnapshot>
|
||||||
): ChangeSet {
|
): ChangeSet {
|
||||||
val workingDir =
|
val workingDir =
|
||||||
FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true)
|
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
|
// - 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).
|
// API's effects).
|
||||||
val unusedChangesCollector = ChangesCollector()
|
val unusedChangesCollector = ChangesCollector()
|
||||||
for (previousSnapshot in previousClassSnapshots) {
|
previousClassSnapshots.forEach {
|
||||||
when (previousSnapshot) {
|
incrementalJvmCache.saveClassToCache(
|
||||||
is KotlinClassSnapshot -> {
|
kotlinClassInfo = it.classInfo,
|
||||||
incrementalJvmCache.saveClassToCache(
|
sourceFiles = null,
|
||||||
kotlinClassInfo = previousSnapshot.classInfo,
|
changesCollector = unusedChangesCollector
|
||||||
sourceFiles = null,
|
)
|
||||||
changesCollector = unusedChangesCollector
|
incrementalJvmCache.markDirty(it.classInfo.className)
|
||||||
)
|
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2:
|
// Step 2:
|
||||||
@@ -164,26 +150,12 @@ object ClasspathChangesComputer {
|
|||||||
// - The intermediate ChangesCollector result will contain symbols in added classes and changed (added/modified/removed) symbols
|
// - 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.
|
// in modified classes. We will collect symbols in removed classes in step 3.
|
||||||
val changesCollector = ChangesCollector()
|
val changesCollector = ChangesCollector()
|
||||||
for (currentSnapshot in currentClassSnapshots) {
|
currentClassSnapshots.forEach {
|
||||||
when (currentSnapshot) {
|
incrementalJvmCache.saveClassToCache(
|
||||||
is KotlinClassSnapshot -> {
|
kotlinClassInfo = it.classInfo,
|
||||||
incrementalJvmCache.saveClassToCache(
|
sourceFiles = null,
|
||||||
kotlinClassInfo = currentSnapshot.classInfo,
|
changesCollector = changesCollector
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3:
|
// Step 3:
|
||||||
@@ -303,11 +275,8 @@ internal object ImpactAnalysis {
|
|||||||
internal fun ClassSnapshot.getClassId(): ClassId {
|
internal fun ClassSnapshot.getClassId(): ClassId {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is KotlinClassSnapshot -> classInfo.classId
|
is KotlinClassSnapshot -> classInfo.classId
|
||||||
is RegularJavaClassSnapshot -> classId
|
is JavaClassSnapshot -> classId
|
||||||
is ProtoBasedJavaClassSnapshot -> serializedJavaClass.classId
|
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
||||||
is InaccessibleClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
|
||||||
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> {
|
internal fun ClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List<ClassId> {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is KotlinClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
is KotlinClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
||||||
is RegularJavaClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
is JavaClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
||||||
is ProtoBasedJavaClassSnapshot -> {
|
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-16
@@ -6,7 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||||
import org.jetbrains.kotlin.incremental.SerializedJavaClass
|
|
||||||
import org.jetbrains.kotlin.incremental.md5
|
import org.jetbrains.kotlin.incremental.md5
|
||||||
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
@@ -72,10 +71,7 @@ class KotlinClassSnapshot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** [ClassSnapshot] of a Java class. */
|
/** [ClassSnapshot] of a Java class. */
|
||||||
sealed class JavaClassSnapshot : ClassSnapshot()
|
class JavaClassSnapshot(
|
||||||
|
|
||||||
/** [JavaClassSnapshot] of a typical Java class. */
|
|
||||||
class RegularJavaClassSnapshot(
|
|
||||||
|
|
||||||
/** [ClassId] of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */
|
/** [ClassId] of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */
|
||||||
val classId: ClassId,
|
val classId: ClassId,
|
||||||
@@ -92,7 +88,7 @@ class RegularJavaClassSnapshot(
|
|||||||
/** [AbiSnapshot]s of the class's methods. */
|
/** [AbiSnapshot]s of the class's methods. */
|
||||||
val methodsAbi: List<AbiSnapshot>
|
val methodsAbi: List<AbiSnapshot>
|
||||||
|
|
||||||
) : JavaClassSnapshot() {
|
) : ClassSnapshot() {
|
||||||
|
|
||||||
val className by lazy {
|
val className by lazy {
|
||||||
JvmClassName.byClassId(classId).also {
|
JvmClassName.byClassId(classId).also {
|
||||||
@@ -123,9 +119,6 @@ class AbiSnapshotForTests(
|
|||||||
|
|
||||||
) : AbiSnapshot(name, abiHash)
|
) : AbiSnapshot(name, abiHash)
|
||||||
|
|
||||||
/** [JavaClassSnapshot] of a typical Java class which uses protos internally. */
|
|
||||||
class ProtoBasedJavaClassSnapshot(val serializedJavaClass: SerializedJavaClass) : JavaClassSnapshot()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [ClassSnapshot] of an inaccessible class.
|
* [ClassSnapshot] of an inaccessible class.
|
||||||
*
|
*
|
||||||
@@ -133,10 +126,3 @@ class ProtoBasedJavaClassSnapshot(val serializedJavaClass: SerializedJavaClass)
|
|||||||
* will not require recompilation of other source files.
|
* will not require recompilation of other source files.
|
||||||
*/
|
*/
|
||||||
object InaccessibleClassSnapshot : ClassSnapshot()
|
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()
|
|
||||||
|
|||||||
+2
-46
@@ -6,7 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import com.intellij.util.io.DataExternalizer
|
import com.intellij.util.io.DataExternalizer
|
||||||
import org.jetbrains.kotlin.incremental.JavaClassProtoMapValueExternalizer
|
|
||||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||||
import org.jetbrains.kotlin.incremental.storage.*
|
import org.jetbrains.kotlin.incremental.storage.*
|
||||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||||
@@ -166,27 +165,6 @@ object PackageMemberExternalizer : DataExternalizer<PackageMember> {
|
|||||||
object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
|
object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: 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)
|
ClassIdExternalizer.save(output, snapshot.classId)
|
||||||
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
||||||
AbiSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers)
|
AbiSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers)
|
||||||
@@ -194,8 +172,8 @@ object RegularJavaClassSnapshotExternalizer : DataExternalizer<RegularJavaClassS
|
|||||||
ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.methodsAbi)
|
ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.methodsAbi)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: DataInput): RegularJavaClassSnapshot {
|
override fun read(input: DataInput): JavaClassSnapshot {
|
||||||
return RegularJavaClassSnapshot(
|
return JavaClassSnapshot(
|
||||||
classId = ClassIdExternalizer.read(input),
|
classId = ClassIdExternalizer.read(input),
|
||||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input),
|
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input),
|
||||||
classAbiExcludingMembers = AbiSnapshotExternalizer.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> {
|
object InaccessibleClassSnapshotExternalizer : DataExternalizer<InaccessibleClassSnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: InaccessibleClassSnapshot) {
|
override fun save(output: DataOutput, snapshot: InaccessibleClassSnapshot) {
|
||||||
@@ -238,14 +205,3 @@ object InaccessibleClassSnapshotExternalizer : DataExternalizer<InaccessibleClas
|
|||||||
return InaccessibleClassSnapshot
|
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+30
-192
@@ -7,10 +7,8 @@ package org.jetbrains.kotlin.incremental.classpathDiff
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.*
|
import org.jetbrains.kotlin.incremental.*
|
||||||
import org.jetbrains.kotlin.incremental.ChangesCollector.Companion.getNonPrivateMemberNames
|
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.load.kotlin.header.KotlinClassHeader.Kind.*
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.utils.DFS
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
@@ -20,145 +18,53 @@ object ClasspathEntrySnapshotter {
|
|||||||
private val DEFAULT_CLASS_FILTER = { unixStyleRelativePath: String, isDirectory: Boolean ->
|
private val DEFAULT_CLASS_FILTER = { unixStyleRelativePath: String, isDirectory: Boolean ->
|
||||||
!isDirectory
|
!isDirectory
|
||||||
&& unixStyleRelativePath.endsWith(".class", ignoreCase = true)
|
&& 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 {
|
fun snapshot(classpathEntry: File): ClasspathEntrySnapshot {
|
||||||
val classes =
|
val classes = DirectoryOrJarContentsReader
|
||||||
DirectoryOrJarContentsReader.read(classpathEntry, DEFAULT_CLASS_FILTER)
|
.read(classpathEntry, DEFAULT_CLASS_FILTER)
|
||||||
.map { (unixStyleRelativePath, contents) ->
|
.map { (unixStyleRelativePath, contents) ->
|
||||||
ClassFileWithContents(ClassFile(classpathEntry, unixStyleRelativePath), contents)
|
ClassFileWithContents(ClassFile(classpathEntry, unixStyleRelativePath), contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
val snapshots = try {
|
val snapshots = ClassSnapshotter.snapshot(classes).map { it.withHash }
|
||||||
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 relativePathsToSnapshotsMap = classes.map { it.classFile.unixStyleRelativePath }.zipToMap(snapshots)
|
val relativePathsToSnapshotsMap = classes.map { it.classFile.unixStyleRelativePath }.zip(snapshots).toMap(LinkedHashMap())
|
||||||
return ClasspathEntrySnapshot(relativePathsToSnapshotsMap)
|
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. */
|
/** Creates [ClassSnapshot]s of classes. */
|
||||||
object ClassSnapshotter {
|
object ClassSnapshotter {
|
||||||
|
|
||||||
/** Creates [ClassSnapshot]s of the given classes. */
|
/** Creates [ClassSnapshot]s of the given classes. */
|
||||||
fun snapshot(
|
fun snapshot(classes: List<ClassFileWithContents>, includeDebugInfoInJavaSnapshot: Boolean? = null): List<ClassSnapshot> {
|
||||||
classes: List<ClassFileWithContents>,
|
// Find inaccessible classes first
|
||||||
protoBased: Boolean? = null,
|
|
||||||
includeDebugInfoInSnapshot: Boolean? = null
|
|
||||||
): List<ClassSnapshot> {
|
|
||||||
// Find inaccessible classes first, their snapshots will be `InaccessibleClassSnapshot`s.
|
|
||||||
val classesInfo: List<BasicClassInfo> = classes.map { it.classInfo }
|
val classesInfo: List<BasicClassInfo> = classes.map { it.classInfo }
|
||||||
val inaccessibleClassesInfo: Set<BasicClassInfo> = getInaccessibleClasses(classesInfo).toSet()
|
val inaccessibleClassesInfo: Set<BasicClassInfo> = getInaccessibleClasses(classesInfo).toSet()
|
||||||
|
|
||||||
// Snapshot the remaining accessible classes
|
return classes.map {
|
||||||
val accessibleClasses: List<ClassFileWithContents> = classes.filter { it.classInfo !in inaccessibleClassesInfo }
|
when {
|
||||||
val accessibleSnapshots: List<ClassSnapshot> = doSnapshot(accessibleClasses, protoBased, includeDebugInfoInSnapshot)
|
it.classInfo in inaccessibleClassesInfo -> InaccessibleClassSnapshot
|
||||||
val accessibleClassSnapshots: Map<ClassFileWithContents, ClassSnapshot> = accessibleClasses.zipToMap(accessibleSnapshots)
|
it.classInfo.isKotlinClass -> snapshotKotlinClass(it)
|
||||||
|
else -> JavaClassSnapshotter.snapshot(it, includeDebugInfoInJavaSnapshot)
|
||||||
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 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() }
|
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. */
|
/** Utility to read the contents of a directory or jar. */
|
||||||
@@ -317,20 +172,3 @@ private object DirectoryOrJarContentsReader {
|
|||||||
return relativePathsToContents.toSortedMap().toMap(LinkedHashMap())
|
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
|
|
||||||
+6
-6
@@ -18,11 +18,11 @@ object JavaClassChangesComputer {
|
|||||||
* Each list must not contain duplicate classes (having the same [JvmClassName]/[ClassId]).
|
* Each list must not contain duplicate classes (having the same [JvmClassName]/[ClassId]).
|
||||||
*/
|
*/
|
||||||
fun compute(
|
fun compute(
|
||||||
currentJavaClassSnapshots: List<RegularJavaClassSnapshot>,
|
currentJavaClassSnapshots: List<JavaClassSnapshot>,
|
||||||
previousJavaClassSnapshots: List<RegularJavaClassSnapshot>
|
previousJavaClassSnapshots: List<JavaClassSnapshot>
|
||||||
): ChangeSet {
|
): ChangeSet {
|
||||||
val currentClasses: Map<ClassId, RegularJavaClassSnapshot> = currentJavaClassSnapshots.associateBy { it.classId }
|
val currentClasses: Map<ClassId, JavaClassSnapshot> = currentJavaClassSnapshots.associateBy { it.classId }
|
||||||
val previousClasses: Map<ClassId, RegularJavaClassSnapshot> = previousJavaClassSnapshots.associateBy { it.classId }
|
val previousClasses: Map<ClassId, JavaClassSnapshot> = previousJavaClassSnapshots.associateBy { it.classId }
|
||||||
|
|
||||||
// Note: Added classes can also impact recompilation.
|
// Note: Added classes can also impact recompilation.
|
||||||
// For example, suppose a source file uses `SomeClass` through `*` imports:
|
// For example, suppose a source file uses `SomeClass` through `*` imports:
|
||||||
@@ -50,8 +50,8 @@ object JavaClassChangesComputer {
|
|||||||
* The two classes must have the same [ClassId].
|
* The two classes must have the same [ClassId].
|
||||||
*/
|
*/
|
||||||
private fun collectClassChanges(
|
private fun collectClassChanges(
|
||||||
currentClassSnapshot: RegularJavaClassSnapshot,
|
currentClassSnapshot: JavaClassSnapshot,
|
||||||
previousClassSnapshot: RegularJavaClassSnapshot,
|
previousClassSnapshot: JavaClassSnapshot,
|
||||||
changes: ChangeSet.Collector
|
changes: ChangeSet.Collector
|
||||||
) {
|
) {
|
||||||
val classId = currentClassSnapshot.classId.also { check(it == previousClassSnapshot.classId) }
|
val classId = currentClassSnapshot.classId.also { check(it == previousClassSnapshot.classId) }
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ object JavaClassSnapshotter {
|
|||||||
abiClass.methods.clear()
|
abiClass.methods.clear()
|
||||||
val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
|
val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
|
||||||
|
|
||||||
return RegularJavaClassSnapshot(
|
return JavaClassSnapshot(
|
||||||
classFile.classInfo.classId, classFile.classInfo.supertypes,
|
classFile.classInfo.classId, classFile.classInfo.supertypes,
|
||||||
classAbiExcludingMembers, fieldsAbi, methodsAbi
|
classAbiExcludingMembers, fieldsAbi, methodsAbi
|
||||||
)
|
)
|
||||||
|
|||||||
+9
-18
@@ -13,8 +13,6 @@ import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommo
|
|||||||
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.Parameterized
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
|
|
||||||
@@ -185,18 +183,11 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RunWith(Parameterized::class)
|
class JavaOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||||
class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : ClasspathChangesComputerTest() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Parameterized.Parameters(name = "protoBased={0}")
|
|
||||||
@JvmStatic
|
|
||||||
fun parameters() = listOf(true, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
override fun testAbiVersusNonAbiChanges() {
|
override fun testAbiVersusNonAbiChanges() {
|
||||||
val changes = computeClasspathChanges(File(testDataDir, "testAbiVersusNonAbiChanges/src/java"), tmpDir, protoBased)
|
val changes = computeClasspathChanges(File(testDataDir, "testAbiVersusNonAbiChanges/src/java"), tmpDir)
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
LookupSymbol(name = "publicFieldChangedType", scope = "com.example.SomeClass"),
|
LookupSymbol(name = "publicFieldChangedType", scope = "com.example.SomeClass"),
|
||||||
@@ -209,7 +200,7 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
override fun testModifiedAddedRemovedElements() {
|
override fun testModifiedAddedRemovedElements() {
|
||||||
val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/java"), tmpDir, protoBased)
|
val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/java"), tmpDir)
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
// ModifiedClassUnchangedMembers
|
// ModifiedClassUnchangedMembers
|
||||||
@@ -241,7 +232,7 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
override fun testImpactAnalysis() {
|
override fun testImpactAnalysis() {
|
||||||
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir, protoBased)
|
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir)
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
LookupSymbol(name = "changedField", scope = "com.example.ChangedSuperClass"),
|
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 {
|
private fun computeClasspathChanges(classpathSourceDir: File, tmpDir: TemporaryFolder): Changes {
|
||||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
|
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
||||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
|
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
||||||
return computeClasspathChanges(currentSnapshot, previousSnapshot)
|
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 classpath = mutableListOf<File>()
|
||||||
val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir ->
|
val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir ->
|
||||||
val classFiles = compileAll(classpathEntrySourceDir, classpath, tmpDir)
|
val classFiles = compileAll(classpathEntrySourceDir, classpath, tmpDir)
|
||||||
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
|
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
|
||||||
|
|
||||||
val relativePaths = classFiles.map { it.unixStyleRelativePath }
|
val relativePaths = classFiles.map { it.unixStyleRelativePath }
|
||||||
val classSnapshots = classFiles.snapshot(protoBased).map { it.withHash }
|
val classSnapshots = classFiles.snapshot().map { it.withHash }
|
||||||
ClasspathEntrySnapshot(
|
ClasspathEntrySnapshot(
|
||||||
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-3
@@ -164,11 +164,11 @@ abstract class ClasspathSnapshotTestCommon {
|
|||||||
|
|
||||||
fun ClassFile.readBytes() = asFile().readBytes()
|
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()) }
|
val classFilesWithContents = this.map { ClassFileWithContents(it, it.readBytes()) }
|
||||||
return ClassSnapshotter.snapshot(classFilesWithContents, protoBased)
|
return ClassSnapshotter.snapshot(classFilesWithContents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ abstract class ClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test ClassSnapshotter's result against expected snapshot`() {
|
fun `test ClassSnapshotter's result against expected snapshot`() {
|
||||||
val classSnapshot = sourceFile.compileSingle().let {
|
val classSnapshot = sourceFile.compileSingle().let {
|
||||||
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInSnapshot = true)
|
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInJavaSnapshot = true)
|
||||||
}.single()
|
}.single()
|
||||||
|
|
||||||
assertEquals(expectedSnapshotFile.readText(), classSnapshot.toGson())
|
assertEquals(expectedSnapshotFile.readText(), classSnapshot.toGson())
|
||||||
|
|||||||
+9
-19
@@ -32,15 +32,13 @@ import java.text.CharacterIterator
|
|||||||
import java.text.StringCharacterIterator
|
import java.text.StringCharacterIterator
|
||||||
|
|
||||||
class BinaryJavaClass(
|
class BinaryJavaClass(
|
||||||
/** If virtualFile is not available, provide classContent & innerClassFinder below. */
|
override val virtualFile: VirtualFile,
|
||||||
override val virtualFile: VirtualFile? = null,
|
|
||||||
override val fqName: FqName,
|
override val fqName: FqName,
|
||||||
internal val context: ClassifierResolutionContext,
|
internal val context: ClassifierResolutionContext,
|
||||||
private val signatureParser: BinaryClassSignatureParser,
|
private val signatureParser: BinaryClassSignatureParser,
|
||||||
override var access: Int = 0,
|
override var access: Int = 0,
|
||||||
override val outerClass: JavaClass?,
|
override val outerClass: JavaClass?,
|
||||||
classContent: ByteArray? = null,
|
classContent: ByteArray? = null
|
||||||
private val innerClassFinder: ((Name) -> JavaClass?)? = null
|
|
||||||
) : ClassVisitor(ASM_API_VERSION_FOR_CLASS_READING), VirtualFileBoundJavaClass, BinaryJavaModifierListOwner, MutableJavaAnnotationOwner {
|
) : ClassVisitor(ASM_API_VERSION_FOR_CLASS_READING), VirtualFileBoundJavaClass, BinaryJavaModifierListOwner, MutableJavaAnnotationOwner {
|
||||||
override val annotations: MutableCollection<JavaAnnotation> = SmartList()
|
override val annotations: MutableCollection<JavaAnnotation> = SmartList()
|
||||||
|
|
||||||
@@ -113,17 +111,13 @@ class BinaryJavaClass(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
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 {
|
try {
|
||||||
ClassReader(classContent ?: virtualFile!!.contentsToByteArray()).accept(
|
ClassReader(classContent ?: virtualFile.contentsToByteArray()).accept(
|
||||||
this,
|
this,
|
||||||
ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES
|
ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} 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? {
|
fun findInnerClass(name: Name, classFileContent: ByteArray?): JavaClass? {
|
||||||
val access = ownInnerClassNameToAccess[name] ?: return null
|
val access = ownInnerClassNameToAccess[name] ?: return null
|
||||||
|
|
||||||
return if (virtualFile != null) {
|
return virtualFile.parent.findChild("${virtualFile.nameWithoutExtension}$$name.class")?.let {
|
||||||
virtualFile.parent.findChild("${virtualFile.nameWithoutExtension}$$name.class")?.let {
|
BinaryJavaClass(
|
||||||
BinaryJavaClass(
|
it, fqName.child(name), context.copyForMember(), signatureParser, access, this,
|
||||||
it, fqName.child(name), context.copyForMember(), signatureParser, access, this,
|
classFileContent
|
||||||
classFileContent
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
innerClassFinder!!(name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user