Extract KotlinClassInfo to a separate class
to reduce the size of IncrementalJvmCache and prepare for the next
change.
^KT-54144 In progress
Handle changes to inline functions/property accessors with `@JvmName`s
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.)
Therefore, we will choose option 1 as it is cleaner and safer.
^KT-54144 In progress
Ignore inline functions that are not found in the bytecode
^KT-54144 In progress
Add unit test for handling `@JvmName`s
Test: KotlinOnlyClasspathChangesComputerTest
#testFunctionsAndPropertyAccessorsWithJvmNames
^KT-54144 Fixed
Small cleanup in IncrementalCompilerRunner
- Add comment for closing caches
- Rename providedChangedFiles to changedFiles
- Tiny clean up in `performWorkBeforeCompilation`
- Count directories to delete in debug logs
^KT-53015 In progress
Small cleanup in IncrementalCompilerRunner
- Add comment for closing caches
- Rename providedChangedFiles to changedFiles
- Tiny clean up in `performWorkBeforeCompilation`
- Count directories to delete in debug logs
^KT-53015 In progress
This commit is contained in:
committed by
nataliya.valtman
parent
fce8b877c8
commit
ec3da62672
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.incremental
|
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.ProtoBuf
|
||||||
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
|
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
@@ -166,7 +168,7 @@ class ChangesCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun PackagePartProtoData.collectAllFromPackage(isRemoved: Boolean) {
|
private fun PackagePartProtoData.collectAllFromPackage(isRemoved: Boolean) {
|
||||||
val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true)
|
val memberNames = getNonPrivateMembers()
|
||||||
if (isRemoved) {
|
if (isRemoved) {
|
||||||
collectRemovedMembers(packageFqName, memberNames)
|
collectRemovedMembers(packageFqName, memberNames)
|
||||||
} else {
|
} else {
|
||||||
@@ -178,14 +180,14 @@ class ChangesCollector {
|
|||||||
val classFqName = nameResolver.getClassId(proto.fqName).asSingleFqName()
|
val classFqName = nameResolver.getClassId(proto.fqName).asSingleFqName()
|
||||||
|
|
||||||
if (proto.isCompanionObject) {
|
if (proto.isCompanionObject) {
|
||||||
val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true)
|
val memberNames = getNonPrivateMembers()
|
||||||
|
|
||||||
val collectMember = if (isRemoved) this@ChangesCollector::collectRemovedMember else this@ChangesCollector::collectChangedMember
|
val collectMember = if (isRemoved) this@ChangesCollector::collectRemovedMember else this@ChangesCollector::collectChangedMember
|
||||||
collectMember(classFqName.parent(), classFqName.shortName().asString())
|
collectMember(classFqName.parent(), classFqName.shortName().asString())
|
||||||
memberNames.forEach { collectMember(classFqName, it) }
|
memberNames.forEach { collectMember(classFqName, it) }
|
||||||
} else {
|
} else {
|
||||||
if (!isRemoved && collectAllMembersForNewClass) {
|
if (!isRemoved && collectAllMembersForNewClass) {
|
||||||
val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true)
|
val memberNames = getNonPrivateMembers()
|
||||||
memberNames.forEach { this@ChangesCollector.collectChangedMember(classFqName, it) }
|
memberNames.forEach { this@ChangesCollector.collectChangedMember(classFqName, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,27 +23,20 @@ import com.intellij.util.io.EnumeratorStringDescriptor
|
|||||||
import gnu.trove.THashSet
|
import gnu.trove.THashSet
|
||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.kotlin.build.GeneratedJvmClass
|
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.incremental.storage.*
|
||||||
import org.jetbrains.kotlin.inline.inlineAccessors
|
import org.jetbrains.kotlin.inline.InlineFunction
|
||||||
import org.jetbrains.kotlin.inline.inlineFunctionsAndAccessors
|
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.header.KotlinClassHeader
|
||||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
||||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
|
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
|
||||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
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.JvmProtoBufUtil
|
||||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
|
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
|
||||||
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
|
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
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.io.File
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
@@ -439,7 +432,7 @@ open class IncrementalJvmCache(
|
|||||||
|
|
||||||
// todo: reuse code with InlineFunctionsMap?
|
// todo: reuse code with InlineFunctionsMap?
|
||||||
private inner class ConstantsMap(storageFile: File) :
|
private inner class ConstantsMap(storageFile: File) :
|
||||||
BasicStringMap<LinkedHashMap<String, Any>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer)) {
|
BasicStringMap<Map<String, Any>>(storageFile, MapExternalizer(StringExternalizer, ConstantValueExternalizer)) {
|
||||||
|
|
||||||
operator fun contains(className: JvmClassName): Boolean =
|
operator fun contains(className: JvmClassName): Boolean =
|
||||||
className.internalName in storage
|
className.internalName in storage
|
||||||
@@ -487,7 +480,7 @@ open class IncrementalJvmCache(
|
|||||||
storage.remove(className.internalName)
|
storage.remove(className.internalName)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dumpValue(value: LinkedHashMap<String, Any>): String =
|
override fun dumpValue(value: Map<String, Any>): String =
|
||||||
value.dumpMap(Any::toString)
|
value.dumpMap(Any::toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,7 +560,10 @@ open class IncrementalJvmCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private inner class InlineFunctionsMap(storageFile: File) :
|
private inner class InlineFunctionsMap(storageFile: File) :
|
||||||
BasicStringMap<LinkedHashMap<String, Long>>(storageFile, LinkedHashMapExternalizer(StringExternalizer, LongExternalizer)) {
|
BasicStringMap<Map<InlineFunctionOrAccessor, Long>>(
|
||||||
|
storageFile,
|
||||||
|
MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer)
|
||||||
|
) {
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
|
fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) {
|
||||||
@@ -581,34 +577,36 @@ open class IncrementalJvmCache(
|
|||||||
storage.remove(key)
|
storage.remove(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (methodSignature in oldMap.keys + newMap.keys) {
|
// Note: If we detect a change in an inline function `foo` with @JvmName `fooJvmName`, we have two options:
|
||||||
val inlineFunctionOrAccessorName = functionNameBySignature(methodSignature)
|
// 1. Report that function `foo` has changed
|
||||||
// Note: If we detect a change in an inline property accessor (e.g., `getFoo`), we have two options:
|
// 2. Report that method `fooJvmName` has changed
|
||||||
// 1. Report that property `foo` has changed (and ignore `getFoo`)
|
//
|
||||||
// 2. Report that property accessor `getFoo` has changed (and ignore `foo`)
|
// Similarly, if we detect a change in an inline property accessor with JvmName `getFoo` of property `foo`, we have two options:
|
||||||
// Both options are possible (the compiler generates `LookupSymbol`s for both `foo` and `getFoo` when `getFoo` is inlined).
|
// 1. Report that property `foo` has changed
|
||||||
// Currently, we choose option 2 as it is simpler (i.e., even though inline property accessors are not immediate members of
|
// 2. Report that property accessor `getFoo` has changed
|
||||||
// a class/package, we treat them like immediate members).
|
//
|
||||||
changesCollector.collectMemberIfValueWasChanged(
|
// The compiler is guaranteed to generate `LookupSymbol`s corresponding to option 1 when referencing inline functions/property
|
||||||
kotlinClassInfo.scopeFqName(),
|
// accessors, but it is not guaranteed to generate `LookupSymbol`s corresponding to option 2. (Currently the compiler seems to
|
||||||
inlineFunctionOrAccessorName,
|
// support option 2 for *inline* functions/property accessors, but that may change.)
|
||||||
oldMap[methodSignature],
|
//
|
||||||
newMap[methodSignature]
|
// 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
|
@Synchronized
|
||||||
fun remove(className: JvmClassName) {
|
fun remove(className: JvmClassName) {
|
||||||
storage.remove(className.internalName)
|
storage.remove(className.internalName)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dumpValue(value: LinkedHashMap<String, Long>): String =
|
override fun dumpValue(value: Map<InlineFunctionOrAccessor, Long>): String =
|
||||||
value.dumpMap { java.lang.Long.toHexString(it) }
|
value.mapKeys { it.key.jvmMethodSignature.asString() }.dumpMap { java.lang.Long.toHexString(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KotlinClassInfo.scopeFqName() = when (classKind) {
|
private fun KotlinClassInfo.scopeFqName() = when (classKind) {
|
||||||
@@ -669,182 +667,3 @@ fun <K : Comparable<K>, V> Map<K, V>.dumpMap(dumpValue: (V) -> String): String =
|
|||||||
@TestOnly
|
@TestOnly
|
||||||
fun <T : Comparable<T>> Collection<T>.dumpCollection(): String =
|
fun <T : Comparable<T>> Collection<T>.dumpCollection(): String =
|
||||||
"[${sorted().joinToString(", ", transform = Any::toString)}]"
|
"[${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]).
|
|
||||||
*
|
|
||||||
* 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<String>, // Can be empty
|
|
||||||
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 inlineFunctionsAndAccessorsMap: LinkedHashMap<String, Long>
|
|
||||||
) {
|
|
||||||
|
|
||||||
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<String>? 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<JvmMemberSignature.Field, Any>, Map<JvmMemberSignature.Method, Long>> {
|
|
||||||
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<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[JvmMemberSignature.Field(name, desc)] = value
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getResult() = result
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 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>?) {
|
|
||||||
super.visit(version, access, name, signature, superName, interfaces)
|
|
||||||
classVersion = version
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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<String>, // Can be empty
|
||||||
|
val classHeaderStrings: Array<String>, // Can be empty
|
||||||
|
val multifileClassName: String?, // Not null iff classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART
|
||||||
|
val constantsMap: Map<String, Any>,
|
||||||
|
val inlineFunctionsAndAccessorsMap: Map<InlineFunctionOrAccessor, Long>
|
||||||
|
) {
|
||||||
|
|
||||||
|
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<String>? 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<JvmMemberSignature.Field, Any>, Map<InlineFunctionOrAccessor, Long>> {
|
||||||
|
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<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[JvmMemberSignature.Field(name, desc)] = value
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getResult() = result
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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>?) {
|
||||||
|
super.visit(version, access, name, signature, superName, interfaces)
|
||||||
|
classVersion = version
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): 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<Any> 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)
|
||||||
|
)
|
||||||
@@ -22,6 +22,10 @@ import com.intellij.util.io.DataExternalizer
|
|||||||
import com.intellij.util.io.EnumeratorStringDescriptor
|
import com.intellij.util.io.EnumeratorStringDescriptor
|
||||||
import com.intellij.util.io.IOUtil
|
import com.intellij.util.io.IOUtil
|
||||||
import com.intellij.util.io.KeyDescriptor
|
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.ClassId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
@@ -165,53 +169,6 @@ object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** [DataExternalizer] for a Kotlin constant. */
|
|
||||||
object ConstantExternalizer : DataExternalizer<Any> {
|
|
||||||
|
|
||||||
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 <T> DataExternalizer<T>.saveToFile(file: File, value: T) {
|
fun <T> DataExternalizer<T>.saveToFile(file: File, value: T) {
|
||||||
return DataOutputStream(FileOutputStream(file).buffered()).use {
|
return DataOutputStream(FileOutputStream(file).buffered()).use {
|
||||||
save(it, value)
|
save(it, value)
|
||||||
@@ -248,6 +205,16 @@ object LongExternalizer : DataExternalizer<Long> {
|
|||||||
override fun read(input: DataInput): Long = input.readLong()
|
override fun read(input: DataInput): Long = input.readLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object FloatExternalizer : DataExternalizer<Float> {
|
||||||
|
override fun save(output: DataOutput, value: Float) = output.writeFloat(value)
|
||||||
|
override fun read(input: DataInput): Float = input.readFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
object DoubleExternalizer : DataExternalizer<Double> {
|
||||||
|
override fun save(output: DataOutput, value: Double) = output.writeDouble(value)
|
||||||
|
override fun read(input: DataInput): Double = input.readDouble()
|
||||||
|
}
|
||||||
|
|
||||||
object StringExternalizer : DataExternalizer<String> {
|
object StringExternalizer : DataExternalizer<String> {
|
||||||
override fun save(output: DataOutput, value: String) = IOUtil.writeString(value, output)
|
override fun save(output: DataOutput, value: String) = IOUtil.writeString(value, output)
|
||||||
override fun read(input: DataInput): String = IOUtil.readString(input)
|
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<T>(
|
||||||
|
val types: List<Class<out T>>,
|
||||||
|
val typesExternalizers: List<DataExternalizer<out T>>
|
||||||
|
) : DataExternalizer<T> {
|
||||||
|
|
||||||
|
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<T>).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].
|
* [DataExternalizer] for a [Collection].
|
||||||
*
|
*
|
||||||
@@ -401,3 +395,53 @@ class LinkedHashMapExternalizer<K, V>(
|
|||||||
keyExternalizer: DataExternalizer<K>,
|
keyExternalizer: DataExternalizer<K>,
|
||||||
valueExternalizer: DataExternalizer<V>
|
valueExternalizer: DataExternalizer<V>
|
||||||
) : MapExternalizer<K, V, LinkedHashMap<K, V>>(keyExternalizer, valueExternalizer, { size -> LinkedHashMap(size) })
|
) : MapExternalizer<K, V, LinkedHashMap<K, V>>(keyExternalizer, valueExternalizer, { size -> LinkedHashMap(size) })
|
||||||
|
|
||||||
|
object JvmMethodSignatureExternalizer : DataExternalizer<JvmMemberSignature.Method> {
|
||||||
|
|
||||||
|
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<InlineFunctionOrAccessor> by DelegateDataExternalizer(
|
||||||
|
types = listOf(InlineFunction::class.java, InlinePropertyAccessor::class.java),
|
||||||
|
typesExternalizers = listOf(InlineFunctionExternalizer, InlinePropertyAccessorExternalizer)
|
||||||
|
)
|
||||||
|
|
||||||
|
private object InlineFunctionExternalizer : DataExternalizer<InlineFunction> {
|
||||||
|
|
||||||
|
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<InlinePropertyAccessor> {
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,27 +24,44 @@ import org.jetbrains.kotlin.metadata.deserialization.NameResolver
|
|||||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||||
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
|
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
|
||||||
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf
|
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.JvmMemberSignature
|
||||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.ProtoEnumFlags
|
import org.jetbrains.kotlin.serialization.deserialization.ProtoEnumFlags
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.descriptorVisibility
|
import org.jetbrains.kotlin.serialization.deserialization.descriptorVisibility
|
||||||
|
|
||||||
fun inlineFunctionsAndAccessors(header: KotlinClassHeader): List<JvmMemberSignature.Method> {
|
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<InlineFunctionOrAccessor> {
|
||||||
val data = header.data ?: return emptyList()
|
val data = header.data ?: return emptyList()
|
||||||
val strings = header.strings ?: return emptyList()
|
val strings = header.strings ?: return emptyList()
|
||||||
|
|
||||||
return when (header.kind) {
|
return when (header.kind) {
|
||||||
KotlinClassHeader.Kind.CLASS -> {
|
KotlinClassHeader.Kind.CLASS -> {
|
||||||
val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(data, strings)
|
val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(data, strings)
|
||||||
inlineFunctions(classProto.functionList, nameResolver, classProto.typeTable) +
|
inlineFunctions(classProto.functionList, nameResolver, classProto.typeTable, excludePrivateMembers) +
|
||||||
inlineAccessors(classProto.propertyList, nameResolver)
|
inlinePropertyAccessors(classProto.propertyList, nameResolver, excludePrivateMembers)
|
||||||
}
|
}
|
||||||
KotlinClassHeader.Kind.FILE_FACADE,
|
KotlinClassHeader.Kind.FILE_FACADE,
|
||||||
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
|
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
|
||||||
val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(data, strings)
|
val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(data, strings)
|
||||||
inlineFunctions(packageProto.functionList, nameResolver, packageProto.typeTable) +
|
inlineFunctions(packageProto.functionList, nameResolver, packageProto.typeTable, excludePrivateMembers) +
|
||||||
inlineAccessors(packageProto.propertyList, nameResolver)
|
inlinePropertyAccessors(packageProto.propertyList, nameResolver, excludePrivateMembers)
|
||||||
}
|
}
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}
|
}
|
||||||
@@ -53,40 +70,51 @@ fun inlineFunctionsAndAccessors(header: KotlinClassHeader): List<JvmMemberSignat
|
|||||||
private fun inlineFunctions(
|
private fun inlineFunctions(
|
||||||
functions: List<ProtoBuf.Function>,
|
functions: List<ProtoBuf.Function>,
|
||||||
nameResolver: NameResolver,
|
nameResolver: NameResolver,
|
||||||
protoTypeTable: ProtoBuf.TypeTable
|
protoTypeTable: ProtoBuf.TypeTable,
|
||||||
): List<JvmMemberSignature.Method> {
|
excludePrivateFunctions: Boolean = false
|
||||||
|
): List<InlineFunction> {
|
||||||
val typeTable = TypeTable(protoTypeTable)
|
val typeTable = TypeTable(protoTypeTable)
|
||||||
return functions.filter { Flags.IS_INLINE.get(it.flags) }.mapNotNull {
|
return functions
|
||||||
JvmProtoBufUtil.getJvmMethodSignature(it, nameResolver, typeTable)
|
.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<ProtoBuf.Property>,
|
properties: List<ProtoBuf.Property>,
|
||||||
nameResolver: NameResolver,
|
nameResolver: NameResolver,
|
||||||
excludePrivateAccessors: Boolean = false
|
excludePrivateAccessors: Boolean = false
|
||||||
): List<JvmMemberSignature.Method> {
|
): List<InlinePropertyAccessor> {
|
||||||
val inlineAccessors = mutableListOf<JvmMethodSignature>()
|
val inlineAccessors = mutableListOf<InlinePropertyAccessor>()
|
||||||
|
|
||||||
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 ->
|
properties.forEach { property ->
|
||||||
val propertySignature = property.getExtensionOrNull(JvmProtoBuf.propertySignature) ?: return@forEach
|
val propertySignature = property.getExtensionOrNull(JvmProtoBuf.propertySignature) ?: return@forEach
|
||||||
|
if (property.hasGetterFlags() && Flags.IS_INLINE_ACCESSOR.get(property.getterFlags)
|
||||||
if (property.hasGetterFlags() && isInline(property.getterFlags)) {
|
&& (!excludePrivateAccessors || !isPrivate(property.getterFlags))
|
||||||
if (!(excludePrivateAccessors && isPrivate(property.getterFlags))) {
|
) {
|
||||||
inlineAccessors.add(propertySignature.getter)
|
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 (property.hasSetterFlags() && Flags.IS_INLINE_ACCESSOR.get(property.setterFlags)
|
||||||
if (!(excludePrivateAccessors && isPrivate(property.setterFlags))) {
|
&& (!excludePrivateAccessors || !isPrivate(property.setterFlags))
|
||||||
inlineAccessors.add(propertySignature.setter)
|
) {
|
||||||
}
|
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
|
||||||
return inlineAccessors.map {
|
|
||||||
JvmMemberSignature.Method(name = nameResolver.getString(it.name), desc = nameResolver.getString(it.desc))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isPrivate(flags: Int) = DescriptorVisibilities.isPrivate(ProtoEnumFlags.descriptorVisibility(Flags.VISIBILITY.get(flags)))
|
||||||
|
|||||||
+2
-2
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.incremental
|
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.ProtoBuf.Visibility.PRIVATE
|
||||||
import org.jetbrains.kotlin.metadata.deserialization.Flags
|
import org.jetbrains.kotlin.metadata.deserialization.Flags
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
@@ -97,8 +98,7 @@ class AbiSnapshotDiffService() {
|
|||||||
when (protoData) {
|
when (protoData) {
|
||||||
is ClassProtoData -> {
|
is ClassProtoData -> {
|
||||||
fqNames.add(fqName)
|
fqNames.add(fqName)
|
||||||
symbols.addAll(
|
symbols.addAll(protoData.getNonPrivateMembers().map { LookupSymbol(it, fqName.asString()) })
|
||||||
protoData.getNonPrivateMemberNames(includeInlineAccessors = true).map { LookupSymbol(it, fqName.asString()) })
|
|
||||||
}
|
}
|
||||||
is PackagePartProtoData -> {
|
is PackagePartProtoData -> {
|
||||||
symbols.addAll(
|
symbols.addAll(
|
||||||
|
|||||||
+23
-19
@@ -18,12 +18,15 @@ package org.jetbrains.kotlin.incremental
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
|
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
|
||||||
import org.jetbrains.kotlin.build.GeneratedFile
|
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.BuildAttribute.*
|
import org.jetbrains.kotlin.build.report.metrics.BuildAttribute.*
|
||||||
import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
|
import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
|
||||||
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
||||||
import org.jetbrains.kotlin.build.report.metrics.measure
|
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.*
|
||||||
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
@@ -80,12 +83,12 @@ abstract class IncrementalCompilerRunner<
|
|||||||
allSourceFiles: List<File>,
|
allSourceFiles: List<File>,
|
||||||
args: Args,
|
args: Args,
|
||||||
messageCollector: MessageCollector,
|
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.
|
// otherwise we track source files changes ourselves.
|
||||||
providedChangedFiles: ChangedFiles?,
|
changedFiles: ChangedFiles?,
|
||||||
projectDir: File? = null
|
projectDir: File? = null
|
||||||
): ExitCode = reporter.measure(BuildTime.INCREMENTAL_COMPILATION_DAEMON) {
|
): 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 -> {
|
is ICResult.Completed -> {
|
||||||
reporter.debug { "Incremental compilation completed" }
|
reporter.debug { "Incremental compilation completed" }
|
||||||
result.exitCode
|
result.exitCode
|
||||||
@@ -95,7 +98,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
reporter.addAttribute(result.reason)
|
reporter.addAttribute(result.reason)
|
||||||
|
|
||||||
compileNonIncrementally(
|
compileNonIncrementally(
|
||||||
result.reason, allSourceFiles, args, projectDir, trackChangedFiles = providedChangedFiles == null, messageCollector
|
result.reason, allSourceFiles, args, projectDir, trackChangedFiles = changedFiles == null, messageCollector
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ICResult.Failed -> {
|
is ICResult.Failed -> {
|
||||||
@@ -114,7 +117,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
reporter.addAttribute(result.reason)
|
reporter.addAttribute(result.reason)
|
||||||
|
|
||||||
compileNonIncrementally(
|
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(
|
private fun tryCompileIncrementally(
|
||||||
allSourceFiles: List<File>,
|
allSourceFiles: List<File>,
|
||||||
providedChangedFiles: ChangedFiles?,
|
changedFiles: ChangedFiles?,
|
||||||
args: Args,
|
args: Args,
|
||||||
projectDir: File?,
|
projectDir: File?,
|
||||||
messageCollector: MessageCollector
|
messageCollector: MessageCollector
|
||||||
): ICResult {
|
): ICResult {
|
||||||
if (providedChangedFiles is ChangedFiles.Unknown) {
|
if (changedFiles is ChangedFiles.Unknown) {
|
||||||
return ICResult.RequiresRebuild(UNKNOWN_CHANGES_IN_GRADLE_INPUTS)
|
return ICResult.RequiresRebuild(UNKNOWN_CHANGES_IN_GRADLE_INPUTS)
|
||||||
}
|
}
|
||||||
providedChangedFiles as ChangedFiles.Known?
|
changedFiles as ChangedFiles.Known?
|
||||||
|
|
||||||
val caches = createCacheManager(args, projectDir)
|
val caches = createCacheManager(args, projectDir)
|
||||||
val exitCode: ExitCode
|
val exitCode: ExitCode
|
||||||
try {
|
try {
|
||||||
// Step 1: Get changed files
|
// Step 1: Get changed files
|
||||||
val changedFiles: ChangedFiles.Known = try {
|
val knownChangedFiles: ChangedFiles.Known = try {
|
||||||
getChangedFiles(providedChangedFiles, allSourceFiles, caches)
|
getChangedFiles(changedFiles, allSourceFiles, caches)
|
||||||
} catch (e: Throwable) {
|
} 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)
|
return ICResult.Failed(IC_FAILED_TO_GET_CHANGED_FILES, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +170,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
// Step 2: Compute files to recompile
|
// Step 2: Compute files to recompile
|
||||||
val compilationMode = try {
|
val compilationMode = try {
|
||||||
reporter.measure(BuildTime.IC_CALCULATE_INITIAL_DIRTY_SET) {
|
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) {
|
} catch (e: Throwable) {
|
||||||
return ICResult.Failed(IC_FAILED_TO_COMPUTE_FILES_TO_RECOMPILE, e)
|
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" }
|
check(it.containsAll(mainOutputDirs)) { "outputDirs is missing classesDir and workingDir: $it" }
|
||||||
} ?: mainOutputDirs
|
} ?: mainOutputDirs
|
||||||
|
|
||||||
reporter.debug { "Cleaning output directories" }
|
reporter.debug { "Cleaning ${outputDirsToClean.size} output directories" }
|
||||||
cleanOrCreateDirectories(outputDirsToClean)
|
cleanOrCreateDirectories(outputDirsToClean)
|
||||||
}
|
}
|
||||||
return createCacheManager(args, projectDir).use { caches ->
|
return createCacheManager(args, projectDir).use { caches ->
|
||||||
@@ -266,20 +270,20 @@ abstract class IncrementalCompilerRunner<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getChangedFiles(
|
private fun getChangedFiles(
|
||||||
providedChangedFiles: ChangedFiles.Known?,
|
changedFiles: ChangedFiles.Known?,
|
||||||
allSourceFiles: List<File>,
|
allSourceFiles: List<File>,
|
||||||
caches: CacheManager
|
caches: CacheManager
|
||||||
): ChangedFiles.Known {
|
): ChangedFiles.Known {
|
||||||
return when {
|
return when {
|
||||||
providedChangedFiles == null -> caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles)
|
changedFiles == null -> caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles)
|
||||||
providedChangedFiles.forDependencies -> {
|
changedFiles.forDependencies -> {
|
||||||
val moreChangedFiles = caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles)
|
val moreChangedFiles = caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles)
|
||||||
ChangedFiles.Known(
|
ChangedFiles.Known(
|
||||||
modified = providedChangedFiles.modified + moreChangedFiles.modified,
|
modified = changedFiles.modified + moreChangedFiles.modified,
|
||||||
removed = providedChangedFiles.removed + moreChangedFiles.removed
|
removed = changedFiles.removed + moreChangedFiles.removed
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> providedChangedFiles
|
else -> changedFiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -109,7 +109,7 @@ fun makeIncrementally(
|
|||||||
classpathChanges = ClasspathSnapshotDisabled
|
classpathChanges = ClasspathSnapshotDisabled
|
||||||
)
|
)
|
||||||
//TODO set properly
|
//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)
|
super.performWorkBeforeCompilation(compilationMode, args)
|
||||||
|
|
||||||
if (compilationMode is CompilationMode.Incremental) {
|
if (compilationMode is CompilationMode.Incremental) {
|
||||||
val destinationDir = args.destinationAsFile
|
args.classpathAsList = listOf(args.destinationAsFile) + args.classpathAsList
|
||||||
destinationDir.mkdirs()
|
|
||||||
args.classpathAsList = listOf(destinationDir) + args.classpathAsList
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-37
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.incremental.classpathDiff
|
|||||||
import com.intellij.util.containers.Interner
|
import com.intellij.util.containers.Interner
|
||||||
import com.intellij.util.io.DataExternalizer
|
import com.intellij.util.io.DataExternalizer
|
||||||
import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
|
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.KotlinClassInfo
|
||||||
import org.jetbrains.kotlin.incremental.storage.*
|
import org.jetbrains.kotlin.incremental.storage.*
|
||||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||||
@@ -53,27 +54,6 @@ object CachedClasspathSnapshotSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal open class DataExternalizerForSealedClass<T>(
|
|
||||||
val baseClass: Class<T>,
|
|
||||||
val inheritorClasses: List<Class<out T>>,
|
|
||||||
val inheritorExternalizers: List<DataExternalizer<*>>
|
|
||||||
) : DataExternalizer<T> {
|
|
||||||
|
|
||||||
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<T>).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<ClasspathEntrySnapshot> {
|
object ClasspathEntrySnapshotExternalizer : DataExternalizer<ClasspathEntrySnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) {
|
override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) {
|
||||||
@@ -87,26 +67,23 @@ object ClasspathEntrySnapshotExternalizer : DataExternalizer<ClasspathEntrySnaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object ClassSnapshotExternalizer : DataExternalizerForSealedClass<ClassSnapshot>(
|
internal object ClassSnapshotExternalizer : DataExternalizer<ClassSnapshot> by DelegateDataExternalizer(
|
||||||
baseClass = ClassSnapshot::class.java,
|
types = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java),
|
||||||
inheritorClasses = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java),
|
typesExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer)
|
||||||
inheritorExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
internal object AccessibleClassSnapshotExternalizer : DataExternalizerForSealedClass<AccessibleClassSnapshot>(
|
internal object AccessibleClassSnapshotExternalizer : DataExternalizer<AccessibleClassSnapshot> by DelegateDataExternalizer(
|
||||||
baseClass = AccessibleClassSnapshot::class.java,
|
types = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java),
|
||||||
inheritorClasses = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java),
|
typesExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer)
|
||||||
inheritorExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private object KotlinClassSnapshotExternalizer : DataExternalizerForSealedClass<KotlinClassSnapshot>(
|
private object KotlinClassSnapshotExternalizer : DataExternalizer<KotlinClassSnapshot> by DelegateDataExternalizer(
|
||||||
baseClass = KotlinClassSnapshot::class.java,
|
types = listOf(
|
||||||
inheritorClasses = listOf(
|
|
||||||
RegularKotlinClassSnapshot::class.java,
|
RegularKotlinClassSnapshot::class.java,
|
||||||
PackageFacadeKotlinClassSnapshot::class.java,
|
PackageFacadeKotlinClassSnapshot::class.java,
|
||||||
MultifileClassKotlinClassSnapshot::class.java
|
MultifileClassKotlinClassSnapshot::class.java
|
||||||
),
|
),
|
||||||
inheritorExternalizers = listOf(
|
typesExternalizers = listOf(
|
||||||
RegularKotlinClassSnapshotExternalizer,
|
RegularKotlinClassSnapshotExternalizer,
|
||||||
PackageFacadeKotlinClassSnapshotExternalizer,
|
PackageFacadeKotlinClassSnapshotExternalizer,
|
||||||
MultifileClassKotlinClassSnapshotExternalizer
|
MultifileClassKotlinClassSnapshotExternalizer
|
||||||
@@ -185,8 +162,8 @@ internal object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo>
|
|||||||
ListExternalizer(StringExternalizer).save(output, info.classHeaderData.toList())
|
ListExternalizer(StringExternalizer).save(output, info.classHeaderData.toList())
|
||||||
ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList())
|
ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList())
|
||||||
NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName)
|
NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName)
|
||||||
LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).save(output, info.constantsMap)
|
MapExternalizer(StringExternalizer, ConstantValueExternalizer).save(output, info.constantsMap)
|
||||||
LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).save(output, info.inlineFunctionsAndAccessorsMap)
|
MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).save(output, info.inlineFunctionsAndAccessorsMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: DataInput): KotlinClassInfo {
|
override fun read(input: DataInput): KotlinClassInfo {
|
||||||
@@ -197,8 +174,8 @@ internal object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo>
|
|||||||
classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
||||||
classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
||||||
multifileClassName = NullableValueExternalizer(StringExternalizer).read(input),
|
multifileClassName = NullableValueExternalizer(StringExternalizer).read(input),
|
||||||
constantsMap = LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).read(input),
|
constantsMap = MapExternalizer(StringExternalizer, ConstantValueExternalizer).read(input),
|
||||||
inlineFunctionsAndAccessorsMap = LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).read(input)
|
inlineFunctionsAndAccessorsMap = MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).read(input)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -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.BuildTime
|
||||||
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
||||||
import org.jetbrains.kotlin.build.report.metrics.measure
|
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.KotlinClassInfo
|
||||||
|
import org.jetbrains.kotlin.incremental.PackagePartProtoData
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL
|
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.md5
|
||||||
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*
|
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*
|
||||||
@@ -96,7 +97,7 @@ object ClassSnapshotter {
|
|||||||
)
|
)
|
||||||
FILE_FACADE, MULTIFILE_CLASS_PART -> PackageFacadeKotlinClassSnapshot(
|
FILE_FACADE, MULTIFILE_CLASS_PART -> PackageFacadeKotlinClassSnapshot(
|
||||||
classId, classAbiHash, classMemberLevelSnapshot,
|
classId, classAbiHash, classMemberLevelSnapshot,
|
||||||
packageMemberNames = kotlinClassInfo.protoData.getNonPrivateMemberNames(includeInlineAccessors = true).toSet()
|
packageMemberNames = (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMembers().toSet()
|
||||||
)
|
)
|
||||||
MULTIFILE_CLASS -> MultifileClassKotlinClassSnapshot(
|
MULTIFILE_CLASS -> MultifileClassKotlinClassSnapshot(
|
||||||
classId, classAbiHash, classMemberLevelSnapshot,
|
classId, classAbiHash, classMemberLevelSnapshot,
|
||||||
|
|||||||
+22
-4
@@ -223,11 +223,9 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
|
|
||||||
LookupSymbol(name = "inlineProperty_ChangedType", scope = "com.example"),
|
LookupSymbol(name = "inlineProperty_ChangedType", scope = "com.example"),
|
||||||
LookupSymbol(name = "inlineProperty_ChangedType_BackingField", 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 = "inlineProperty_ChangedGetterImpl", scope = "com.example"),
|
||||||
LookupSymbol(name = "setInlineProperty_ChangedSetterImpl", scope = "com.example"),
|
LookupSymbol(name = "inlineProperty_ChangedSetterImpl", scope = "com.example"),
|
||||||
|
|
||||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass")
|
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass")
|
||||||
),
|
),
|
||||||
@@ -238,6 +236,26 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
).assertEquals(changes)
|
).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]. */
|
/** Tests [SupertypesInheritorsImpact]. */
|
||||||
@Test
|
@Test
|
||||||
override fun testImpactComputation_SupertypesInheritors() {
|
override fun testImpactComputation_SupertypesInheritors() {
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+20
@@ -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
|
||||||
+20
@@ -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
|
||||||
+2
-2
@@ -57,7 +57,7 @@ object InlineTestUtil {
|
|||||||
val binaryClasses = hashMapOf<String, KotlinJvmBinaryClass>()
|
val binaryClasses = hashMapOf<String, KotlinJvmBinaryClass>()
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
val binaryClass = loadBinaryClass(file)
|
val binaryClass = loadBinaryClass(file)
|
||||||
val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader)
|
val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader).map { it.jvmMethodSignature }.toSet()
|
||||||
|
|
||||||
val classVisitor = object : ClassVisitorWithName() {
|
val classVisitor = object : ClassVisitorWithName() {
|
||||||
override fun visitMethod(
|
override fun visitMethod(
|
||||||
@@ -81,7 +81,7 @@ object InlineTestUtil {
|
|||||||
var doLambdaInliningCheck = true
|
var doLambdaInliningCheck = true
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
val binaryClass = loadBinaryClass(file)
|
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
|
//if inline function creates anonymous object then do not try to check that all lambdas are inlined
|
||||||
val classVisitor = object : ClassVisitorWithName() {
|
val classVisitor = object : ClassVisitorWithName() {
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ import org.junit.Test
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.io.path.ExperimentalPathApi
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
import kotlin.io.path.createTempDirectory
|
import kotlin.io.path.createTempDirectory
|
||||||
import kotlin.io.path.createTempFile
|
|
||||||
import org.jetbrains.kotlin.konan.file.File as KonanFile
|
import org.jetbrains.kotlin.konan.file.File as KonanFile
|
||||||
|
|
||||||
@OptIn(ExperimentalPathApi::class)
|
@OptIn(ExperimentalPathApi::class)
|
||||||
@@ -325,7 +324,7 @@ class GenerateIrRuntime {
|
|||||||
buildHistoryFile = buildHistoryFile,
|
buildHistoryFile = buildHistoryFile,
|
||||||
modulesApiHistory = EmptyModulesApiHistory
|
modulesApiHistory = EmptyModulesApiHistory
|
||||||
)
|
)
|
||||||
compiler.compile(allFiles, args, MessageCollector.NONE, providedChangedFiles = null)
|
compiler.compile(allFiles, args, MessageCollector.NONE, changedFiles = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val cleanBuildTime = System.nanoTime() - cleanBuildStart
|
val cleanBuildTime = System.nanoTime() - cleanBuildStart
|
||||||
|
|||||||
Reference in New Issue
Block a user