diff --git a/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt b/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt index d28e0ce336b..9603244c671 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.incremental +import org.jetbrains.kotlin.incremental.DifferenceCalculatorForClass.Companion.getNonPrivateMembers +import org.jetbrains.kotlin.incremental.DifferenceCalculatorForPackageFacade.Companion.getNonPrivateMembers import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.deserialization.NameResolver import org.jetbrains.kotlin.name.FqName @@ -166,7 +168,7 @@ class ChangesCollector { } private fun PackagePartProtoData.collectAllFromPackage(isRemoved: Boolean) { - val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true) + val memberNames = getNonPrivateMembers() if (isRemoved) { collectRemovedMembers(packageFqName, memberNames) } else { @@ -178,14 +180,14 @@ class ChangesCollector { val classFqName = nameResolver.getClassId(proto.fqName).asSingleFqName() if (proto.isCompanionObject) { - val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true) + val memberNames = getNonPrivateMembers() val collectMember = if (isRemoved) this@ChangesCollector::collectRemovedMember else this@ChangesCollector::collectChangedMember collectMember(classFqName.parent(), classFqName.shortName().asString()) memberNames.forEach { collectMember(classFqName, it) } } else { if (!isRemoved && collectAllMembersForNewClass) { - val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true) + val memberNames = getNonPrivateMembers() memberNames.forEach { this@ChangesCollector.collectChangedMember(classFqName, it) } } diff --git a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt index 41248f93b7e..7341a97bc60 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt @@ -23,27 +23,20 @@ import com.intellij.util.io.EnumeratorStringDescriptor import gnu.trove.THashSet import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.build.GeneratedJvmClass -import org.jetbrains.kotlin.incremental.DifferenceCalculatorForClass.Companion.getNonPrivateMembers -import org.jetbrains.kotlin.incremental.DifferenceCalculatorForPackageFacade.Companion.getNonPrivateMembers import org.jetbrains.kotlin.incremental.storage.* -import org.jetbrains.kotlin.inline.inlineAccessors -import org.jetbrains.kotlin.inline.inlineFunctionsAndAccessors +import org.jetbrains.kotlin.inline.InlineFunction +import org.jetbrains.kotlin.inline.InlineFunctionOrAccessor +import org.jetbrains.kotlin.inline.InlinePropertyAccessor import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto import org.jetbrains.kotlin.metadata.ProtoBuf -import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding -import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName -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.ClassReader.SKIP_CODE -import org.jetbrains.org.objectweb.asm.ClassReader.SKIP_DEBUG import java.io.File import java.security.MessageDigest @@ -439,7 +432,7 @@ open class IncrementalJvmCache( // todo: reuse code with InlineFunctionsMap? private inner class ConstantsMap(storageFile: File) : - BasicStringMap>(storageFile, LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer)) { + BasicStringMap>(storageFile, MapExternalizer(StringExternalizer, ConstantValueExternalizer)) { operator fun contains(className: JvmClassName): Boolean = className.internalName in storage @@ -487,7 +480,7 @@ open class IncrementalJvmCache( storage.remove(className.internalName) } - override fun dumpValue(value: LinkedHashMap): String = + override fun dumpValue(value: Map): String = value.dumpMap(Any::toString) } @@ -567,7 +560,10 @@ open class IncrementalJvmCache( } private inner class InlineFunctionsMap(storageFile: File) : - BasicStringMap>(storageFile, LinkedHashMapExternalizer(StringExternalizer, LongExternalizer)) { + BasicStringMap>( + storageFile, + MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer) + ) { @Synchronized fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) { @@ -581,34 +577,36 @@ open class IncrementalJvmCache( storage.remove(key) } - for (methodSignature in oldMap.keys + newMap.keys) { - val inlineFunctionOrAccessorName = functionNameBySignature(methodSignature) - // Note: If we detect a change in an inline property accessor (e.g., `getFoo`), we have two options: - // 1. Report that property `foo` has changed (and ignore `getFoo`) - // 2. Report that property accessor `getFoo` has changed (and ignore `foo`) - // Both options are possible (the compiler generates `LookupSymbol`s for both `foo` and `getFoo` when `getFoo` is inlined). - // Currently, we choose option 2 as it is simpler (i.e., even though inline property accessors are not immediate members of - // a class/package, we treat them like immediate members). - changesCollector.collectMemberIfValueWasChanged( - kotlinClassInfo.scopeFqName(), - inlineFunctionOrAccessorName, - oldMap[methodSignature], - newMap[methodSignature] - ) + // Note: If we detect a change in an inline function `foo` with @JvmName `fooJvmName`, we have two options: + // 1. Report that function `foo` has changed + // 2. Report that method `fooJvmName` has changed + // + // Similarly, if we detect a change in an inline property accessor with JvmName `getFoo` of property `foo`, we have two options: + // 1. Report that property `foo` has changed + // 2. Report that property accessor `getFoo` has changed + // + // The compiler is guaranteed to generate `LookupSymbol`s corresponding to option 1 when referencing inline functions/property + // accessors, but it is not guaranteed to generate `LookupSymbol`s corresponding to option 2. (Currently the compiler seems to + // support option 2 for *inline* functions/property accessors, but that may change.) + // + // In the following, we will choose option 1 as it is cleaner and safer. + val scope = kotlinClassInfo.scopeFqName() + (oldMap.keys + newMap.keys).forEach { + val name = when (it) { + is InlineFunction -> it.kotlinFunctionName + is InlinePropertyAccessor -> it.propertyName + } + changesCollector.collectMemberIfValueWasChanged(scope, name, oldMap[it], newMap[it]) } } - // TODO get name in better way instead of using substringBefore - private fun functionNameBySignature(signature: String): String = - signature.substringBefore("(") - @Synchronized fun remove(className: JvmClassName) { storage.remove(className.internalName) } - override fun dumpValue(value: LinkedHashMap): String = - value.dumpMap { java.lang.Long.toHexString(it) } + override fun dumpValue(value: Map): String = + value.mapKeys { it.key.jvmMethodSignature.asString() }.dumpMap { java.lang.Long.toHexString(it) } } private fun KotlinClassInfo.scopeFqName() = when (classKind) { @@ -669,182 +667,3 @@ fun , V> Map.dumpMap(dumpValue: (V) -> String): String = @TestOnly fun > Collection.dumpCollection(): String = "[${sorted().joinToString(", ", transform = Any::toString)}]" - -/** - * @param includeInlineAccessors If `true`, also include inline property accessors in the returned list. This is necessary because property - * accessors are not immediate members of a class/package, and in some cases we treat them like immediate members (see - * [org.jetbrains.kotlin.incremental.IncrementalJvmCache.InlineFunctionsMap.process]). Note that here we deal with *inline* property - * accessors only as [org.jetbrains.kotlin.incremental.IncrementalJvmCache.InlineFunctionsMap.process] is written only to handle *inline* - * property accessors (and functions). - */ -fun ProtoData.getNonPrivateMemberNames(includeInlineAccessors: Boolean): List { - return when (this) { - is ClassProtoData -> { - getNonPrivateMembers() + (if (includeInlineAccessors) { - inlineAccessors(proto.propertyList, nameResolver, excludePrivateAccessors = true).map { it.name } - } else emptyList()) - } - is PackagePartProtoData -> { - getNonPrivateMembers() + (if (includeInlineAccessors) { - inlineAccessors(proto.propertyList, nameResolver, excludePrivateAccessors = true).map { it.name } - } else emptyList()) - } - } -} - -/** - * Minimal information about a Kotlin class to compute recompilation-triggering changes during an incremental run of the `KotlinCompile` - * task (see [IncrementalJvmCache.saveClassToCache]). - * - * It's important that this class contain only the minimal required information, as it will be part of the classpath snapshot of the - * `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( - 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: LinkedHashMap, - val inlineFunctionsAndAccessorsMap: LinkedHashMap -) { - - val className: JvmClassName by lazy { JvmClassName.byClassId(classId) } - - val protoMapValue: ProtoMapValue by lazy { - ProtoMapValue( - isPackageFacade = classKind != KotlinClassHeader.Kind.CLASS, - BitEncoding.decodeBytes(classHeaderData), - classHeaderStrings - ) - } - - /** - * The [ProtoData] of this class. - * - * NOTE: The caller needs to ensure `classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS` first, as the compiler doesn't write proto - * data to [KotlinClassHeader.Kind.MULTIFILE_CLASS] classes. - */ - val protoData: ProtoData by lazy { - check(classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS) { - "Proto data is not available for KotlinClassHeader.Kind.MULTIFILE_CLASS: $classId" - } - protoMapValue.toProtoData(classId.packageFqName) - } - - /** Name of the companion object of this class (default is "Companion") iff this class HAS a companion object, or null otherwise. */ - val companionObject: ClassId? by lazy { - if (classKind == KotlinClassHeader.Kind.CLASS) { - (protoData as ClassProtoData).getCompanionObjectName()?.let { - classId.createNestedClassId(Name.identifier(it)) - } - } else null - } - - /** List of constants defined in this class iff this class IS a companion object, or null otherwise. The list could be empty. */ - val constantsInCompanionObject: List? by lazy { - if (classKind == KotlinClassHeader.Kind.CLASS) { - val classProtoData = protoData as ClassProtoData - if (classProtoData.proto.isCompanionObject) { - classProtoData.getConstants() - } else null - } else null - } - - companion object { - - fun createFrom(kotlinClass: LocalFileKotlinClass): KotlinClassInfo { - return createFrom(kotlinClass.classId, kotlinClass.classHeader, kotlinClass.fileContents) - } - - fun createFrom(classId: ClassId, classHeader: KotlinClassHeader, classContents: ByteArray): KotlinClassInfo { - val (constants, inlineFunctionsAndAccessors) = getConstantsAndInlineFunctionsOrAccessors(classHeader, classContents) - - return KotlinClassInfo( - classId, - classHeader.kind, - classHeader.data ?: emptyArray(), - classHeader.strings ?: emptyArray(), - classHeader.multifileClassName, - constants.mapKeysTo(LinkedHashMap()) { it.key.name }, - inlineFunctionsAndAccessors.mapKeysTo(LinkedHashMap()) { it.key.asString() }, - ) - } - } -} - -/** - * 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) - - return if (inlineFunctionsAndAccessors.isEmpty()) { - // parsingOptions = (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info are not important for constants - ClassReader(classContents).accept(constantsClassVisitor, SKIP_CODE or SKIP_DEBUG) - Pair(constantsClassVisitor.getResult(), emptyMap()) - } else { - val inlineFunctionsAndAccessorsClassVisitor = - InlineFunctionsAndAccessorsClassVisitor(inlineFunctionsAndAccessors.toSet(), constantsClassVisitor) - // parsingOptions must not include (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info (e.g., line numbers) are important for - // inline functions/accessors - ClassReader(classContents).accept(inlineFunctionsAndAccessorsClassVisitor, 0) - Pair(constantsClassVisitor.getResult(), inlineFunctionsAndAccessorsClassVisitor.getResult()) - } -} - -private class ConstantsClassVisitor : ClassVisitor(Opcodes.API_VERSION) { - private val result = mutableMapOf() - - 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 - - val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL - if (value != null && access and staticFinal == staticFinal) { - result[JvmMemberSignature.Field(name, desc)] = value - } - return null - } - - fun getResult() = result -} - -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(Opcodes.API_VERSION, cv) { - - private val result = mutableMapOf() - private var classVersion: Int? = null - - 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 - } - - override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { - if (access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE) return null - - 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 getResult() = result -} diff --git a/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt b/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt new file mode 100644 index 00000000000..57b121791bf --- /dev/null +++ b/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2010-2022 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 com.intellij.util.io.DataExternalizer +import org.jetbrains.kotlin.incremental.storage.* +import org.jetbrains.kotlin.inline.InlineFunctionOrAccessor +import org.jetbrains.kotlin.inline.inlineFunctionsAndAccessors +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader +import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature +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.* + +/** + * Minimal information about a Kotlin class to compute recompilation-triggering changes during an incremental run of the `KotlinCompile` + * task (see [IncrementalJvmCache.saveClassToCache]). + * + * It's important that this class contain only the minimal required information, as it will be part of the classpath snapshot of the + * `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( + 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 className: JvmClassName by lazy { JvmClassName.byClassId(classId) } + + val protoMapValue: ProtoMapValue by lazy { + ProtoMapValue( + isPackageFacade = classKind != KotlinClassHeader.Kind.CLASS, + BitEncoding.decodeBytes(classHeaderData), + classHeaderStrings + ) + } + + /** + * The [ProtoData] of this class. + * + * NOTE: The caller needs to ensure `classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS` first, as the compiler doesn't write proto + * data to [KotlinClassHeader.Kind.MULTIFILE_CLASS] classes. + */ + val protoData: ProtoData by lazy { + check(classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS) { + "Proto data is not available for KotlinClassHeader.Kind.MULTIFILE_CLASS: $classId" + } + protoMapValue.toProtoData(classId.packageFqName) + } + + /** Name of the companion object of this class (default is "Companion") iff this class HAS a companion object, or null otherwise. */ + val companionObject: ClassId? by lazy { + if (classKind == KotlinClassHeader.Kind.CLASS) { + (protoData as ClassProtoData).getCompanionObjectName()?.let { + classId.createNestedClassId(Name.identifier(it)) + } + } else null + } + + /** List of constants defined in this class iff this class IS a companion object, or null otherwise. The list could be empty. */ + val constantsInCompanionObject: List? by lazy { + if (classKind == KotlinClassHeader.Kind.CLASS) { + val classProtoData = protoData as ClassProtoData + if (classProtoData.proto.isCompanionObject) { + classProtoData.getConstants() + } else null + } else null + } + + companion object { + + fun createFrom(kotlinClass: LocalFileKotlinClass): KotlinClassInfo { + return createFrom(kotlinClass.classId, kotlinClass.classHeader, kotlinClass.fileContents) + } + + fun createFrom(classId: ClassId, classHeader: KotlinClassHeader, classContents: ByteArray): KotlinClassInfo { + val (constants, inlineFunctionsAndAccessors) = getConstantsAndInlineFunctionsOrAccessors(classHeader, classContents) + + return KotlinClassInfo( + classId, + classHeader.kind, + classHeader.data ?: emptyArray(), + classHeader.strings ?: emptyArray(), + classHeader.multifileClassName, + constants.mapKeys { it.key.name }, + inlineFunctionsAndAccessors + ) + } + } +} + +/** + * 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) + + 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()) + } 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 + // 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) + } +} + +private class ConstantsClassVisitor : ClassVisitor(Opcodes.API_VERSION) { + private val result = mutableMapOf() + + 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 + + val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL + if (value != null && access and staticFinal == staticFinal) { + result[JvmMemberSignature.Field(name, desc)] = value + } + return null + } + + fun getResult() = result +} + +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(Opcodes.API_VERSION, cv) { + + private val result = mutableMapOf() + private var classVersion: Int? = null + + 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 + } + + 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 getResult() = result +} + +/** + * [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) +) 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 a5c2fd2d45f..a5d46ff8e87 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt @@ -22,6 +22,10 @@ import com.intellij.util.io.DataExternalizer import com.intellij.util.io.EnumeratorStringDescriptor import com.intellij.util.io.IOUtil import com.intellij.util.io.KeyDescriptor +import org.jetbrains.kotlin.inline.InlineFunction +import org.jetbrains.kotlin.inline.InlineFunctionOrAccessor +import org.jetbrains.kotlin.inline.InlinePropertyAccessor +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.jvm.JvmClassName @@ -165,53 +169,6 @@ object StringToLongMapExternalizer : StringMapExternalizer() { } } -/** [DataExternalizer] for a Kotlin constant. */ -object ConstantExternalizer : DataExternalizer { - - override fun save(output: DataOutput, value: Any) { - when (value) { - is Int -> { - output.writeByte(Kind.INT.ordinal) - output.writeInt(value) - } - is Float -> { - output.writeByte(Kind.FLOAT.ordinal) - output.writeFloat(value) - } - is Long -> { - output.writeByte(Kind.LONG.ordinal) - output.writeLong(value) - } - is Double -> { - output.writeByte(Kind.DOUBLE.ordinal) - output.writeDouble(value) - } - is String -> { - output.writeByte(Kind.STRING.ordinal) - output.writeString(value) - } - else -> throw IllegalStateException("Unexpected constant class: ${value::class.java}") - } - } - - override fun read(input: DataInput): Any { - return when (Kind.values()[input.readByte().toInt()]) { - Kind.INT -> input.readInt() - Kind.FLOAT -> input.readFloat() - Kind.LONG -> input.readLong() - Kind.DOUBLE -> input.readDouble() - Kind.STRING -> input.readString() - } - } - - // The constants' values are provided by ASM, so their types can only be the following. - // See https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html#visitField(int,java.lang.String,java.lang.String,java.lang.String,java.lang.Object) - // (Note: Boolean constants have Integer (0, 1) values in ASM.) - private enum class Kind { - INT, FLOAT, LONG, DOUBLE, STRING - } -} - fun DataExternalizer.saveToFile(file: File, value: T) { return DataOutputStream(FileOutputStream(file).buffered()).use { save(it, value) @@ -248,6 +205,16 @@ object LongExternalizer : DataExternalizer { override fun read(input: DataInput): Long = input.readLong() } +object FloatExternalizer : DataExternalizer { + override fun save(output: DataOutput, value: Float) = output.writeFloat(value) + override fun read(input: DataInput): Float = input.readFloat() +} + +object DoubleExternalizer : DataExternalizer { + override fun save(output: DataOutput, value: Double) = output.writeDouble(value) + override fun read(input: DataInput): Double = input.readDouble() +} + object StringExternalizer : DataExternalizer { override fun save(output: DataOutput, value: String) = IOUtil.writeString(value, output) override fun read(input: DataInput): String = IOUtil.readString(input) @@ -268,6 +235,33 @@ object PathStringDescriptor : EnumeratorStringDescriptor() { } } +/** [DataExternalizer] that delegates to another [DataExternalizer] depending on the type of the object to externalize. */ +class DelegateDataExternalizer( + val types: List>, + val typesExternalizers: List> +) : DataExternalizer { + + init { + check(types.size == typesExternalizers.size) + check(types.size < Byte.MAX_VALUE) // We will writeByte(index), so we need lastIndex (types.size - 1) <= Byte.MAX_VALUE + } + + override fun save(output: DataOutput, objectToExternalize: T) { + val type = types.single { it.isAssignableFrom(objectToExternalize!!::class.java) } + val typeIndex = types.indexOf(type) + + output.writeByte(typeIndex) + @Suppress("UNCHECKED_CAST") + (typesExternalizers[typeIndex] as DataExternalizer).save(output, objectToExternalize) + } + + override fun read(input: DataInput): T { + val typeIndex = input.readByte().toInt() + @Suppress("UNCHECKED_CAST") + return typesExternalizers[typeIndex].read(input) as T + } +} + /** * [DataExternalizer] for a [Collection]. * @@ -401,3 +395,53 @@ class LinkedHashMapExternalizer( keyExternalizer: DataExternalizer, valueExternalizer: DataExternalizer ) : MapExternalizer>(keyExternalizer, valueExternalizer, { size -> LinkedHashMap(size) }) + +object JvmMethodSignatureExternalizer : DataExternalizer { + + override fun save(output: DataOutput, method: JvmMemberSignature.Method) { + StringExternalizer.save(output, method.name) + StringExternalizer.save(output, method.desc) + } + + override fun read(input: DataInput): JvmMemberSignature.Method { + return JvmMemberSignature.Method( + name = StringExternalizer.read(input), + desc = StringExternalizer.read(input) + ) + } +} + +object InlineFunctionOrAccessorExternalizer : DataExternalizer by DelegateDataExternalizer( + types = listOf(InlineFunction::class.java, InlinePropertyAccessor::class.java), + typesExternalizers = listOf(InlineFunctionExternalizer, InlinePropertyAccessorExternalizer) +) + +private object InlineFunctionExternalizer : DataExternalizer { + + override fun save(output: DataOutput, function: InlineFunction) { + JvmMethodSignatureExternalizer.save(output, function.jvmMethodSignature) + StringExternalizer.save(output, function.kotlinFunctionName) + } + + override fun read(input: DataInput): InlineFunction { + return InlineFunction( + jvmMethodSignature = JvmMethodSignatureExternalizer.read(input), + kotlinFunctionName = StringExternalizer.read(input) + ) + } +} + +private object InlinePropertyAccessorExternalizer : DataExternalizer { + + override fun save(output: DataOutput, accessor: InlinePropertyAccessor) { + JvmMethodSignatureExternalizer.save(output, accessor.jvmMethodSignature) + StringExternalizer.save(output, accessor.propertyName) + } + + override fun read(input: DataInput): InlinePropertyAccessor { + return InlinePropertyAccessor( + jvmMethodSignature = JvmMethodSignatureExternalizer.read(input), + propertyName = StringExternalizer.read(input) + ) + } +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/inline/inlineUtil.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/inline/inlineUtil.kt index edd4e01c5cc..c33c8d4d341 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/inline/inlineUtil.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/inline/inlineUtil.kt @@ -24,27 +24,44 @@ import org.jetbrains.kotlin.metadata.deserialization.NameResolver import org.jetbrains.kotlin.metadata.deserialization.TypeTable import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf -import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.JvmMethodSignature import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.serialization.deserialization.ProtoEnumFlags import org.jetbrains.kotlin.serialization.deserialization.descriptorVisibility -fun inlineFunctionsAndAccessors(header: KotlinClassHeader): List { +sealed interface InlineFunctionOrAccessor { + val jvmMethodSignature: JvmMemberSignature.Method +} + +data class InlineFunction( + override val jvmMethodSignature: JvmMemberSignature.Method, + + /** The Kotlin name of the function. It may be different from the JVM name of the function if [JvmName] is used. */ + val kotlinFunctionName: String +) : InlineFunctionOrAccessor + +data class InlinePropertyAccessor( + override val jvmMethodSignature: JvmMemberSignature.Method, + + /** The name of the property that this property accessor belongs to. */ + val propertyName: String +) : InlineFunctionOrAccessor + +fun inlineFunctionsAndAccessors(header: KotlinClassHeader, excludePrivateMembers: Boolean = false): List { val data = header.data ?: return emptyList() val strings = header.strings ?: return emptyList() return when (header.kind) { KotlinClassHeader.Kind.CLASS -> { val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(data, strings) - inlineFunctions(classProto.functionList, nameResolver, classProto.typeTable) + - inlineAccessors(classProto.propertyList, nameResolver) + inlineFunctions(classProto.functionList, nameResolver, classProto.typeTable, excludePrivateMembers) + + inlinePropertyAccessors(classProto.propertyList, nameResolver, excludePrivateMembers) } KotlinClassHeader.Kind.FILE_FACADE, KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> { val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(data, strings) - inlineFunctions(packageProto.functionList, nameResolver, packageProto.typeTable) + - inlineAccessors(packageProto.propertyList, nameResolver) + inlineFunctions(packageProto.functionList, nameResolver, packageProto.typeTable, excludePrivateMembers) + + inlinePropertyAccessors(packageProto.propertyList, nameResolver, excludePrivateMembers) } else -> emptyList() } @@ -53,40 +70,51 @@ fun inlineFunctionsAndAccessors(header: KotlinClassHeader): List, nameResolver: NameResolver, - protoTypeTable: ProtoBuf.TypeTable -): List { + protoTypeTable: ProtoBuf.TypeTable, + excludePrivateFunctions: Boolean = false +): List { val typeTable = TypeTable(protoTypeTable) - return functions.filter { Flags.IS_INLINE.get(it.flags) }.mapNotNull { - JvmProtoBufUtil.getJvmMethodSignature(it, nameResolver, typeTable) - } + return functions + .filter { Flags.IS_INLINE.get(it.flags) && (!excludePrivateFunctions || !isPrivate(it.flags)) } + .mapNotNull { inlineFunction -> + JvmProtoBufUtil.getJvmMethodSignature(inlineFunction, nameResolver, typeTable)?.let { + InlineFunction(jvmMethodSignature = it, kotlinFunctionName = nameResolver.getString(inlineFunction.name)) + } + } } -fun inlineAccessors( +private fun inlinePropertyAccessors( properties: List, nameResolver: NameResolver, excludePrivateAccessors: Boolean = false -): List { - val inlineAccessors = mutableListOf() - - fun isInline(flags: Int) = Flags.IS_INLINE_ACCESSOR.get(flags) - fun isPrivate(flags: Int) = DescriptorVisibilities.isPrivate(ProtoEnumFlags.descriptorVisibility(Flags.VISIBILITY.get(flags))) - +): List { + val inlineAccessors = mutableListOf() properties.forEach { property -> val propertySignature = property.getExtensionOrNull(JvmProtoBuf.propertySignature) ?: return@forEach - - if (property.hasGetterFlags() && isInline(property.getterFlags)) { - if (!(excludePrivateAccessors && isPrivate(property.getterFlags))) { - inlineAccessors.add(propertySignature.getter) - } + if (property.hasGetterFlags() && Flags.IS_INLINE_ACCESSOR.get(property.getterFlags) + && (!excludePrivateAccessors || !isPrivate(property.getterFlags)) + ) { + val getter = propertySignature.getter + inlineAccessors.add( + InlinePropertyAccessor( + JvmMemberSignature.Method(name = nameResolver.getString(getter.name), desc = nameResolver.getString(getter.desc)), + propertyName = nameResolver.getString(property.name) + ) + ) } - if (property.hasSetterFlags() && isInline(property.setterFlags)) { - if (!(excludePrivateAccessors && isPrivate(property.setterFlags))) { - inlineAccessors.add(propertySignature.setter) - } + if (property.hasSetterFlags() && Flags.IS_INLINE_ACCESSOR.get(property.setterFlags) + && (!excludePrivateAccessors || !isPrivate(property.setterFlags)) + ) { + val setter = propertySignature.setter + inlineAccessors.add( + InlinePropertyAccessor( + JvmMemberSignature.Method(name = nameResolver.getString(setter.name), desc = nameResolver.getString(setter.desc)), + propertyName = nameResolver.getString(property.name) + ) + ) } } - - return inlineAccessors.map { - JvmMemberSignature.Method(name = nameResolver.getString(it.name), desc = nameResolver.getString(it.desc)) - } + return inlineAccessors } + +private fun isPrivate(flags: Int) = DescriptorVisibilities.isPrivate(ProtoEnumFlags.descriptorVisibility(Flags.VISIBILITY.get(flags))) diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/AbiSnapshotDiffService.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/AbiSnapshotDiffService.kt index 59ed70b99bc..c5a7452d8a9 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/AbiSnapshotDiffService.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/AbiSnapshotDiffService.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.incremental +import org.jetbrains.kotlin.incremental.DifferenceCalculatorForClass.Companion.getNonPrivateMembers import org.jetbrains.kotlin.metadata.ProtoBuf.Visibility.PRIVATE import org.jetbrains.kotlin.metadata.deserialization.Flags import org.jetbrains.kotlin.name.FqName @@ -97,8 +98,7 @@ class AbiSnapshotDiffService() { when (protoData) { is ClassProtoData -> { fqNames.add(fqName) - symbols.addAll( - protoData.getNonPrivateMemberNames(includeInlineAccessors = true).map { LookupSymbol(it, fqName.asString()) }) + symbols.addAll(protoData.getNonPrivateMembers().map { LookupSymbol(it, fqName.asString()) }) } is PackagePartProtoData -> { symbols.addAll( diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt index 3f9593bee7f..b9cc910bee0 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt @@ -18,12 +18,15 @@ package org.jetbrains.kotlin.incremental import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS import org.jetbrains.kotlin.build.GeneratedFile -import org.jetbrains.kotlin.build.report.* +import org.jetbrains.kotlin.build.report.BuildReporter +import org.jetbrains.kotlin.build.report.debug +import org.jetbrains.kotlin.build.report.info import org.jetbrains.kotlin.build.report.metrics.BuildAttribute import org.jetbrains.kotlin.build.report.metrics.BuildAttribute.* import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric import org.jetbrains.kotlin.build.report.metrics.BuildTime import org.jetbrains.kotlin.build.report.metrics.measure +import org.jetbrains.kotlin.build.report.warn import org.jetbrains.kotlin.cli.common.* import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.messages.MessageCollector @@ -80,12 +83,12 @@ abstract class IncrementalCompilerRunner< allSourceFiles: List, args: Args, messageCollector: MessageCollector, - // when [providedChangedFiles] is not null, changes are provided by external system (e.g. Gradle) + // when `changedFiles` is not null, changes are provided by external system (e.g. Gradle) // otherwise we track source files changes ourselves. - providedChangedFiles: ChangedFiles?, + changedFiles: ChangedFiles?, projectDir: File? = null ): ExitCode = reporter.measure(BuildTime.INCREMENTAL_COMPILATION_DAEMON) { - return when (val result = tryCompileIncrementally(allSourceFiles, providedChangedFiles, args, projectDir, messageCollector)) { + return when (val result = tryCompileIncrementally(allSourceFiles, changedFiles, args, projectDir, messageCollector)) { is ICResult.Completed -> { reporter.debug { "Incremental compilation completed" } result.exitCode @@ -95,7 +98,7 @@ abstract class IncrementalCompilerRunner< reporter.addAttribute(result.reason) compileNonIncrementally( - result.reason, allSourceFiles, args, projectDir, trackChangedFiles = providedChangedFiles == null, messageCollector + result.reason, allSourceFiles, args, projectDir, trackChangedFiles = changedFiles == null, messageCollector ) } is ICResult.Failed -> { @@ -114,7 +117,7 @@ abstract class IncrementalCompilerRunner< reporter.addAttribute(result.reason) compileNonIncrementally( - result.reason, allSourceFiles, args, projectDir, trackChangedFiles = providedChangedFiles == null, messageCollector + result.reason, allSourceFiles, args, projectDir, trackChangedFiles = changedFiles == null, messageCollector ) } } @@ -141,23 +144,24 @@ abstract class IncrementalCompilerRunner< */ private fun tryCompileIncrementally( allSourceFiles: List, - providedChangedFiles: ChangedFiles?, + changedFiles: ChangedFiles?, args: Args, projectDir: File?, messageCollector: MessageCollector ): ICResult { - if (providedChangedFiles is ChangedFiles.Unknown) { + if (changedFiles is ChangedFiles.Unknown) { return ICResult.RequiresRebuild(UNKNOWN_CHANGES_IN_GRADLE_INPUTS) } - providedChangedFiles as ChangedFiles.Known? + changedFiles as ChangedFiles.Known? val caches = createCacheManager(args, projectDir) val exitCode: ExitCode try { // Step 1: Get changed files - val changedFiles: ChangedFiles.Known = try { - getChangedFiles(providedChangedFiles, allSourceFiles, caches) + val knownChangedFiles: ChangedFiles.Known = try { + getChangedFiles(changedFiles, allSourceFiles, caches) } catch (e: Throwable) { + // Don't need to close caches in cases where we return `ICResult.Failed` because we will compile non-incrementally anyway return ICResult.Failed(IC_FAILED_TO_GET_CHANGED_FILES, e) } @@ -166,7 +170,7 @@ abstract class IncrementalCompilerRunner< // Step 2: Compute files to recompile val compilationMode = try { reporter.measure(BuildTime.IC_CALCULATE_INITIAL_DIRTY_SET) { - calculateSourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot ?: emptyMap()) + calculateSourcesToCompile(caches, knownChangedFiles, args, messageCollector, classpathAbiSnapshot ?: emptyMap()) } } catch (e: Throwable) { return ICResult.Failed(IC_FAILED_TO_COMPUTE_FILES_TO_RECOMPILE, e) @@ -227,7 +231,7 @@ abstract class IncrementalCompilerRunner< check(it.containsAll(mainOutputDirs)) { "outputDirs is missing classesDir and workingDir: $it" } } ?: mainOutputDirs - reporter.debug { "Cleaning output directories" } + reporter.debug { "Cleaning ${outputDirsToClean.size} output directories" } cleanOrCreateDirectories(outputDirsToClean) } return createCacheManager(args, projectDir).use { caches -> @@ -266,20 +270,20 @@ abstract class IncrementalCompilerRunner< } private fun getChangedFiles( - providedChangedFiles: ChangedFiles.Known?, + changedFiles: ChangedFiles.Known?, allSourceFiles: List, caches: CacheManager ): ChangedFiles.Known { return when { - providedChangedFiles == null -> caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles) - providedChangedFiles.forDependencies -> { + changedFiles == null -> caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles) + changedFiles.forDependencies -> { val moreChangedFiles = caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles) ChangedFiles.Known( - modified = providedChangedFiles.modified + moreChangedFiles.modified, - removed = providedChangedFiles.removed + moreChangedFiles.removed + modified = changedFiles.modified + moreChangedFiles.modified, + removed = changedFiles.removed + moreChangedFiles.removed ) } - else -> providedChangedFiles + else -> changedFiles } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt index 2f27915a9c4..822396f93d2 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt @@ -109,7 +109,7 @@ fun makeIncrementally( classpathChanges = ClasspathSnapshotDisabled ) //TODO set properly - compiler.compile(sourceFiles, args, messageCollector, providedChangedFiles = null) + compiler.compile(sourceFiles, args, messageCollector, changedFiles = null) } } @@ -388,9 +388,7 @@ open class IncrementalJvmCompilerRunner( super.performWorkBeforeCompilation(compilationMode, args) if (compilationMode is CompilationMode.Incremental) { - val destinationDir = args.destinationAsFile - destinationDir.mkdirs() - args.classpathAsList = listOf(destinationDir) + args.classpathAsList + args.classpathAsList = listOf(args.destinationAsFile) + args.classpathAsList } } 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 f41bfbb8e21..d7d399cbfa1 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,6 +8,7 @@ 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 @@ -53,27 +54,6 @@ object CachedClasspathSnapshotSerializer { } } -internal open class DataExternalizerForSealedClass( - val baseClass: Class, - val inheritorClasses: List>, - val inheritorExternalizers: List> -) : DataExternalizer { - - override fun save(output: DataOutput, objectToExternalize: T) { - val inheritorClassIndex = - inheritorClasses.indexOfFirst { it.isAssignableFrom(objectToExternalize!!::class.java) }.also { check(it != -1) } - output.writeByte(inheritorClassIndex.also { check(it <= Byte.MAX_VALUE) }) // Write byte so the data is smaller - @Suppress("UNCHECKED_CAST") - (inheritorExternalizers[inheritorClassIndex] as DataExternalizer).save(output, objectToExternalize) - } - - override fun read(input: DataInput): T { - val inheritorClassIndex = input.readByte().toInt() - @Suppress("UNCHECKED_CAST") - return inheritorExternalizers[inheritorClassIndex].read(input) as T - } -} - object ClasspathEntrySnapshotExternalizer : DataExternalizer { override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) { @@ -87,26 +67,23 @@ object ClasspathEntrySnapshotExternalizer : DataExternalizer( - baseClass = ClassSnapshot::class.java, - inheritorClasses = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java), - inheritorExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer) +internal object ClassSnapshotExternalizer : DataExternalizer by DelegateDataExternalizer( + types = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java), + typesExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer) ) -internal object AccessibleClassSnapshotExternalizer : DataExternalizerForSealedClass( - baseClass = AccessibleClassSnapshot::class.java, - inheritorClasses = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java), - inheritorExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer) +internal object AccessibleClassSnapshotExternalizer : DataExternalizer by DelegateDataExternalizer( + types = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java), + typesExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer) ) -private object KotlinClassSnapshotExternalizer : DataExternalizerForSealedClass( - baseClass = KotlinClassSnapshot::class.java, - inheritorClasses = listOf( +private object KotlinClassSnapshotExternalizer : DataExternalizer by DelegateDataExternalizer( + types = listOf( RegularKotlinClassSnapshot::class.java, PackageFacadeKotlinClassSnapshot::class.java, MultifileClassKotlinClassSnapshot::class.java ), - inheritorExternalizers = listOf( + typesExternalizers = listOf( RegularKotlinClassSnapshotExternalizer, PackageFacadeKotlinClassSnapshotExternalizer, MultifileClassKotlinClassSnapshotExternalizer @@ -185,8 +162,8 @@ internal object KotlinClassInfoExternalizer : DataExternalizer ListExternalizer(StringExternalizer).save(output, info.classHeaderData.toList()) ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList()) NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName) - LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).save(output, info.constantsMap) - LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).save(output, info.inlineFunctionsAndAccessorsMap) + MapExternalizer(StringExternalizer, ConstantValueExternalizer).save(output, info.constantsMap) + MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).save(output, info.inlineFunctionsAndAccessorsMap) } override fun read(input: DataInput): KotlinClassInfo { @@ -197,8 +174,8 @@ internal object KotlinClassInfoExternalizer : DataExternalizer classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(), classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(), multifileClassName = NullableValueExternalizer(StringExternalizer).read(input), - constantsMap = LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).read(input), - inlineFunctionsAndAccessorsMap = LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).read(input) + constantsMap = MapExternalizer(StringExternalizer, ConstantValueExternalizer).read(input), + inlineFunctionsAndAccessorsMap = MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).read(input) ) } } 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 90e1a2d5f28..443ece74095 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,9 +9,10 @@ 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.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.getNonPrivateMemberNames import org.jetbrains.kotlin.incremental.md5 import org.jetbrains.kotlin.incremental.storage.toByteArray import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.* @@ -96,7 +97,7 @@ object ClassSnapshotter { ) FILE_FACADE, MULTIFILE_CLASS_PART -> PackageFacadeKotlinClassSnapshot( classId, classAbiHash, classMemberLevelSnapshot, - packageMemberNames = kotlinClassInfo.protoData.getNonPrivateMemberNames(includeInlineAccessors = true).toSet() + packageMemberNames = (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMembers().toSet() ) MULTIFILE_CLASS -> MultifileClassKotlinClassSnapshot( classId, classAbiHash, classMemberLevelSnapshot, 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 61cf45b6358..72d5c064130 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 @@ -223,11 +223,9 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() { LookupSymbol(name = "inlineProperty_ChangedType", scope = "com.example"), LookupSymbol(name = "inlineProperty_ChangedType_BackingField", scope = "com.example"), - LookupSymbol(name = "getInlineProperty_ChangedType", scope = "com.example"), - LookupSymbol(name = "setInlineProperty_ChangedType", scope = "com.example"), - LookupSymbol(name = "getInlineProperty_ChangedGetterImpl", scope = "com.example"), - LookupSymbol(name = "setInlineProperty_ChangedSetterImpl", scope = "com.example"), + LookupSymbol(name = "inlineProperty_ChangedGetterImpl", scope = "com.example"), + LookupSymbol(name = "inlineProperty_ChangedSetterImpl", scope = "com.example"), LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass") ), @@ -238,6 +236,26 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() { ).assertEquals(changes) } + @Test + fun testFunctionsAndPropertyAccessorsWithJvmNames() { + val changes = computeClasspathChanges(File(testDataDir, "KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src"), tmpDir) + Changes( + lookupSymbols = setOf( + LookupSymbol(name = "changedFunction", scope = "com.example.SomeClass"), + LookupSymbol(name = "changedPropertyAccessor", scope = "com.example.SomeClass"), + + LookupSymbol(name = "changedInlineFunction", scope = "com.example"), + LookupSymbol(name = "changedInlinePropertyAccessor", scope = "com.example"), + + LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass"), + ), + fqNames = setOf( + "com.example", + "com.example.SomeClass" + ) + ).assertEquals(changes) + } + /** Tests [SupertypesInheritorsImpact]. */ @Test override fun testImpactComputation_SupertypesInheritors() { diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClass.class new file mode 100644 index 00000000000..89cf8b70a4e Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClass.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClassKt.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClassKt.class new file mode 100644 index 00000000000..aa7e950a5f2 Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClassKt.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClass.class new file mode 100644 index 00000000000..8e631dc4575 Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClass.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClassKt.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClassKt.class new file mode 100644 index 00000000000..c2e2a6e3628 Binary files /dev/null and b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClassKt.class differ diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src/current-classpath/com/example/SomeClass.kt b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src/current-classpath/com/example/SomeClass.kt new file mode 100644 index 00000000000..ad256295b21 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src/current-classpath/com/example/SomeClass.kt @@ -0,0 +1,20 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package com.example + +class SomeClass { + + @JvmName("changedFunctionJvmName") + fun changedFunction(): Long = 0 + + val changedPropertyAccessor: Long + @JvmName("changedPropertyAccessorJvmName") + get() = 0 +} + +@JvmName("changedInlineFunctionJvmName") +inline fun changedInlineFunction(): Long = 0 + +inline val changedInlinePropertyAccessor: Long + @JvmName("changedInlinePropertyAccessorJvmName") + get() = 0 \ No newline at end of file diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src/previous-classpath/com/example/SomeClass.kt b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src/previous-classpath/com/example/SomeClass.kt new file mode 100644 index 00000000000..480d3cb2d36 --- /dev/null +++ b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src/previous-classpath/com/example/SomeClass.kt @@ -0,0 +1,20 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package com.example + +class SomeClass { + + @JvmName("changedFunctionJvmName") + fun changedFunction(): Int = 0 + + val changedPropertyAccessor: Int + @JvmName("changedPropertyAccessorJvmName") + get() = 0 +} + +@JvmName("changedInlineFunctionJvmName") +inline fun changedInlineFunction(): Int = 0 + +inline val changedInlinePropertyAccessor: Int + @JvmName("changedInlinePropertyAccessorJvmName") + get() = 0 \ No newline at end of file diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/InlineTestUtil.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/InlineTestUtil.kt index 3bcc2b79c9f..c8df70c3a30 100644 --- a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/InlineTestUtil.kt +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/InlineTestUtil.kt @@ -57,7 +57,7 @@ object InlineTestUtil { val binaryClasses = hashMapOf() for (file in files) { val binaryClass = loadBinaryClass(file) - val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader) + val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader).map { it.jvmMethodSignature }.toSet() val classVisitor = object : ClassVisitorWithName() { override fun visitMethod( @@ -81,7 +81,7 @@ object InlineTestUtil { var doLambdaInliningCheck = true for (file in files) { val binaryClass = loadBinaryClass(file) - val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader) + val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader).map { it.jvmMethodSignature }.toSet() //if inline function creates anonymous object then do not try to check that all lambdas are inlined val classVisitor = object : ClassVisitorWithName() { diff --git a/jps/jps-plugin/testData/incremental/pureKotlin/inlinePropertyInClass/build.log b/jps/jps-plugin/testData/incremental/pureKotlin/inlinePropertyInClass/build.log index b88aa00d6bf..af5ed8bd041 100644 --- a/jps/jps-plugin/testData/incremental/pureKotlin/inlinePropertyInClass/build.log +++ b/jps/jps-plugin/testData/incremental/pureKotlin/inlinePropertyInClass/build.log @@ -8,14 +8,17 @@ Compiling files: src/inline.kt End of files After build round. Marked as dirty by Kotlin: + src/useGetter.kt src/useSetter.kt Exit code: ADDITIONAL_PASS_REQUIRED ------------------------------------------ Cleaning output files: out/production/module/META-INF/module.kotlin_module out/production/module/usage/UseSetterKt.class + out/production/module/usage2/UseGetterKt.class End of files Compiling files: + src/useGetter.kt src/useSetter.kt End of files Exit code: OK @@ -32,14 +35,17 @@ Compiling files: End of files After build round. Marked as dirty by Kotlin: src/useGetter.kt + src/useSetter.kt Exit code: ADDITIONAL_PASS_REQUIRED ------------------------------------------ Cleaning output files: out/production/module/META-INF/module.kotlin_module + out/production/module/usage/UseSetterKt.class out/production/module/usage2/UseGetterKt.class End of files Compiling files: src/useGetter.kt + src/useSetter.kt End of files Exit code: OK ------------------------------------------- \ No newline at end of file +------------------------------------------ diff --git a/jps/jps-plugin/testData/incremental/pureKotlin/inlinePropertyOnTopLevel/build.log b/jps/jps-plugin/testData/incremental/pureKotlin/inlinePropertyOnTopLevel/build.log index eac89c80264..a4544891788 100644 --- a/jps/jps-plugin/testData/incremental/pureKotlin/inlinePropertyOnTopLevel/build.log +++ b/jps/jps-plugin/testData/incremental/pureKotlin/inlinePropertyOnTopLevel/build.log @@ -8,14 +8,17 @@ Compiling files: src/inline.kt End of files After build round. Marked as dirty by Kotlin: + src/useGetter.kt src/useSetter.kt Exit code: ADDITIONAL_PASS_REQUIRED ------------------------------------------ Cleaning output files: out/production/module/META-INF/module.kotlin_module out/production/module/usage/UseSetterKt.class + out/production/module/usage2/UseGetterKt.class End of files Compiling files: + src/useGetter.kt src/useSetter.kt End of files Exit code: OK @@ -32,14 +35,17 @@ Compiling files: End of files After build round. Marked as dirty by Kotlin: src/useGetter.kt + src/useSetter.kt Exit code: ADDITIONAL_PASS_REQUIRED ------------------------------------------ Cleaning output files: out/production/module/META-INF/module.kotlin_module + out/production/module/usage/UseSetterKt.class out/production/module/usage2/UseGetterKt.class End of files Compiling files: src/useGetter.kt + src/useSetter.kt End of files Exit code: OK ------------------------------------------- \ No newline at end of file +------------------------------------------ diff --git a/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt b/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt index ff445459288..81bdc23d193 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt @@ -326,7 +326,7 @@ class GenerateIrRuntime { buildHistoryFile = buildHistoryFile, modulesApiHistory = EmptyModulesApiHistory ) - compiler.compile(allFiles, args, MessageCollector.NONE, providedChangedFiles = null) + compiler.compile(allFiles, args, MessageCollector.NONE, changedFiles = null) } val cleanBuildTime = System.nanoTime() - cleanBuildStart