From 4ac6f01d31c17f7b2145560bed6da7435b053d60 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 9 Jun 2023 00:57:15 +0200 Subject: [PATCH] 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. --- .../box/coroutines/tailCallToNothing.kt | 4 ++ .../codegen/box/delegatedProperty/kt35707.kt | 3 ++ .../codegen/box/regressions/kt3850.kt | 4 ++ .../codegen/AbstractLightAnalysisModeTest.kt | 19 +++++++ .../BytecodeListingTextCollectingVisitor.kt | 52 ++++++++++++++----- .../jvm/abi/AbstractJvmAbiContentTest.kt | 9 +++- 6 files changed, 77 insertions(+), 14 deletions(-) diff --git a/compiler/testData/codegen/box/coroutines/tailCallToNothing.kt b/compiler/testData/codegen/box/coroutines/tailCallToNothing.kt index a0611f37ef9..2101f575e0a 100644 --- a/compiler/testData/codegen/box/coroutines/tailCallToNothing.kt +++ b/compiler/testData/codegen/box/coroutines/tailCallToNothing.kt @@ -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.* diff --git a/compiler/testData/codegen/box/delegatedProperty/kt35707.kt b/compiler/testData/codegen/box/delegatedProperty/kt35707.kt index 643657e36b6..d5d39733063 100644 --- a/compiler/testData/codegen/box/delegatedProperty/kt35707.kt +++ b/compiler/testData/codegen/box/delegatedProperty/kt35707.kt @@ -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 { diff --git a/compiler/testData/codegen/box/regressions/kt3850.kt b/compiler/testData/codegen/box/regressions/kt3850.kt index 7a1d643e7c4..007dc26fb5e 100644 --- a/compiler/testData/codegen/box/regressions/kt3850.kt +++ b/compiler/testData/codegen/box/regressions/kt3850.kt @@ -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"} diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/AbstractLightAnalysisModeTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/AbstractLightAnalysisModeTest.kt index 3c944275c99..b50fab33e63 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/AbstractLightAnalysisModeTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/AbstractLightAnalysisModeTest.kt @@ -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 } } diff --git a/compiler/tests-compiler-utils/tests/org/jetbrains/kotlin/codegen/BytecodeListingTextCollectingVisitor.kt b/compiler/tests-compiler-utils/tests/org/jetbrains/kotlin/codegen/BytecodeListingTextCollectingVisitor.kt index ae579671490..2e49adaf00e 100644 --- a/compiler/tests-compiler-utils/tests/org/jetbrains/kotlin/codegen/BytecodeListingTextCollectingVisitor.kt +++ b/compiler/tests-compiler-utils/tests/org/jetbrains/kotlin/codegen/BytecodeListingTextCollectingVisitor.kt @@ -11,11 +11,11 @@ import org.jetbrains.org.objectweb.asm.tree.ClassNode class BytecodeListingTextCollectingVisitor( val filter: Filter, + val allClasses: Map, 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() val parameterAnnotations = hashMapOf>() @@ -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 + } } diff --git a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/AbstractJvmAbiContentTest.kt b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/AbstractJvmAbiContentTest.kt index cf4e21c9d7d..8fda1193680 100644 --- a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/AbstractJvmAbiContentTest.kt +++ b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/AbstractJvmAbiContentTest.kt @@ -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)