diff --git a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt index f4c38ce703b..598942de613 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt @@ -441,7 +441,7 @@ open class IncrementalJvmCache( storageFile: File, icContext: IncrementalCompilationContext, ) : - BasicStringMap>(storageFile, MapExternalizer(StringExternalizer, ConstantValueExternalizer), icContext) { + BasicStringMap>(storageFile, MapExternalizer(StringExternalizer, LongExternalizer), icContext) { operator fun contains(className: JvmClassName): Boolean = className.internalName in storage @@ -451,7 +451,7 @@ open class IncrementalJvmCache( val key = kotlinClassInfo.className.internalName val oldMap = storage[key] ?: emptyMap() - val newMap = kotlinClassInfo.constantsMap + val newMap = kotlinClassInfo.extraInfo.constantSnapshots if (newMap.isNotEmpty()) { storage[key] = newMap } else { @@ -489,8 +489,8 @@ open class IncrementalJvmCache( storage.remove(className.internalName) } - override fun dumpValue(value: Map): String = - value.dumpMap(Any::toString) + override fun dumpValue(value: Map): String = + value.dumpMap(Long::toString) } private inner class PackagePartMap( @@ -592,7 +592,7 @@ open class IncrementalJvmCache( val key = kotlinClassInfo.className.internalName val oldMap = storage[key] ?: emptyMap() - val newMap = kotlinClassInfo.inlineFunctionsAndAccessorsMap + val newMap = kotlinClassInfo.extraInfo.inlineFunctionOrAccessorSnapshots if (newMap.isNotEmpty()) { storage[key] = newMap } else { diff --git a/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt b/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt index 00b76643f2c..025c7fa8cca 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt @@ -5,8 +5,12 @@ package org.jetbrains.kotlin.incremental -import com.intellij.util.io.DataExternalizer -import org.jetbrains.kotlin.incremental.storage.* +import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.snapshotClassExcludingMembers +import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.snapshotField +import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.snapshotMethod +import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.sortClassMembers +import org.jetbrains.kotlin.incremental.KotlinClassInfo.ExtraInfo +import org.jetbrains.kotlin.incremental.storage.ProtoMapValue import org.jetbrains.kotlin.inline.InlineFunctionOrAccessor import org.jetbrains.kotlin.inline.inlineFunctionsAndAccessors import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader @@ -16,6 +20,9 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.org.objectweb.asm.* +import org.jetbrains.org.objectweb.asm.tree.ClassNode +import org.jetbrains.org.objectweb.asm.tree.FieldNode +import org.jetbrains.org.objectweb.asm.tree.MethodNode /** * Minimal information about a Kotlin class to compute recompilation-triggering changes during an incremental run of the `KotlinCompile` @@ -25,16 +32,34 @@ import org.jetbrains.org.objectweb.asm.* * `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures, * and should not contain private method signatures, or method implementations. */ -class KotlinClassInfo constructor( +class KotlinClassInfo( val classId: ClassId, val classKind: KotlinClassHeader.Kind, val classHeaderData: Array, // Can be empty val classHeaderStrings: Array, // Can be empty val multifileClassName: String?, // Not null iff classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART - val constantsMap: Map, - val inlineFunctionsAndAccessorsMap: Map + val extraInfo: ExtraInfo ) { + /** Extra information about a Kotlin class that is not captured in the Kotlin class metadata. */ + class ExtraInfo( + + /** + * Snapshot of the class excluding its fields and methods and Kotlin metadata (iff classKind == [KotlinClassHeader.Kind.CLASS]). + * + * For example, the class's annotations which are currently not captured by Kotlin metadata (KT-57919) will be captured here. + * + * Note: It also excludes Kotlin metadata as [ExtraInfo] should only contain info not yet captured in Kotlin metadata. + */ + val classSnapshotExcludingMembers: Long?, + + /** Snapshots of the class's constants (including their values). The map's keys are the constants' names. */ + val constantSnapshots: Map, + + /** Snapshots of the class's inline functions and property accessors (including their implementation). */ + val inlineFunctionOrAccessorSnapshots: Map + ) + val className: JvmClassName by lazy { JvmClassName.byClassId(classId) } val protoMapValue: ProtoMapValue by lazy { @@ -84,123 +109,148 @@ class KotlinClassInfo constructor( } fun createFrom(classId: ClassId, classHeader: KotlinClassHeader, classContents: ByteArray): KotlinClassInfo { - val (constants, inlineFunctionsAndAccessors) = getConstantsAndInlineFunctionsOrAccessors(classHeader, classContents) - return KotlinClassInfo( classId, classHeader.kind, classHeader.data ?: classHeader.incompatibleData ?: emptyArray(), classHeader.strings ?: emptyArray(), classHeader.multifileClassName, - constants.mapKeys { it.key.name }, - inlineFunctionsAndAccessors + extraInfo = getExtraInfo(classHeader, classContents) ) } } } -/** - * Parses the class file only once to get both constants and inline functions/property accessors. This is faster than getting them - * separately in two passes. - */ -private fun getConstantsAndInlineFunctionsOrAccessors( - classHeader: KotlinClassHeader, - classContents: ByteArray -): Pair, Map> { - val constantsClassVisitor = ConstantsClassVisitor() - val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(classHeader, excludePrivateMembers = true) +private fun getExtraInfo(classHeader: KotlinClassHeader, classContents: ByteArray): ExtraInfo { + // Get the list of (non-private) inline functions and accessors from Kotlin class metadata, then find and snapshot them in the bytecode. + // Note: + // - Some of them may not be found in the bytecode. Specifically, internal/private inline functions/accessors may be removed from the + // bytecode if code shrinker is used. For example, `kotlin-reflect-1.7.20.jar` contains `/kotlin/reflect/jvm/internal/UtilKt.class` in + // which the internal inline function `reflectionCall` appears in the Kotlin class metadata (also in the source file), but not in the + // bytecode. When that happens, we will ignore those methods. It is safe to ignore because the methods are not declared in the bytecode + // and therefore can't be referenced. + // - Look for private methods as well because a *public* inline function/accessor may have a *private* corresponding method in the + // bytecode (see `InlineOnlyKt.isInlineOnlyPrivateInBytecode`). + val inlineFunctionsAndAccessors: List = inlineFunctionsAndAccessors(classHeader, excludePrivateMembers = true) + val inlineFunctionOrAccessorSignatures: Map = + inlineFunctionsAndAccessors.associateBy { it.jvmMethodSignature } - return if (inlineFunctionsAndAccessors.isEmpty()) { - // Handle this case differently to improve performance - // parsingOptions = (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info are not important for constants - ClassReader(classContents).accept(constantsClassVisitor, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG) - Pair(constantsClassVisitor.getResult(), emptyMap()) + val parsingOptions = if (inlineFunctionsAndAccessors.isNotEmpty()) { + // Do not pass (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info (e.g., line numbers) are important for inline + // functions/accessors + 0 } else { - val inlineFunctionsAndAccessorsClassVisitor = InlineFunctionsAndAccessorsClassVisitor( - inlineFunctionsAndAccessors.map { it.jvmMethodSignature }.toSet(), - constantsClassVisitor - ) - // parsingOptions must not include (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info (e.g., line numbers) are important for + // Pass (SKIP_CODE, SKIP_DEBUG) to improve performance as method bodies and debug info are not important when we're not analyzing // inline functions/accessors - ClassReader(classContents).accept(inlineFunctionsAndAccessorsClassVisitor, 0) - val constantsMap = constantsClassVisitor.getResult() - val methodHashesMap = inlineFunctionsAndAccessorsClassVisitor.getResult() - val inlineFunctionsAndAccessorsMap = inlineFunctionsAndAccessors.mapNotNull { inline -> - // Note that internal/private inline functions may be removed from the bytecode if code shrinker is used. For example, - // `kotlin-reflect-1.7.20.jar` contains `/kotlin/reflect/jvm/internal/UtilKt.class` in which the internal inline function - // `reflectionCall` appears in the Kotlin metadata (also in the source file), but not in the bytecode. - // When that happens (i.e., when the map lookup below returns null), we will ignore the method. It is safe to ignore because the - // method is not declared in the bytecode and therefore can't be referenced. - methodHashesMap[inline.jvmMethodSignature]?.let { inline to it } - }.toMap() - Pair(constantsMap, inlineFunctionsAndAccessorsMap) + ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG } -} -private class ConstantsClassVisitor : ClassVisitor(Opcodes.API_VERSION) { - private val result = mutableMapOf() + // Load class contents into a `ClassNode`. + // Note that we'll only load methods that are inline functions/accessors (including private methods -- see comment at the top of + // `getExtraInfo`) as we don't need to snapshot the other methods when computing `ExtraInfo`. + val classNode = ClassNode() + val classReader = ClassReader(classContents) + classReader.accept(SkipMethodClassVisitor(classNode) { it !in inlineFunctionOrAccessorSignatures.keys }, parsingOptions) + sortClassMembers(classNode) - override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? { - if (access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE) return null + // Snapshot the class excluding its fields and methods and metadata + val classSnapshotExcludingMembers = if (classHeader.kind == KotlinClassHeader.Kind.CLASS) { + // Also exclude Kotlin metadata (see `ExtraInfo.classSnapshotExcludingMembers`'s kdoc) + snapshotClassExcludingMembers(classNode, alsoExcludeKotlinMetaData = true) + } else null - val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL - if (value != null && access and staticFinal == staticFinal) { - result[JvmMemberSignature.Field(name, desc)] = value + // Snapshot constants + fun FieldNode.isPrivate() = (access and Opcodes.ACC_PRIVATE) != 0 + fun FieldNode.isStaticFinal() = (access and (Opcodes.ACC_STATIC or Opcodes.ACC_FINAL)) == (Opcodes.ACC_STATIC or Opcodes.ACC_FINAL) + + val constantSnapshots: Map = classNode.fields + .filter { !it.isPrivate() && it.isStaticFinal() } + .associate { it.name to snapshotField(it) } + + // Snapshot inline functions and accessors + fun MethodNode.signature() = JvmMemberSignature.Method(name = name, desc = desc) + + val inlineFunctionOrAccessorSnapshots: Map = classNode.methods + .associate { methodNode -> + // `methodNode` must be an inline function/accessor because we loaded only inline functions/accessors into `classNode` + inlineFunctionOrAccessorSignatures[methodNode.signature()]!! to snapshotMethod(methodNode, classNode.version) } - return null - } - fun getResult() = result + return ExtraInfo(classSnapshotExcludingMembers, constantSnapshots, inlineFunctionOrAccessorSnapshots) } -private class InlineFunctionsAndAccessorsClassVisitor( - private val inlineFunctionsAndAccessors: Set, - cv: ConstantsClassVisitor // Note: cv must not override `visitMethod` (it will not be called with the current implementation below) +/** [ClassVisitor] which skips visiting methods where `[shouldSkipMethod] == true`. */ +private class SkipMethodClassVisitor( + cv: ClassVisitor, + private val shouldSkipMethod: (JvmMemberSignature.Method) -> Boolean, ) : ClassVisitor(Opcodes.API_VERSION, cv) { - private val result = mutableMapOf() - private var classVersion: Int? = null + override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { + return if (shouldSkipMethod(JvmMemberSignature.Method(name, desc))) { + null + } else { + cv.visitMethod(access, name, desc, signature, exceptions) + } + } +} - override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array?) { - super.visit(version, access, name, signature, superName, interfaces) - classVersion = version +/** Computes the snapshot of a Java class represented by a [ClassNode]. */ +object ClassNodeSnapshotter { + + fun snapshotClass(classNode: ClassNode): Long { + val classWriter = ClassWriter(0) + classNode.accept(classWriter) + return classWriter.toByteArray().hashToLong() } - override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { - // Note: Do not filter out private methods here because a *public* inline function may actually have a *private* corresponding JVM - // method in the bytecode (see `InlineOnlyKt.isInlineOnlyPrivateInBytecode`). - // Just filter the methods based on the given `inlineFunctionsAndAccessors` set. - val method = JvmMemberSignature.Method(name, desc) - if (method !in inlineFunctionsAndAccessors) return null - - val classWriter = ClassWriter(0) - - // The `version` and `name` parameters are important (see KT-38857), the others can be null. - classWriter.visit(/* version */ classVersion!!, /* access */ 0, /* name */ "ClassWithOneMethod", null, null, null) - - return object : MethodVisitor(Opcodes.API_VERSION, classWriter.visitMethod(access, name, desc, signature, exceptions)) { - override fun visitEnd() { - result[method] = classWriter.toByteArray().md5() - } + fun snapshotClassExcludingMembers(classNode: ClassNode, alsoExcludeKotlinMetaData: Boolean = false): Long { + val originalFields = classNode.fields + val originalMethods = classNode.methods + val originalVisibleAnnotations = classNode.visibleAnnotations + classNode.fields = emptyList() + classNode.methods = emptyList() + if (alsoExcludeKotlinMetaData) { + classNode.visibleAnnotations = originalVisibleAnnotations?.filterNot { it.desc == "Lkotlin/Metadata;" } + } + return snapshotClass(classNode).also { + classNode.fields = originalFields + classNode.methods = originalMethods + classNode.visibleAnnotations = originalVisibleAnnotations } } - fun getResult() = result + fun snapshotField(fieldNode: FieldNode): Long { + val classNode = emptyClass() + classNode.fields.add(fieldNode) + return snapshotClass(classNode) + } + + fun snapshotMethod(methodNode: MethodNode, classVersion: Int): Long { + val classNode = emptyClass() + classNode.version = classVersion // Class version is required for method bodies (see KT-38857) + classNode.methods.add(methodNode) + return snapshotClass(classNode) + } + + /** + * Sorts fields and methods in the given class. + * + * This is useful when we want to ensure a change in the order of the fields and methods doesn't impact the snapshot (i.e., if their + * order has changed in the `.class` file, it shouldn't require recompilation of the other source files). + */ + fun sortClassMembers(classNode: ClassNode) { + classNode.fields.sortWith(compareBy({ it.name }, { it.desc })) + classNode.methods.sortWith(compareBy({ it.name }, { it.desc })) + } + + private fun emptyClass() = ClassNode().also { + // A name is required + it.name = "EmptyClass" + } } -/** - * [DataExternalizer] for the value of a Kotlin constant. - * - * The constants' values are provided by ASM (see the javadoc of [ConstantsClassVisitor.visitField]), so their types can only be the - * following: Integer, Long, Float, Double, String. (Boolean constants have Integer (0, 1) values in ASM.) - */ -object ConstantValueExternalizer : DataExternalizer by DelegateDataExternalizer( - listOf( - java.lang.Integer::class.java, - java.lang.Long::class.java, - java.lang.Float::class.java, - java.lang.Double::class.java, - java.lang.String::class.java - ), - listOf(IntExternalizer, LongExternalizer, FloatExternalizer, DoubleExternalizer, StringExternalizer) -) +fun ByteArray.hashToLong(): Long { + // Note: The returned type `Long` is 64-bit, but we currently don't have a good 64-bit hash function. + // The method below uses `md5` which is 128-bit and converts it to `Long`. + return md5() +} diff --git a/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt b/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt index 625b2b512c3..5b1070d436c 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt @@ -274,8 +274,7 @@ class DelegateDataExternalizer( override fun read(input: DataInput): T { val typeIndex = input.readByte().toInt() - @Suppress("UNCHECKED_CAST") - return typesExternalizers[typeIndex].read(input) as T + return typesExternalizers[typeIndex].read(input) } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClassFile.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClassFile.kt index 63b6d723e41..e1cf5b88d4d 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClassFile.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClassFile.kt @@ -34,17 +34,13 @@ class ClassFileWithContentsProvider( val classFile: ClassFile, val contentsProvider: () -> ByteArray ) { - - fun loadContents(): ClassFileWithContents { - val classContents = contentsProvider.invoke() - val classInfo = BasicClassInfo.compute(classContents) - return ClassFileWithContents(classFile, classContents, classInfo) - } + fun loadContents() = ClassFileWithContents(classFile, contentsProvider.invoke()) } /** Information about the location of a .class file ([ClassFile]) and its contents. */ class ClassFileWithContents( @Suppress("unused") val classFile: ClassFile, - val contents: ByteArray, - val classInfo: BasicClassInfo -) + val contents: ByteArray +) { + val classInfo: BasicClassInfo = BasicClassInfo.compute(contents) +} diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputer.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputer.kt index 98ced528c9b..0839ea144ff 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputer.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputer.kt @@ -246,6 +246,22 @@ object ClasspathChangesComputer { // classes, and symbols in removed classes. incrementalJvmCache.clearCacheForRemovedClasses(changesCollector) + // 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. + 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]!!) { + // `areSubclassesAffected = false` as we don't need to compute impacted symbols at this step + changesCollector.collectSignature(fqName = it.asSingleFqName(), areSubclassesAffected = false) + } + } + // Get the changes and clean up val dirtyData = changesCollector.getChangedSymbols(DoNothingICReporter) workingDir.deleteRecursively() diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt index d7d399cbfa1..51acefd0659 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.incremental.classpathDiff import com.intellij.util.containers.Interner import com.intellij.util.io.DataExternalizer import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric -import org.jetbrains.kotlin.incremental.ConstantValueExternalizer import org.jetbrains.kotlin.incremental.KotlinClassInfo import org.jetbrains.kotlin.incremental.storage.* import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader @@ -162,8 +161,7 @@ internal object KotlinClassInfoExternalizer : DataExternalizer ListExternalizer(StringExternalizer).save(output, info.classHeaderData.toList()) ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList()) NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName) - MapExternalizer(StringExternalizer, ConstantValueExternalizer).save(output, info.constantsMap) - MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).save(output, info.inlineFunctionsAndAccessorsMap) + ExtraInfoExternalizer.save(output, info.extraInfo) } override fun read(input: DataInput): KotlinClassInfo { @@ -174,8 +172,24 @@ internal object KotlinClassInfoExternalizer : DataExternalizer classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(), classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(), multifileClassName = NullableValueExternalizer(StringExternalizer).read(input), - constantsMap = MapExternalizer(StringExternalizer, ConstantValueExternalizer).read(input), - inlineFunctionsAndAccessorsMap = MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).read(input) + extraInfo = ExtraInfoExternalizer.read(input) + ) + } +} + +internal object ExtraInfoExternalizer : DataExternalizer { + + override fun save(output: DataOutput, info: KotlinClassInfo.ExtraInfo) { + NullableValueExternalizer(LongExternalizer).save(output, info.classSnapshotExcludingMembers) + MapExternalizer(StringExternalizer, LongExternalizer).save(output, info.constantSnapshots) + MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).save(output, info.inlineFunctionOrAccessorSnapshots) + } + + override fun read(input: DataInput): KotlinClassInfo.ExtraInfo { + return KotlinClassInfo.ExtraInfo( + classSnapshotExcludingMembers = NullableValueExternalizer(LongExternalizer).read(input), + constantSnapshots = MapExternalizer(StringExternalizer, LongExternalizer).read(input), + inlineFunctionOrAccessorSnapshots = MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).read(input) ) } } @@ -200,7 +214,7 @@ private object JavaClassSnapshotExternalizer : DataExternalizer { +internal object JavaClassMemberLevelSnapshotExternalizer : DataExternalizer { override fun save(output: DataOutput, snapshot: JavaClassMemberLevelSnapshot) { JavaElementSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers) diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt index b63adfdac97..0f30fc5928a 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt @@ -9,15 +9,23 @@ import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter import org.jetbrains.kotlin.build.report.metrics.BuildTime import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter import org.jetbrains.kotlin.build.report.metrics.measure +import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.snapshotClass +import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.snapshotClassExcludingMembers +import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.snapshotField +import org.jetbrains.kotlin.incremental.ClassNodeSnapshotter.snapshotMethod +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.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL -import org.jetbrains.kotlin.incremental.md5 +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.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 @@ -94,7 +102,7 @@ object ClassSnapshotter { snapshotKotlinClass(clazz, granularity) } else -> metrics.measure(BuildTime.SNAPSHOT_JAVA_CLASSES) { - JavaClassSnapshotter.snapshot(clazz, granularity) + snapshotJavaClass(clazz, granularity) } } } @@ -120,11 +128,7 @@ object ClassSnapshotter { } } - /** - * Computes a [KotlinClassSnapshot] of the given Kotlin class. - * - * (The caller must ensure that the given class is a Kotlin class.) - */ + /** Computes a [KotlinClassSnapshot] of the given Kotlin class. */ private fun snapshotKotlinClass(classFile: ClassFileWithContents, granularity: ClassSnapshotGranularity): KotlinClassSnapshot { val kotlinClassInfo = KotlinClassInfo.createFrom(classFile.classInfo.classId, classFile.classInfo.kotlinClassHeader!!, classFile.contents) @@ -145,13 +149,60 @@ object ClassSnapshotter { ) MULTIFILE_CLASS -> MultifileClassKotlinClassSnapshot( classId, classAbiHash, classMemberLevelSnapshot, - constantNames = kotlinClassInfo.constantsMap.keys + constantNames = kotlinClassInfo.extraInfo.constantSnapshots.keys ) SYNTHETIC_CLASS -> error("Unexpected class $classId with class kind ${SYNTHETIC_CLASS.name} (synthetic classes should have been removed earlier)") UNKNOWN -> error("Can't handle class $classId with class kind ${UNKNOWN.name}") } } + /** Computes a [JavaClassSnapshot] of the given Java class. */ + private fun snapshotJavaClass(classFile: ClassFileWithContents, granularity: ClassSnapshotGranularity): JavaClassSnapshot { + // For incremental compilation, we only care about the ABI info of a class. There are 2 approaches: + // 1. Collect ABI info directly + // 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. + + val classNode = ClassNode() + val classReader = ClassReader(classFile.contents) + + // 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) + 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 + val classMemberLevelSnapshot = if (granularity == CLASS_MEMBER_LEVEL) { + JavaClassMemberLevelSnapshot( + classAbiExcludingMembers = JavaElementSnapshot(classNode.name, snapshotClassExcludingMembers(classNode)), + fieldsAbi = classNode.fields.map { JavaElementSnapshot(it.name, snapshotField(it)) }, + methodsAbi = classNode.methods.map { JavaElementSnapshot(it.name, snapshotMethod(it, classNode.version)) } + ) + } else { + null + } + val classAbiHash = if (granularity == CLASS_MEMBER_LEVEL) { + JavaClassMemberLevelSnapshotExternalizer.toByteArray(classMemberLevelSnapshot!!).hashToLong() + } else { + snapshotClass(classNode) + } + + return JavaClassSnapshot( + classId = classFile.classInfo.classId, + classAbiHash = classAbiHash, + classMemberLevelSnapshot = classMemberLevelSnapshot, + supertypes = classFile.classInfo.supertypes + ) + } + } private sealed interface DirectoryOrJarReader : Closeable { @@ -227,9 +278,3 @@ private class JarReader(jar: File) : DirectoryOrJarReader { zipFile.close() } } - -internal fun ByteArray.hashToLong(): Long { - // Note: The returned type `Long` is 64-bit, but we currently don't have a good 64-bit hash function. - // The method below uses `md5` which is 128-bit and converts it to `Long`. - return md5() -} diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassSnapshotter.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassSnapshotter.kt deleted file mode 100644 index f28e3356ac2..00000000000 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/JavaClassSnapshotter.kt +++ /dev/null @@ -1,95 +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.classpathDiff - -import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL -import org.jetbrains.org.objectweb.asm.ClassReader -import org.jetbrains.org.objectweb.asm.ClassWriter -import org.jetbrains.org.objectweb.asm.Opcodes -import org.jetbrains.org.objectweb.asm.tree.ClassNode -import org.jetbrains.org.objectweb.asm.tree.FieldNode -import org.jetbrains.org.objectweb.asm.tree.MethodNode - -/** Computes a [JavaClassSnapshot] of a Java class. */ -object JavaClassSnapshotter { - - fun snapshot(classFile: ClassFileWithContents, granularity: ClassSnapshotGranularity): JavaClassSnapshot { - // We will extract ABI information from the given class and store it into the `abiClass` variable. - // It is acceptable to collect more info than required, but it is incorrect to collect less info than required. - // There are 2 approaches: - // 1. Collect ABI info directly. The collected info must be exhaustive (now and in the future when there are updates to Java/ASM). - // 2. Collect all info and remove non-ABI info. The removed info should be exhaustive, but even if it's not, it is still - // acceptable. - // In the following, we will use the second approach as it is safer. - val abiClass = ClassNode() - - // First, collect all info. - // Note the parsing options passed to ClassReader: - // - SKIP_CODE is set as method bodies will not be part of the ABI of the class. - // - SKIP_DEBUG seems possible but is *not* set just to be safe, so that we don't skip any info that might be important. - // - SKIP_FRAMES or EXPAND_FRAMES is not needed (and not relevant when SKIP_CODE is set). - val classReader = ClassReader(classFile.contents) - classReader.accept(abiClass, ClassReader.SKIP_CODE) - - // Then, remove non-ABI info, which includes: - // - Method bodies: Should have already been removed (see SKIP_CODE above) - // - Private fields and methods - fun Int.isPrivate() = (this and Opcodes.ACC_PRIVATE) != 0 - abiClass.fields.removeIf { it.access.isPrivate() } - abiClass.methods.removeIf { it.access.isPrivate() } - - // Normalize the class: Sort fields and methods as their order is not important (we still use List instead of Set as we want the - // serialized snapshot to be deterministic). - abiClass.fields.sortWith(compareBy({ it.name }, { it.desc })) - abiClass.methods.sortWith(compareBy({ it.name }, { it.desc })) - - // Snapshot the class - val classAbiHash = snapshotClass(abiClass) - val classMemberLevelSnapshot = if (granularity == CLASS_MEMBER_LEVEL) { - val fieldsAbi = abiClass.fields.map { JavaElementSnapshot(it.name, snapshotField(it)) } - val methodsAbi = abiClass.methods.map { JavaElementSnapshot(it.name, snapshotMethod(it)) } - val classAbiExcludingMembers = abiClass.let { - it.fields.clear() - it.methods.clear() - JavaElementSnapshot(it.name, snapshotClass(it)) - } - JavaClassMemberLevelSnapshot(classAbiExcludingMembers, fieldsAbi, methodsAbi) - } else null - - return JavaClassSnapshot( - classId = classFile.classInfo.classId, - classAbiHash = classAbiHash, - classMemberLevelSnapshot = classMemberLevelSnapshot, - supertypes = classFile.classInfo.supertypes - ) - } - - private fun snapshotClass(classNode: ClassNode): Long { - val classWriter = ClassWriter(0) - classNode.accept(classWriter) - return classWriter.toByteArray().hashToLong() - } - - private fun snapshotField(fieldNode: FieldNode): Long { - val classNode = emptyClass() - classNode.fields.add(fieldNode) - return snapshotClass(classNode) - } - - private fun snapshotMethod(methodNode: MethodNode): Long { - val classNode = emptyClass() - classNode.methods.add(methodNode) - return snapshotClass(classNode) - } - - private fun emptyClass() = ClassNode().also { - // We need to provide some minimal info to the class: - // - Name is required. - // - Class version is required if method bodies are considered, but we have removed method bodies in this class, so it's optional. - // - Other info is optional. - it.name = "EmptyClass" - } -} diff --git a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt index 9125e1bafca..4fad567f052 100644 --- a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt +++ b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt @@ -259,6 +259,9 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() { /** Regression test for KT-55021. */ @Test 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. val changes = computeClasspathChanges(File(testDataDir, "KotlinOnly/testRenameFileFacade/src"), tmpDir) Changes( lookupSymbols = setOf( @@ -269,6 +272,20 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() { ).assertEquals(changes) } + /** Regression test for KT-58289.*/ + @Test + fun testChangedAnnotations() { + val changes = computeClasspathChanges(File(testDataDir, "KotlinOnly/testChangedAnnotations/src"), tmpDir) + Changes( + lookupSymbols = setOf( + LookupSymbol(name = "SomeClassWithChangedAnnotation", scope = "com.example"), + ), + fqNames = setOf( + "com.example.SomeClassWithChangedAnnotation", + ) + ).assertEquals(changes) + } + /** Tests [SupertypesInheritorsImpact]. */ @Test override fun testImpactComputation_SupertypesInheritors() { diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/AnnotationOne.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/AnnotationOne.class new file mode 100644 index 00000000000..dfe12344c9a Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/AnnotationOne.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/AnnotationTwo.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/AnnotationTwo.class new file mode 100644 index 00000000000..8bb86052a8a Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/AnnotationTwo.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/SomeClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/SomeClass.class new file mode 100644 index 00000000000..c18987092a1 Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/SomeClass.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/SomeClassWithChangedAnnotation.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/SomeClassWithChangedAnnotation.class new file mode 100644 index 00000000000..0c74ad8d22b Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/current-classpath/com/example/SomeClassWithChangedAnnotation.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/AnnotationOne.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/AnnotationOne.class new file mode 100644 index 00000000000..dfe12344c9a Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/AnnotationOne.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/AnnotationTwo.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/AnnotationTwo.class new file mode 100644 index 00000000000..8bb86052a8a Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/AnnotationTwo.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/SomeClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/SomeClass.class new file mode 100644 index 00000000000..7429952fa3a Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/SomeClass.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/SomeClassWithChangedAnnotation.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/SomeClassWithChangedAnnotation.class new file mode 100644 index 00000000000..2290c065832 Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/classes/previous-classpath/com/example/SomeClassWithChangedAnnotation.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/src/current-classpath/com/example/SomeClass.kt b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/src/current-classpath/com/example/SomeClass.kt new file mode 100644 index 00000000000..48028997a78 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/src/current-classpath/com/example/SomeClass.kt @@ -0,0 +1,20 @@ +package com.example + +@AnnotationTwo // Changed from @AnnotationOne +class SomeClassWithChangedAnnotation { + val unchangeProperty = 0 + fun unchangedFunction() {} +} + +class SomeClass { + + @AnnotationTwo // Changed from @AnnotationOne + val propertyWithChangedAnnotation = 0 + + @AnnotationTwo // Changed from @AnnotationOne + fun functionWithChangedAnnotation() { + } +} + +annotation class AnnotationOne +annotation class AnnotationTwo \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/src/previous-classpath/com/example/SomeClass.kt b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/src/previous-classpath/com/example/SomeClass.kt new file mode 100644 index 00000000000..7541f1dd94b --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testChangedAnnotations/src/previous-classpath/com/example/SomeClass.kt @@ -0,0 +1,20 @@ +package com.example + +@AnnotationOne // Will change to @AnnotationTwo +class SomeClassWithChangedAnnotation { + val unchangeProperty = 0 + fun unchangedFunction() {} +} + +class SomeClass { + + @AnnotationOne // Will change to @AnnotationTwo + val propertyWithChangedAnnotation = 0 + + @AnnotationOne // Will change to @AnnotationTwo + fun functionWithChangedAnnotation() { + } +} + +annotation class AnnotationOne +annotation class AnnotationTwo \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot/com/example/SimpleClass.json b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot/com/example/SimpleClass.json index 57a2f1e6d69..80be85ca59f 100644 --- a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot/com/example/SimpleClass.json +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/java/testSimpleClass/expected-snapshot/com/example/SimpleClass.json @@ -12,7 +12,7 @@ }, "local": false }, - "classAbiHash": -6515999856905133685, + "classAbiHash": -6381547343043280587, "classMemberLevelSnapshot": { "classAbiExcludingMembers": { "name": "com/example/SimpleClass", @@ -27,11 +27,11 @@ "methodsAbi": [ { "name": "\u003cinit\u003e", - "abiHash": 2413123887428571534 + "abiHash": 9200648777950343158 }, { "name": "publicMethod", - "abiHash": 7574889098198027162 + "abiHash": -2847179652577921235 } ] }, diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json index 0bb09f2e3b3..c2b4a9ae022 100644 --- a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot-CLASS_LEVEL/com/example/SimpleClass.json @@ -12,7 +12,7 @@ }, "local": false }, - "classAbiHash": -2605231127310346403, + "classAbiHash": 8283317449409255124, "supertypes": [ { "internalName": "java/lang/Object" diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot/com/example/SimpleClass.json b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot/com/example/SimpleClass.json index 29ab0f2dbbf..9b9fb6060e1 100644 --- a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot/com/example/SimpleClass.json +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest/kotlin/testSimpleClass/expected-snapshot/com/example/SimpleClass.json @@ -12,7 +12,7 @@ }, "local": false }, - "classAbiHash": -2605231127310346403, + "classAbiHash": 8283317449409255124, "classMemberLevelSnapshot": { "classId": { "packageFqName": { @@ -43,8 +43,11 @@ "privateFunction", "publicFunction" ], - "constantsMap": {}, - "inlineFunctionsAndAccessorsMap": {}, + "extraInfo": { + "classSnapshotExcludingMembers": -3712984790086353322, + "constantSnapshots": {}, + "inlineFunctionOrAccessorSnapshots": {} + }, "className$delegate": { "initializer": {}, "_value": {}