Add ReplaceWithSupertypeAnonymousTypeTransformer to light analysis tests

To make these tests behave closer to kapt, since kapt is the primary use
case for the light analysis mode.

AbstractLightAnalysisModeTest compares the text dump of bytecode
obtained with full analysis and light analysis, removing things like
anonymous/synthetic entities. In the light analysis mode anonymous
objects in supertypes are always approximated, and in the full analysis
mode they are always present as is in signatures. So we're transforming
the text dump in the same way, by approximating anonymous objects in
signatures (more precisely, in return types of methods and fields) to
the supertype.
This commit is contained in:
Alexander Udalov
2023-06-09 00:57:15 +02:00
parent c5f44486a9
commit 4ac6f01d31
6 changed files with 77 additions and 14 deletions
@@ -1,6 +1,10 @@
// WITH_STDLIB
// WITH_COROUTINES
// SKIP_MANGLE_VERIFICATION
// Light analysis mode test is muted because of some bug related to the old JVM backend. To be unmuted once the test is migrated to JVM IR.
// IGNORE_LIGHT_ANALYSIS
import helpers.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
@@ -1,5 +1,8 @@
// ISSUE: KT-35707
// Light analysis mode test is muted because of some bug related to the old JVM backend. To be unmuted once the test is migrated to JVM IR.
// IGNORE_LIGHT_ANALYSIS
import kotlin.reflect.KProperty
interface PropertyDelegate {
+4
View File
@@ -1,4 +1,8 @@
// !LANGUAGE: -PrivateInFileEffectiveVisibility
// In light analysis mode, anonymous object type is approximated to the supertype, so `fy` is unresolved.
// IGNORE_LIGHT_ANALYSIS
private class One {
val a1 = arrayOf(
object { val fy = "text"}
@@ -7,12 +7,18 @@ package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.Flags
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.ReplaceWithSupertypeAnonymousTypeTransformer
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
import org.jetbrains.kotlin.test.util.KtTestUtil.getAnnotationsJar
@@ -63,6 +69,16 @@ abstract class AbstractLightAnalysisModeTest : CodegenTestCase() {
)
val environment = KotlinCoreEnvironment.createForTests(testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
AnalysisHandlerExtension.registerExtension(environment.project, PartialAnalysisHandlerExtension())
StorageComponentContainerContributor.registerExtension(
environment.project,
object : StorageComponentContainerContributor {
override fun registerModuleComponents(
container: StorageComponentContainer, platform: TargetPlatform, moduleDescriptor: ModuleDescriptor
) {
container.useInstance(ReplaceWithSupertypeAnonymousTypeTransformer())
}
}
)
val testFiles = loadMultiFiles(files, environment.project)
val classFileFactory = GenerationUtils.compileFiles(testFiles.psiFiles, environment, TEST_LIGHT_ANALYSIS).factory
@@ -112,5 +128,8 @@ abstract class AbstractLightAnalysisModeTest : CodegenTestCase() {
override fun shouldWriteInnerClass(name: String, outerName: String?, innerName: String?) =
outerName != null && innerName != null
override val shouldTransformAnonymousTypes: Boolean
get() = true
}
}
@@ -11,11 +11,11 @@ import org.jetbrains.org.objectweb.asm.tree.ClassNode
class BytecodeListingTextCollectingVisitor(
val filter: Filter,
val allClasses: Map<Type, ClassNode>,
val withSignatures: Boolean,
api: Int = Opcodes.API_VERSION,
val withAnnotations: Boolean = true,
val sortDeclarations: Boolean = true,
) : ClassVisitor(api) {
) : ClassVisitor(Opcodes.API_VERSION) {
companion object {
@JvmOverloads
fun getText(
@@ -23,18 +23,27 @@ class BytecodeListingTextCollectingVisitor(
filter: Filter = Filter.EMPTY,
withSignatures: Boolean = false,
withAnnotations: Boolean = true,
sortDeclarations: Boolean = true
) = factory.getClassFiles()
.sortedBy { it.relativePath }
.mapNotNull {
val cr = ClassReader(it.asByteArray())
val node = ClassNode(Opcodes.API_VERSION)
cr.accept(node, ClassReader.SKIP_CODE)
val visitor = BytecodeListingTextCollectingVisitor(filter, withSignatures, withAnnotations = withAnnotations, sortDeclarations = sortDeclarations)
sortDeclarations: Boolean = true,
): String {
val classes = factory.getClassFiles()
.sortedBy { it.relativePath }
.map {
ClassNode(Opcodes.API_VERSION).also { node ->
ClassReader(it.asByteArray()).accept(node, ClassReader.SKIP_CODE)
}
}
val allClasses = classes.associateBy { Type.getObjectType(it.name) }
return classes.mapNotNull { node ->
val visitor = BytecodeListingTextCollectingVisitor(
filter, allClasses, withSignatures, withAnnotations = withAnnotations, sortDeclarations = sortDeclarations
)
node.accept(visitor)
if (!filter.shouldWriteClass(node)) null else visitor.text
}.joinToString("\n\n", postfix = "\n")
}
private val CLASS_OR_FIELD_OR_METHOD = setOf(ModifierTarget.CLASS, ModifierTarget.FIELD, ModifierTarget.METHOD)
private val CLASS_OR_METHOD = setOf(ModifierTarget.CLASS, ModifierTarget.METHOD)
@@ -69,12 +78,14 @@ class BytecodeListingTextCollectingVisitor(
fun shouldWriteMethod(access: Int, name: String, desc: String): Boolean
fun shouldWriteField(access: Int, name: String, desc: String): Boolean
fun shouldWriteInnerClass(name: String, outerName: String?, innerName: String?): Boolean
val shouldTransformAnonymousTypes: Boolean
object EMPTY : Filter {
override fun shouldWriteClass(node: ClassNode) = true
override fun shouldWriteMethod(access: Int, name: String, desc: String) = true
override fun shouldWriteField(access: Int, name: String, desc: String) = true
override fun shouldWriteInnerClass(name: String, outerName: String?, innerName: String?) = true
override val shouldTransformAnonymousTypes: Boolean get() = false
}
object ForCodegenTests : Filter {
@@ -82,6 +93,7 @@ class BytecodeListingTextCollectingVisitor(
override fun shouldWriteMethod(access: Int, name: String, desc: String): Boolean = true
override fun shouldWriteField(access: Int, name: String, desc: String): Boolean = true
override fun shouldWriteInnerClass(name: String, outerName: String?, innerName: String?): Boolean = !name.startsWith("helpers/")
override val shouldTransformAnonymousTypes: Boolean get() = false
}
}
@@ -183,7 +195,7 @@ class BytecodeListingTextCollectingVisitor(
return null
}
val returnType = Type.getReturnType(desc).className
val returnType = transformAnonymousTypeIfNeeded(Type.getReturnType(desc)).className
val parameterTypes = Type.getArgumentTypes(desc).map { it.className }
val methodAnnotations = arrayListOf<String>()
val parameterAnnotations = hashMapOf<Int, MutableList<String>>()
@@ -235,7 +247,7 @@ class BytecodeListingTextCollectingVisitor(
return null
}
val type = Type.getType(desc).className
val type = transformAnonymousTypeIfNeeded(Type.getType(desc)).className
val fieldSignature = if (withSignatures) "<$signature> " else ""
val fieldDeclaration = Declaration("field $fieldSignature$name: $type")
declarationsInsideClass.add(fieldDeclaration)
@@ -362,4 +374,20 @@ class BytecodeListingTextCollectingVisitor(
}
}
}
private fun transformAnonymousTypeIfNeeded(type: Type): Type {
if (filter.shouldTransformAnonymousTypes) {
val node = allClasses[type]
if (node != null && isAnonymousClass(node)) {
return Type.getObjectType(node.interfaces.singleOrNull() ?: node.superName)
}
}
return type
}
private fun isAnonymousClass(node: ClassNode): Boolean {
val innerClassAttr = node.innerClasses.find { it.name == node.name }
return innerClassAttr != null && innerClassAttr.innerName == null && innerClassAttr.outerName == null
}
}
@@ -1,9 +1,14 @@
/*
* Copyright 2010-2023 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.jvm.abi
import org.jetbrains.kotlin.codegen.BytecodeListingTextCollectingVisitor
import org.jetbrains.kotlin.incremental.isClassFile
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.ClassReader
import java.io.File
abstract class AbstractJvmAbiContentTest : BaseJvmAbiTest() {
@@ -19,8 +24,8 @@ abstract class AbstractJvmAbiContentTest : BaseJvmAbiTest() {
val reader = ClassReader(bytes)
val visitor = BytecodeListingTextCollectingVisitor(
filter = BytecodeListingTextCollectingVisitor.Filter.EMPTY,
allClasses = emptyMap(),
withSignatures = false,
api = Opcodes.API_VERSION,
sortDeclarations = false, // Declaration order matters for the ABI
)
reader.accept(visitor, 0)