New IC: Include inline property accessors in class/package members

Problem: ClasspathChangesComputer (a core component of the new IC) uses
the existing inline function analysis from the old IC
(IncrementalJvmCache.InlineFunctionsMap). If an inline property accessor
has changed, the inline function analysis will report the name of the
property accessor, not the name of the property.
ClasspathChangesComputer doesn't see the property accessor's name in the
list of class/package members, so it will throw an exception.

Solution: There are 2 options:
  1. If an inline property accessor has changed, the inline function
     analysis can report the name of the property instead of the
     property accessor.
  2. ClasspathChangesComputer can include property accessors in the list
     of class/package members.

In this commit, we will choose option 2 as it is simpler.

Test: New KotlinOnlyClasspathChangesComputerTest.testPropertyAccessors

Small cleanup - PLS SQUASH INTO PREVIOUS COMMIT

  - Address review
  - Fix failed tests
  - Add some trivial changes

^KT-53871 Fixed
This commit is contained in:
Hung Nguyen
2022-09-04 15:39:32 +01:00
committed by nataliya.valtman
parent 2ba7c7b8a9
commit 03f83ff339
23 changed files with 376 additions and 126 deletions
@@ -36,32 +36,6 @@ class ChangesCollector {
fun protoDataChanges(): Map<FqName, ProtoData> = storage
fun protoDataRemoved(): List<FqName> = removed
companion object {
fun <T> T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List<MessageLite>) =
members.flatMap { this.it().filterNot { it.isPrivate }.names(nameResolver) }.toSet()
fun ClassProtoData.getNonPrivateMemberNames(): Set<String> {
return proto.getNonPrivateNames(
nameResolver,
// The types below should match the logic at `DifferenceCalculatorForClass.difference`
ProtoBuf.Class::getConstructorList,
ProtoBuf.Class::getFunctionList,
ProtoBuf.Class::getPropertyList,
ProtoBuf.Class::getTypeAliasList
) + proto.enumEntryList.map { nameResolver.getString(it.name) }
}
fun PackagePartProtoData.getNonPrivateMemberNames(): Set<String> {
return proto.getNonPrivateNames(
nameResolver,
// The types below should match the logic at `DifferenceCalculatorForPackageFacade.difference`
ProtoBuf.Package::getFunctionList,
ProtoBuf.Package::getPropertyList,
ProtoBuf.Package::getTypeAliasList
)
}
}
fun changes(): List<ChangeInfo> {
val changes = arrayListOf<ChangeInfo>()
@@ -192,7 +166,7 @@ class ChangesCollector {
}
private fun PackagePartProtoData.collectAllFromPackage(isRemoved: Boolean) {
val memberNames = getNonPrivateMemberNames()
val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true)
if (isRemoved) {
collectRemovedMembers(packageFqName, memberNames)
} else {
@@ -204,14 +178,14 @@ class ChangesCollector {
val classFqName = nameResolver.getClassId(proto.fqName).asSingleFqName()
if (proto.isCompanionObject) {
val memberNames = getNonPrivateMemberNames()
val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true)
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()
val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true)
memberNames.forEach { this@ChangesCollector.collectChangedMember(classFqName, it) }
}
@@ -23,8 +23,11 @@ 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.inlineFunctionsJvmNames
import org.jetbrains.kotlin.inline.inlineAccessors
import org.jetbrains.kotlin.inline.inlineFunctionsAndAccessors
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
@@ -39,7 +42,8 @@ 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.*
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
@@ -570,19 +574,26 @@ open class IncrementalJvmCache(
val key = kotlinClassInfo.className.internalName
val oldMap = storage[key] ?: emptyMap()
val newMap = kotlinClassInfo.inlineFunctionsMap
val newMap = kotlinClassInfo.inlineFunctionsAndAccessorsMap
if (newMap.isNotEmpty()) {
storage[key] = newMap
} else {
storage.remove(key)
}
for (fn in oldMap.keys + newMap.keys) {
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(),
functionNameBySignature(fn),
oldMap[fn],
newMap[fn]
inlineFunctionOrAccessorName,
oldMap[methodSignature],
newMap[methodSignature]
)
}
}
@@ -659,6 +670,28 @@ fun <K : Comparable<K>, V> Map<K, V>.dumpMap(dumpValue: (V) -> String): String =
fun <T : Comparable<T>> Collection<T>.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<String> {
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]).
@@ -674,7 +707,7 @@ class KotlinClassInfo constructor(
val classHeaderStrings: Array<String>, // Can be empty
val multifileClassName: String?, // Not null iff classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART
val constantsMap: LinkedHashMap<String, Any>,
val inlineFunctionsMap: LinkedHashMap<String, Long>
val inlineFunctionsAndAccessorsMap: LinkedHashMap<String, Long>
) {
val className: JvmClassName by lazy { JvmClassName.byClassId(classId) }
@@ -726,48 +759,55 @@ class KotlinClassInfo constructor(
}
fun createFrom(classId: ClassId, classHeader: KotlinClassHeader, classContents: ByteArray): KotlinClassInfo {
val constantsAndInlineFunctions = getConstantsAndInlineFunctions(classHeader, classContents)
val (constants, inlineFunctionsAndAccessors) = getConstantsAndInlineFunctionsOrAccessors(classHeader, classContents)
return KotlinClassInfo(
classId,
classHeader.kind,
classHeader.data ?: classHeader.incompatibleData ?: emptyArray(),
classHeader.data ?: emptyArray(),
classHeader.strings ?: emptyArray(),
classHeader.multifileClassName,
constantsMap = constantsAndInlineFunctions.first,
inlineFunctionsMap = constantsAndInlineFunctions.second
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. */
private fun getConstantsAndInlineFunctions(
/**
* 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<LinkedHashMap<String, Any>, LinkedHashMap<String, Long>> {
): Pair<Map<JvmMemberSignature.Field, Any>, Map<JvmMemberSignature.Method, Long>> {
val constantsClassVisitor = ConstantsClassVisitor()
val inlineFunctionSignatures = inlineFunctionsJvmNames(classHeader)
val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(classHeader)
return if (inlineFunctionSignatures.isEmpty()) {
ClassReader(classContents).accept(constantsClassVisitor, SKIP_CODE or SKIP_DEBUG or SKIP_FRAMES)
Pair(constantsClassVisitor.getResult(), LinkedHashMap())
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 inlineFunctionsClassVisitor = InlineFunctionsClassVisitor(inlineFunctionSignatures, constantsClassVisitor)
ClassReader(classContents).accept(inlineFunctionsClassVisitor, 0)
Pair(constantsClassVisitor.getResult(), inlineFunctionsClassVisitor.getResult())
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 = LinkedHashMap<String, Any>()
private val result = mutableMapOf<JvmMemberSignature.Field, Any>()
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[name] = value
result[JvmMemberSignature.Field(name, desc)] = value
}
return null
}
@@ -775,12 +815,12 @@ private class ConstantsClassVisitor : ClassVisitor(Opcodes.API_VERSION) {
fun getResult() = result
}
private class InlineFunctionsClassVisitor(
private val inlineFunctionSignatures: Set<String>,
cv: ConstantsClassVisitor // Note: cv must not override the visitMethod (it will not be called with the current implementation below)
private class InlineFunctionsAndAccessorsClassVisitor(
private val inlineFunctionsAndAccessors: Set<JvmMemberSignature.Method>,
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 = LinkedHashMap<String, Long>()
private val result = mutableMapOf<JvmMemberSignature.Method, Long>()
private var classVersion: Int? = null
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array<out String>?) {
@@ -791,11 +831,8 @@ private class InlineFunctionsClassVisitor(
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
if (access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE) return null
// Note: Here, functionSignature = name + descriptor.
// It is different from the `signature` parameter above, which is essentially a more detailed descriptor when generics are used
// (or null otherwise).
val functionSignature = JvmMemberSignature.Method(name, desc).asString()
if (functionSignature !in inlineFunctionSignatures) return null
val method = JvmMemberSignature.Method(name, desc)
if (method !in inlineFunctionsAndAccessors) return null
val classWriter = ClassWriter(0)
@@ -804,7 +841,7 @@ private class InlineFunctionsClassVisitor(
return object : MethodVisitor(Opcodes.API_VERSION, classWriter.visitMethod(access, name, desc, signature, exceptions)) {
override fun visitEnd() {
result[functionSignature] = classWriter.toByteArray().md5()
result[method] = classWriter.toByteArray().md5()
}
}
}
@@ -51,17 +51,17 @@ fun ProtoMapValue.toProtoData(packageFqName: FqName): ProtoData =
}
internal val MessageLite.isPrivate: Boolean
get() = DescriptorVisibilities.isPrivate(
ProtoEnumFlags.descriptorVisibility(
when (this) {
is ProtoBuf.Constructor -> Flags.VISIBILITY.get(flags)
is ProtoBuf.Function -> Flags.VISIBILITY.get(flags)
is ProtoBuf.Property -> Flags.VISIBILITY.get(flags)
is ProtoBuf.TypeAlias -> Flags.VISIBILITY.get(flags)
else -> error("Unknown message: $this")
}
)
)
get() {
val visibility = when (this) {
is ProtoBuf.Constructor -> Flags.VISIBILITY.get(flags)
is ProtoBuf.Function -> Flags.VISIBILITY.get(flags)
is ProtoBuf.Property -> Flags.VISIBILITY.get(flags)
is ProtoBuf.TypeAlias -> Flags.VISIBILITY.get(flags)
is ProtoBuf.EnumEntry -> return false // EnumEntry doesn't have a visibility flag
else -> error("Unknown message: $this")
}
return DescriptorVisibilities.isPrivate(ProtoEnumFlags.descriptorVisibility(visibility))
}
private fun MessageLite.name(nameResolver: NameResolver): String {
return when (this) {
@@ -69,6 +69,7 @@ private fun MessageLite.name(nameResolver: NameResolver): String {
is ProtoBuf.Function -> nameResolver.getString(name)
is ProtoBuf.Property -> nameResolver.getString(name)
is ProtoBuf.TypeAlias -> nameResolver.getString(name)
is ProtoBuf.EnumEntry -> nameResolver.getString(name)
else -> error("Unknown message: $this")
}
}
@@ -307,6 +308,25 @@ class DifferenceCalculatorForClass(
return Difference(isClassAffected, areSubclassesAffected, names, changedSupertypes)
}
companion object {
fun ClassProtoData.getNonPrivateMembers(): List<String> {
val membersResolvers: List<(ProtoBuf.Class) -> List<MessageLite>> = listOf(
// This list must match the logic in `DifferenceCalculatorForClass.difference`
// TODO: Consider adding COMPANION_OBJECT_NAME and NESTED_CLASS_NAME_LIST as they are also members of a class (see
// `DifferenceCalculatorForClass.difference`)
ProtoBuf.Class::getConstructorList,
ProtoBuf.Class::getFunctionList,
ProtoBuf.Class::getPropertyList,
ProtoBuf.Class::getTypeAliasList,
ProtoBuf.Class::getEnumEntryList
)
return membersResolvers.flatMap { membersResolver ->
membersResolver(proto).filterNot { it.isPrivate }.names(nameResolver)
}
}
}
}
class DifferenceCalculatorForPackageFacade(
@@ -362,6 +382,21 @@ class DifferenceCalculatorForPackageFacade(
return Difference(changedMembersNames = names)
}
companion object {
fun PackagePartProtoData.getNonPrivateMembers(): List<String> {
val membersResolvers: List<(ProtoBuf.Package) -> List<MessageLite>> = listOf(
// This list must match the logic in `DifferenceCalculatorForPackageFacade.difference`
ProtoBuf.Package::getFunctionList,
ProtoBuf.Package::getPropertyList,
ProtoBuf.Package::getTypeAliasList
)
return membersResolvers.flatMap { membersResolver ->
membersResolver(proto).filterNot { it.isPrivate }.names(nameResolver)
}
}
}
}
private val ProtoBuf.Class.isSealed: Boolean
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.inline
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags
@@ -24,57 +25,68 @@ 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 inlineFunctionsJvmNames(header: KotlinClassHeader): Set<String> {
val annotationData = header.data ?: return emptySet()
val strings = header.strings ?: return emptySet()
fun inlineFunctionsAndAccessors(header: KotlinClassHeader): List<JvmMemberSignature.Method> {
val data = header.data ?: return emptyList()
val strings = header.strings ?: return emptyList()
return when (header.kind) {
KotlinClassHeader.Kind.CLASS -> {
val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(annotationData, strings)
inlineFunctionsJvmNames(classProto.functionList, nameResolver, classProto.typeTable) +
inlineAccessorsJvmNames(classProto.propertyList, nameResolver)
val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(data, strings)
inlineFunctions(classProto.functionList, nameResolver, classProto.typeTable) +
inlineAccessors(classProto.propertyList, nameResolver)
}
KotlinClassHeader.Kind.FILE_FACADE,
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(annotationData, strings)
inlineFunctionsJvmNames(packageProto.functionList, nameResolver, packageProto.typeTable) +
inlineAccessorsJvmNames(packageProto.propertyList, nameResolver)
val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(data, strings)
inlineFunctions(packageProto.functionList, nameResolver, packageProto.typeTable) +
inlineAccessors(packageProto.propertyList, nameResolver)
}
else -> emptySet()
else -> emptyList()
}
}
private fun inlineFunctionsJvmNames(functions: List<ProtoBuf.Function>, nameResolver: NameResolver, protoTypeTable: ProtoBuf.TypeTable): Set<String> {
private fun inlineFunctions(
functions: List<ProtoBuf.Function>,
nameResolver: NameResolver,
protoTypeTable: ProtoBuf.TypeTable
): List<JvmMemberSignature.Method> {
val typeTable = TypeTable(protoTypeTable)
val inlineFunctions = functions.filter { Flags.IS_INLINE.get(it.flags) }
val jvmNames = inlineFunctions.mapNotNull {
JvmProtoBufUtil.getJvmMethodSignature(it, nameResolver, typeTable)?.asString()
return functions.filter { Flags.IS_INLINE.get(it.flags) }.mapNotNull {
JvmProtoBufUtil.getJvmMethodSignature(it, nameResolver, typeTable)
}
return jvmNames.toSet()
}
private fun inlineAccessorsJvmNames(properties: List<ProtoBuf.Property>, nameResolver: NameResolver): Set<String> {
val propertiesWithInlineAccessors = properties.filter { proto ->
proto.hasGetterFlags() && Flags.IS_INLINE_ACCESSOR.get(proto.getterFlags) ||
proto.hasSetterFlags() && Flags.IS_INLINE_ACCESSOR.get(proto.setterFlags)
}
val inlineAccessors = arrayListOf<JvmMethodSignature>()
propertiesWithInlineAccessors.forEach { proto ->
val signature = proto.getExtensionOrNull(JvmProtoBuf.propertySignature)
if (signature != null) {
if (proto.hasGetterFlags() && Flags.IS_INLINE_ACCESSOR.get(proto.getterFlags)) {
inlineAccessors.add(signature.getter)
}
fun inlineAccessors(
properties: List<ProtoBuf.Property>,
nameResolver: NameResolver,
excludePrivateAccessors: Boolean = false
): List<JvmMemberSignature.Method> {
val inlineAccessors = mutableListOf<JvmMethodSignature>()
if (proto.hasSetterFlags() && Flags.IS_INLINE_ACCESSOR.get(proto.setterFlags)) {
inlineAccessors.add(signature.setter)
fun isInline(flags: Int) = Flags.IS_INLINE_ACCESSOR.get(flags)
fun isPrivate(flags: Int) = DescriptorVisibilities.isPrivate(ProtoEnumFlags.descriptorVisibility(Flags.VISIBILITY.get(flags)))
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.hasSetterFlags() && isInline(property.setterFlags)) {
if (!(excludePrivateAccessors && isPrivate(property.setterFlags))) {
inlineAccessors.add(propertySignature.setter)
}
}
}
return inlineAccessors.map {
nameResolver.getString(it.name) + nameResolver.getString(it.desc)
}.toSet()
JvmMemberSignature.Method(name = nameResolver.getString(it.name), desc = nameResolver.getString(it.desc))
}
}
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.incremental
import org.jetbrains.kotlin.incremental.ChangesCollector.Companion.getNonPrivateMemberNames
import org.jetbrains.kotlin.metadata.ProtoBuf.Visibility.PRIVATE
import org.jetbrains.kotlin.metadata.deserialization.Flags
import org.jetbrains.kotlin.name.FqName
@@ -98,7 +97,8 @@ class AbiSnapshotDiffService() {
when (protoData) {
is ClassProtoData -> {
fqNames.add(fqName)
symbols.addAll(protoData.getNonPrivateMemberNames().map { LookupSymbol(it, fqName.asString()) })
symbols.addAll(
protoData.getNonPrivateMemberNames(includeInlineAccessors = true).map { LookupSymbol(it, fqName.asString()) })
}
is PackagePartProtoData -> {
symbols.addAll(
@@ -12,7 +12,8 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.AnnotationVisitor
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassReader.*
import org.jetbrains.org.objectweb.asm.ClassReader.SKIP_CODE
import org.jetbrains.org.objectweb.asm.ClassReader.SKIP_DEBUG
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
@@ -47,7 +48,8 @@ class BasicClassInfo(
val innerClassesClassVisitor = InnerClassesClassVisitor(kotlinClassHeaderClassVisitor)
val basicClassInfoVisitor = BasicClassInfoClassVisitor(innerClassesClassVisitor)
ClassReader(classContents).accept(basicClassInfoVisitor, SKIP_CODE or SKIP_DEBUG or SKIP_FRAMES)
// parsingOptions = (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info are not important
ClassReader(classContents).accept(basicClassInfoVisitor, SKIP_CODE or SKIP_DEBUG)
val className = basicClassInfoVisitor.getClassName()
val innerClassesInfo = innerClassesClassVisitor.getInnerClassesInfo()
@@ -186,7 +186,7 @@ internal object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo>
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.inlineFunctionsMap)
LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).save(output, info.inlineFunctionsAndAccessorsMap)
}
override fun read(input: DataInput): KotlinClassInfo {
@@ -198,7 +198,7 @@ internal object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo>
classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
multifileClassName = NullableValueExternalizer(StringExternalizer).read(input),
constantsMap = LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).read(input),
inlineFunctionsMap = LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).read(input)
inlineFunctionsAndAccessorsMap = LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).read(input)
)
}
}
@@ -9,10 +9,9 @@ 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.ChangesCollector.Companion.getNonPrivateMemberNames
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.*
@@ -97,7 +96,7 @@ object ClassSnapshotter {
)
FILE_FACADE, MULTIFILE_CLASS_PART -> PackageFacadeKotlinClassSnapshot(
classId, classAbiHash, classMemberLevelSnapshot,
packageMemberNames = (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMemberNames()
packageMemberNames = kotlinClassInfo.protoData.getNonPrivateMemberNames(includeInlineAccessors = true).toSet()
)
MULTIFILE_CLASS -> MultifileClassKotlinClassSnapshot(
classId, classAbiHash, classMemberLevelSnapshot,
@@ -183,7 +183,10 @@ private object ConstantsInCompanionObjectsImpact : Impact {
} else null
}
}.toMap()
val classToCompanionObject: Map<ClassId, ClassId> = companionObjectToConstants.keys.associateBy { it.parentClassId!! }
val classToCompanionObject: Map<ClassId, ClassId> = companionObjectToConstants.keys.associateBy { companionObject ->
// companionObject.parentClassId should be present in `allClasses` as this is a companion object
companionObject.parentClassId!!
}
return object : ImpactedSymbolsResolver {
override fun getImpactedClasses(classId: ClassId): Set<ClassId> {
@@ -211,6 +214,7 @@ private object ConstantsInCompanionObjectsImpact : Impact {
return object : ImpactingClassesResolver {
override fun getImpactingClasses(classId: ClassId): Set<ClassId> {
return if (classId in companionObjects) {
// classId.parentClassId should be present in `allClasses` as this is a companion object
setOf(classId.parentClassId!!)
} else emptySet()
}
@@ -28,11 +28,11 @@ object JavaClassSnapshotter {
// First, collect all info.
// Note the parsing options passed to ClassReader:
// - SKIP_CODE and SKIP_FRAMES are set as method bodies will not be part of the ABI of the class.
// - SKIP_DEBUG is not set as it would skip method parameters, which may be used by annotation processors like Room.
// - EXPAND_FRAMES is not needed (and not relevant when SKIP_CODE is set).
// - 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 or ClassReader.SKIP_FRAMES)
classReader.accept(abiClass, ClassReader.SKIP_CODE)
// Then, remove non-ABI info, which includes:
// - Method bodies: Should have already been removed (see SKIP_CODE above)
@@ -202,6 +202,7 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
LookupSymbol(name = "inlineFunctionChangedSignature", scope = "com.example.SomeClass"),
LookupSymbol(name = "inlineFunctionChangedImplementation", scope = "com.example.SomeClass"),
LookupSymbol(name = "inlineFunctionChangedLineNumber", scope = "com.example.SomeClass"),
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass"),
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass.CompanionObject"),
@@ -213,6 +214,30 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
).assertEquals(changes)
}
@Test
fun testPropertyAccessors() {
val changes = computeClasspathChanges(File(testDataDir, "KotlinOnly/testPropertyAccessors/src"), tmpDir)
Changes(
lookupSymbols = setOf(
LookupSymbol(name = "property_ChangedType", scope = "com.example.SomeClass"),
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 = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass")
),
fqNames = setOf(
"com.example",
"com.example.SomeClass"
)
).assertEquals(changes)
}
/** Tests [SupertypesInheritorsImpact]. */
@Test
override fun testImpactComputation_SupertypesInheritors() {
@@ -14,6 +14,8 @@ class SomeClass {
inline fun inlineFunctionChangedSignature(): Long = 0
inline fun inlineFunctionChangedImplementation(): Int = 1000
inline fun inlineFunctionChangedLineNumber(): Int = 0
inline fun inlineFunctionChangedUnchanged(): Int = 0
private inline fun privateInlineFunctionChangedSignature(): Long = 0
}
@@ -14,6 +14,8 @@ class SomeClass {
inline fun inlineFunctionChangedSignature(): Int = 0
inline fun inlineFunctionChangedImplementation(): Int = 0
inline fun inlineFunctionChangedLineNumber(): Int = 0
inline fun inlineFunctionChangedUnchanged(): Int = 0
private inline fun privateInlineFunctionChangedSignature(): Int = 0
}
@@ -0,0 +1,79 @@
package com.example
@Suppress("RedundantGetter", "RedundantSetter")
class SomeClass {
var property_ChangedType: Long = 0
get() = field
set(value) {
field = value
}
var property_ChangedGetterImpl: Int = 0
get() {
println("Getter implementation has changed!")
return field
}
set(value) {
field = value
}
var property_ChangedSetterImpl: Int = 0
get() = field
set(value) {
println("Setter implementation has changed!")
field = value
}
var property_Unchanged: Int = 0
get() = field
set(value) {
field = value
}
private var privateProperty_ChangedType: Long = 0
get() = field
set(value) {
field = value
}
}
var inlineProperty_ChangedType_BackingField: Long = 0
var inlineProperty_ChangedGetterImpl_BackingField: Int = 0
var inlineProperty_ChangedSetterImpl_BackingField: Int = 0
var inlineProperty_Unchanged_BackingField: Int = 0
private var privateInlineProperty_ChangedType_BackingField: Long = 0
inline var inlineProperty_ChangedType: Long
get() = inlineProperty_ChangedType_BackingField
set(value) {
inlineProperty_ChangedType_BackingField = value
}
inline var inlineProperty_ChangedGetterImpl: Int
get() {
println("Getter implementation has changed!")
return inlineProperty_ChangedGetterImpl_BackingField
}
set(value) {
inlineProperty_ChangedGetterImpl_BackingField = value
}
inline var inlineProperty_ChangedSetterImpl: Int
get() = inlineProperty_ChangedSetterImpl_BackingField
set(value) {
println("Setter implementation has changed!")
inlineProperty_ChangedSetterImpl_BackingField = value
}
inline var inlineProperty_Unchanged: Int
get() = inlineProperty_Unchanged_BackingField
set(value) {
inlineProperty_Unchanged_BackingField = value
}
private inline var privateInlineProperty_ChangedType: Long
get() = privateInlineProperty_ChangedType_BackingField
set(value) {
privateInlineProperty_ChangedType_BackingField = value
}
@@ -0,0 +1,79 @@
package com.example
@Suppress("RedundantGetter", "RedundantSetter")
class SomeClass {
var property_ChangedType: Int = 0
get() = field
set(value) {
field = value
}
var property_ChangedGetterImpl: Int = 0
get() {
println("Getter implementation")
return field
}
set(value) {
field = value
}
var property_ChangedSetterImpl: Int = 0
get() = field
set(value) {
println("Setter implementation")
field = value
}
var property_Unchanged: Int = 0
get() = field
set(value) {
field = value
}
private var privateProperty_ChangedType: Int = 0
get() = field
set(value) {
field = value
}
}
var inlineProperty_ChangedType_BackingField: Int = 0
var inlineProperty_ChangedGetterImpl_BackingField: Int = 0
var inlineProperty_ChangedSetterImpl_BackingField: Int = 0
var inlineProperty_Unchanged_BackingField: Int = 0
private var privateInlineProperty_ChangedType_BackingField: Int = 0
inline var inlineProperty_ChangedType: Int
get() = inlineProperty_ChangedType_BackingField
set(value) {
inlineProperty_ChangedType_BackingField = value
}
inline var inlineProperty_ChangedGetterImpl: Int
get() {
println("Getter implementation")
return inlineProperty_ChangedGetterImpl_BackingField
}
set(value) {
inlineProperty_ChangedGetterImpl_BackingField = value
}
inline var inlineProperty_ChangedSetterImpl: Int
get() = inlineProperty_ChangedSetterImpl_BackingField
set(value) {
println("Setter implementation")
inlineProperty_ChangedSetterImpl_BackingField = value
}
inline var inlineProperty_Unchanged: Int
get() = inlineProperty_Unchanged_BackingField
set(value) {
inlineProperty_Unchanged_BackingField = value
}
private inline var privateInlineProperty_ChangedType: Int
get() = privateInlineProperty_ChangedType_BackingField
set(value) {
privateInlineProperty_ChangedType_BackingField = value
}
@@ -44,7 +44,7 @@
"publicFunction"
],
"constantsMap": {},
"inlineFunctionsMap": {},
"inlineFunctionsAndAccessorsMap": {},
"className$delegate": {
"initializer": {},
"_value": {}
@@ -18,15 +18,15 @@ package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.backend.common.output.OutputFile
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
import org.jetbrains.kotlin.inline.inlineFunctionsAndAccessors
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.kotlin.FileBasedKotlinClass
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.*
object InlineTestUtil {
fun checkNoCallsToInline(
@@ -57,13 +57,13 @@ object InlineTestUtil {
val binaryClasses = hashMapOf<String, KotlinJvmBinaryClass>()
for (file in files) {
val binaryClass = loadBinaryClass(file)
val inlineFunctions = inlineFunctionsJvmNames(binaryClass.classHeader)
val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader)
val classVisitor = object : ClassVisitorWithName() {
override fun visitMethod(
access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?
): MethodVisitor? {
if (name + desc in inlineFunctions) {
if (JvmMemberSignature.Method(name, desc) in inlineFunctionsAndAccessors) {
inlineMethods.add(MethodInfo(className, name, desc))
}
return null
@@ -81,14 +81,14 @@ object InlineTestUtil {
var doLambdaInliningCheck = true
for (file in files) {
val binaryClass = loadBinaryClass(file)
val inlineFunctions = inlineFunctionsJvmNames(binaryClass.classHeader)
val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader)
//if inline function creates anonymous object then do not try to check that all lambdas are inlined
val classVisitor = object : ClassVisitorWithName() {
override fun visitMethod(
access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?
): MethodVisitor? {
if (name + desc in inlineFunctions) {
if (JvmMemberSignature.Method(name, desc) in inlineFunctionsAndAccessors) {
return object : MethodNodeWithAnonymousObjectCheck(inlineInfo, access, name, desc, signature, exceptions) {
override fun onAnonymousConstructorCallOrSingletonAccess(owner: String) {
doLambdaInliningCheck = false