IC: Small cleanup in KotlinClassInfo.kt

This is to address the not-yet-resolved comments in
https://github.com/JetBrains/kotlin/pull/5127, plus a few other small
(non-functional) changes.

^KT-58986: In progress
This commit is contained in:
Hung Nguyen
2023-06-05 07:18:53 +01:00
committed by Space Team
parent cba7154e8a
commit ff612f15cf
8 changed files with 151 additions and 80 deletions
@@ -42,5 +42,7 @@ class ClassFileWithContents(
@Suppress("unused") val classFile: ClassFile,
val contents: ByteArray
) {
val classInfo: BasicClassInfo = BasicClassInfo.compute(contents)
val classInfo: BasicClassInfo by lazy {
BasicClassInfo.compute(contents)
}
}
@@ -248,17 +248,20 @@ object ClasspathChangesComputer {
// IncrementalJvmCache currently doesn't use the `KotlinClassInfo.extraInfo.classSnapshotExcludingMembers` info when comparing
// classes, so we need to do it here.
// TODO(KT-58289): Ensure IncrementalJvmCache uses that info when comparing classes.
// TODO(KT-59292): Ensure IncrementalJvmCache uses that info when comparing classes, so we can remove this code.
val currentClassSnapshotsExcludingMembers = currentClassSnapshots
.associate { it.classId to it.classMemberLevelSnapshot!!.extraInfo.classSnapshotExcludingMembers }
.filter { it.value != null }
val previousClassSnapshotsExcludingMembers = previousClassSnapshots
.associate { it.classId to it.classMemberLevelSnapshot!!.extraInfo.classSnapshotExcludingMembers }
.filter { it.value != null }
previousClassSnapshotsExcludingMembers.keys.intersect(currentClassSnapshotsExcludingMembers.keys).forEach {
if (previousClassSnapshotsExcludingMembers[it]!! != currentClassSnapshotsExcludingMembers[it]!!) {
previousClassSnapshots.forEach { previousClassSnapshot ->
val classId = previousClassSnapshot.classId
val currentClassSnapshotExcludingMember = currentClassSnapshotsExcludingMembers[classId]
val previousClassSnapshotExcludingMembers =
previousClassSnapshot.classMemberLevelSnapshot!!.extraInfo.classSnapshotExcludingMembers
if (currentClassSnapshotExcludingMember != null && previousClassSnapshotExcludingMembers != null
&& currentClassSnapshotExcludingMember != previousClassSnapshotExcludingMembers
) {
// `areSubclassesAffected = false` as we don't need to compute impacted symbols at this step
changesCollector.collectSignature(fqName = it.asSingleFqName(), areSubclassesAffected = false)
changesCollector.collectSignature(fqName = classId.asSingleFqName(), areSubclassesAffected = false)
}
}
@@ -177,7 +177,7 @@ internal object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo>
}
}
internal object ExtraInfoExternalizer : DataExternalizer<KotlinClassInfo.ExtraInfo> {
private object ExtraInfoExternalizer : DataExternalizer<KotlinClassInfo.ExtraInfo> {
override fun save(output: DataOutput, info: KotlinClassInfo.ExtraInfo) {
NullableValueExternalizer(LongExternalizer).save(output, info.classSnapshotExcludingMembers)
@@ -17,18 +17,18 @@ import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.sortClassMembers
import org.jetbrains.kotlin.incremental.DifferenceCalculatorForPackageFacade.Companion.getNonPrivateMembers
import org.jetbrains.kotlin.incremental.KotlinClassInfo
import org.jetbrains.kotlin.incremental.PackagePartProtoData
import org.jetbrains.kotlin.incremental.SelectiveClassVisitor
import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL
import org.jetbrains.kotlin.incremental.hashToLong
import org.jetbrains.kotlin.incremental.storage.toByteArray
import org.jetbrains.kotlin.konan.file.use
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.ClassNode
import java.io.Closeable
import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
/** Computes a [ClasspathEntrySnapshot] of a classpath entry (directory or jar). */
@@ -163,23 +163,30 @@ object ClassSnapshotter {
// 2. Remove non-ABI info from the full class
// Note that for incremental compilation to be correct, all ABI info must be collected exhaustively (now and in the future when
// there are updates to Java/ASM), whereas it is acceptable if non-ABI info is not removed completely.
// In the following, we will use the second approach as it is safer and easier.
// Therefore, we will use the second approach as it is safer and easier.
// 1. Create a ClassNode that will contain ABI info of the class
val classNode = ClassNode()
val classReader = ClassReader(classFile.contents)
// 2. Load the class's contents into the ClassNode, removing non-ABI info:
// - Remove private fields and methods
// - Remove method bodies
// - [Not yet implemented] Ignore fields' values except for constants
// Note the `parsingOptions` passed to `classReader`:
// - Pass SKIP_CODE as method bodies are not important
// - Do not pass SKIP_DEBUG as debug info (e.g., method parameter names) may be important
classReader.accept(classNode, ClassReader.SKIP_CODE)
// - Pass SKIP_CODE as we want to remove method bodies
// - Do not pass SKIP_DEBUG as debug info (e.g., method parameter names) may be important
val classReader = ClassReader(classFile.contents)
val selectiveClassVisitor = SelectiveClassVisitor(
classNode,
shouldVisitField = { _: JvmMemberSignature.Field, isPrivate: Boolean, _: Boolean -> !isPrivate },
shouldVisitMethod = { _: JvmMemberSignature.Method, isPrivate: Boolean -> !isPrivate }
)
classReader.accept(selectiveClassVisitor, ClassReader.SKIP_CODE)
// 3. Sort fields and methods as their order is not important
sortClassMembers(classNode)
// Remove private fields and methods
fun Int.isPrivate() = (this and Opcodes.ACC_PRIVATE) != 0
classNode.fields.removeIf { it.access.isPrivate() }
classNode.methods.removeIf { it.access.isPrivate() }
// Snapshot the class
// 4. Snapshot the class
val classMemberLevelSnapshot = if (granularity == CLASS_MEMBER_LEVEL) {
JavaClassMemberLevelSnapshot(
classAbiExcludingMembers = JavaElementSnapshot(classNode.name, snapshotClassExcludingMembers(classNode)),
@@ -190,6 +197,7 @@ object ClassSnapshotter {
null
}
val classAbiHash = if (granularity == CLASS_MEMBER_LEVEL) {
// We already have the class-member-level snapshot, so we can just hash it instead of snapshotting the class again
JavaClassMemberLevelSnapshotExternalizer.toByteArray(classMemberLevelSnapshot!!).hashToLong()
} else {
snapshotClass(classNode)
@@ -261,7 +261,7 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
fun testRenameFileFacade() {
// Check that classpath changes computation doesn't fail.
// Ideally, the returned changes should be empty (renaming a file facade alone shouldn't cause any `LookupSymbol`s to change), but
// it is currently not the case. However, this is just a small efficiency, not a serious bug.
// it is currently not the case. However, this is just a small inefficiency, not an IC-correctness bug.
val changes = computeClasspathChanges(File(testDataDir, "KotlinOnly/testRenameFileFacade/src"), tmpDir)
Changes(
lookupSymbols = setOf(
@@ -275,6 +275,10 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
/** Regression test for KT-58289.*/
@Test
fun testChangedAnnotations() {
// Note: Currently we detect changes to annotations on Kotlin classes, but not annotations on properties and functions, so the
// following changes are missing: `SomeClass.propertyWithChangedAnnotation` and `SomeClass.functionWithChangedAnnotation`.
// Once all annotations are added to Kotlin metadata (KT-57919), we should be able to detect changes to them (i.e., we'll need to
// update this test assertion then).
val changes = computeClasspathChanges(File(testDataDir, "KotlinOnly/testChangedAnnotations/src"), tmpDir)
Changes(
lookupSymbols = setOf(
@@ -138,6 +138,8 @@ abstract class ClasspathSnapshotTestCommon {
"-classpath", (listOf(srcDir) + classpath).joinToString(File.pathSeparator) { it.path }
)
runCommandInNewProcess(commandAndArgs)
classesDir.resolve("META-INF").deleteRecursively()
}
private fun compileJava(srcDir: File, classesDir: File, classpath: List<File>): List<ClassFile> {
@@ -12,7 +12,7 @@
},
"local": false
},
"classAbiHash": -6381547343043280587,
"classAbiHash": 2881075740228300324,
"classMemberLevelSnapshot": {
"classAbiExcludingMembers": {
"name": "com/example/SimpleClass",
@@ -21,17 +21,17 @@
"fieldsAbi": [
{
"name": "publicField",
"abiHash": 8370167348140025367
"abiHash": -2698261112133196916
}
],
"methodsAbi": [
{
"name": "\u003cinit\u003e",
"abiHash": 9200648777950343158
"abiHash": 6894913661751208844
},
{
"name": "publicMethod",
"abiHash": -2847179652577921235
"abiHash": -3018578973377534537
}
]
},