KT-45777: Parse classes only once with ASM

to get both constants and inline functions.

Also add BasicClassInfo to ClassFileWithContents to simplify the code.
This commit is contained in:
Hung Nguyen
2021-12-20 09:37:23 +00:00
committed by nataliya.valtman
parent 9a995af0df
commit 9b96dbe2d2
5 changed files with 88 additions and 95 deletions
@@ -12,6 +12,7 @@ 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.ClassVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
@@ -46,10 +47,7 @@ class BasicClassInfo(
val innerClassesClassVisitor = InnerClassesClassVisitor(kotlinClassHeaderClassVisitor)
val basicClassInfoVisitor = BasicClassInfoClassVisitor(innerClassesClassVisitor)
ClassReader(classContents).accept(
basicClassInfoVisitor,
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
)
ClassReader(classContents).accept(basicClassInfoVisitor, SKIP_CODE or SKIP_DEBUG or SKIP_FRAMES)
val className = basicClassInfoVisitor.getClassName()
val innerClassesInfo = innerClassesClassVisitor.getInnerClassesInfo()
@@ -29,8 +29,12 @@ class ClassFile(
}
}
/** Information to locate a .class file, plus their contents. */
/** Contains the contents of a [ClassFile] and information extracted from the contents. */
class ClassFileWithContents(
val classFile: ClassFile,
val contents: ByteArray
)
) {
val classInfo: BasicClassInfo by lazy {
BasicClassInfo.compute(contents)
}
}
@@ -76,18 +76,13 @@ object ClassSnapshotter {
protoBased: Boolean? = null,
includeDebugInfoInSnapshot: Boolean? = null
): List<ClassSnapshot> {
val classesInfo: List<BasicClassInfo> = classes.map { BasicClassInfo.compute(it.contents) }
// Find inaccessible classes first, their snapshots will be `InaccessibleClassSnapshot`s.
val inaccessibleClasses: Set<BasicClassInfo> = getInaccessibleClasses(classesInfo).toSet()
val classesInfo: List<BasicClassInfo> = classes.map { it.classInfo }
val inaccessibleClassesInfo: Set<BasicClassInfo> = getInaccessibleClasses(classesInfo).toSet()
// Snapshot the remaining accessible classes
val accessibleClasses: List<ClassFileWithContents> = classes.mapIndexedNotNull { index, clazz ->
if (classesInfo[index] in inaccessibleClasses) null else clazz
}
val accessibleClassesInfo: List<BasicClassInfo> = classesInfo.filterNot { it in inaccessibleClasses }
val accessibleSnapshots: List<ClassSnapshot> =
doSnapshot(accessibleClasses, accessibleClassesInfo, protoBased, includeDebugInfoInSnapshot)
val accessibleClasses: List<ClassFileWithContents> = classes.filter { it.classInfo !in inaccessibleClassesInfo }
val accessibleSnapshots: List<ClassSnapshot> = doSnapshot(accessibleClasses, protoBased, includeDebugInfoInSnapshot)
val accessibleClassSnapshots: Map<ClassFileWithContents, ClassSnapshot> = accessibleClasses.zipToMap(accessibleSnapshots)
return classes.map { accessibleClassSnapshots[it] ?: InaccessibleClassSnapshot }
@@ -95,58 +90,47 @@ object ClassSnapshotter {
private fun doSnapshot(
classes: List<ClassFileWithContents>,
classesInfo: List<BasicClassInfo>,
protoBased: Boolean? = null,
includeDebugInfoInSnapshot: Boolean? = null
): List<ClassSnapshot> {
// Snapshot Kotlin classes first
val kotlinSnapshots: List<KotlinClassSnapshot?> = classes.mapIndexed { index, clazz ->
trySnapshotKotlinClass(clazz, classesInfo[index])
val kotlinSnapshots: List<KotlinClassSnapshot?> = classes.map { clazz ->
trySnapshotKotlinClass(clazz)
}
val kotlinClassSnapshots: Map<ClassFileWithContents, KotlinClassSnapshot?> = classes.zipToMap(kotlinSnapshots)
// Snapshot the remaining Java classes
val javaClasses: List<ClassFileWithContents> = classes.filter { kotlinClassSnapshots[it] == null }
val javaClassesInfo: List<BasicClassInfo> = classesInfo.mapIndexedNotNull { index, classInfo ->
val javaClass = classes[index]
if (kotlinClassSnapshots[javaClass] == null) classInfo else null
}
val javaSnapshots: List<JavaClassSnapshot> =
snapshotJavaClasses(javaClasses, javaClassesInfo, protoBased, includeDebugInfoInSnapshot)
val javaSnapshots: List<JavaClassSnapshot> = snapshotJavaClasses(javaClasses, protoBased, includeDebugInfoInSnapshot)
val javaClassSnapshots: Map<ClassFileWithContents, JavaClassSnapshot> = javaClasses.zipToMap(javaSnapshots)
return classes.map { kotlinClassSnapshots[it] ?: javaClassSnapshots[it]!! }
}
/** Creates [KotlinClassSnapshot] of the given class, or returns `null` if the class is not a Kotlin class. */
private fun trySnapshotKotlinClass(classFile: ClassFileWithContents, classInfo: BasicClassInfo): KotlinClassSnapshot? {
return if (classInfo.isKotlinClass) {
val kotlinClassInfo = KotlinClassInfo.createFrom(classInfo.classId, classInfo.kotlinClassHeader!!, classFile.contents)
KotlinClassSnapshot(kotlinClassInfo, classInfo.supertypes)
private fun trySnapshotKotlinClass(classFile: ClassFileWithContents): KotlinClassSnapshot? {
return if (classFile.classInfo.isKotlinClass) {
val kotlinClassInfo =
KotlinClassInfo.createFrom(classFile.classInfo.classId, classFile.classInfo.kotlinClassHeader!!, classFile.contents)
KotlinClassSnapshot(kotlinClassInfo, classFile.classInfo.supertypes)
} else null
}
/** Creates [JavaClassSnapshot]s of the given Java classes. */
private fun snapshotJavaClasses(
classes: List<ClassFileWithContents>,
classesInfo: List<BasicClassInfo>,
protoBased: Boolean? = null,
includeDebugInfoInSnapshot: Boolean? = null
): List<JavaClassSnapshot> {
return if (protoBased ?: protoBasedDefaultValue) {
snapshotJavaClassesProtoBased(classes, classesInfo)
snapshotJavaClassesProtoBased(classes)
} else {
classes.mapIndexed { index, clazz ->
JavaClassSnapshotter.snapshot(clazz.contents, classesInfo[index], includeDebugInfoInSnapshot)
}
classes.map { JavaClassSnapshotter.snapshot(it, includeDebugInfoInSnapshot) }
}
}
private fun snapshotJavaClassesProtoBased(
classFilesWithContents: List<ClassFileWithContents>,
classesInfo: List<BasicClassInfo>
): List<JavaClassSnapshot> {
val classIds = classesInfo.map { it.classId }
private fun snapshotJavaClassesProtoBased(classFilesWithContents: List<ClassFileWithContents>): List<JavaClassSnapshot> {
val classIds = classFilesWithContents.map { it.classInfo.classId }
val classesContents = classFilesWithContents.map { it.contents }
val descriptors: List<JavaClassDescriptor?> = JavaClassDescriptorCreator.create(classIds, classesContents)
val snapshots: List<JavaClassSnapshot> = descriptors.mapIndexed { index, descriptor ->
@@ -14,7 +14,7 @@ import org.jetbrains.org.objectweb.asm.tree.ClassNode
/** Computes a [JavaClassSnapshot] of a Java class. */
object JavaClassSnapshotter {
fun snapshot(classContents: ByteArray, classInfo: BasicClassInfo, includeDebugInfoInSnapshot: Boolean? = null): JavaClassSnapshot {
fun snapshot(classFile: ClassFileWithContents, includeDebugInfoInSnapshot: Boolean? = null): JavaClassSnapshot {
// We will extract ABI information from the given class and store it into the `abiClass` variable.
// It is acceptable to collect more info than required, but it is incorrect to collect less info than required.
// There are 2 approaches:
@@ -29,7 +29,7 @@ object JavaClassSnapshotter {
// - 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).
val classReader = ClassReader(classContents)
val classReader = ClassReader(classFile.contents)
classReader.accept(abiClass, ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES)
// Then, remove non-ABI info, which includes:
@@ -52,7 +52,10 @@ object JavaClassSnapshotter {
abiClass.methods.clear()
val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
return RegularJavaClassSnapshot(classInfo.classId, classInfo.supertypes, classAbiExcludingMembers, fieldsAbi, methodsAbi)
return RegularJavaClassSnapshot(
classFile.classInfo.classId, classFile.classInfo.supertypes,
classAbiExcludingMembers, fieldsAbi, methodsAbi
)
}
private val gson by lazy {