From 87d3ce6ded98cc64b5dad524c008aa457f34567a Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 13 Jul 2022 14:10:08 +0200 Subject: [PATCH] Write a copy of SMAP to a new annotation To make it available for dynamically attached JVMTI agents. `@SourceDebugExtension` annotation value is equal to the SourceDebugExtension attribute value, which is checked now for all box tests. The difference is that the annotation stored in the constant pool, which is available for dynamically attached JVMTI agents. #KT-53438 Fixed --- .../kotlin/codegen/AbstractClassBuilder.java | 9 ++++ .../inline/AnonymousObjectTransformer.kt | 23 +++++++---- .../kotlin/codegen/CommonSMAPTestUtil.kt | 41 +++++++++++++++++++ .../BytecodeListingTextCollectingVisitor.kt | 25 ++++++++--- .../kotlin/load/java/JvmAnnotationNames.java | 2 + .../jvm/internal/SourceDebugExtension.kt | 18 ++++++++ .../kotlin-stdlib-runtime-merged.txt | 4 ++ 7 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 libraries/stdlib/jvm/runtime/kotlin/jvm/internal/SourceDebugExtension.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractClassBuilder.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractClassBuilder.java index c16dfe7ac0b..251191b28f4 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractClassBuilder.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractClassBuilder.java @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.codegen.inline.FileMapping; import org.jetbrains.kotlin.codegen.inline.SMAPBuilder; import org.jetbrains.kotlin.codegen.inline.SourceMapper; import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings; +import org.jetbrains.kotlin.load.java.JvmAnnotationNames; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.org.objectweb.asm.*; @@ -116,6 +117,14 @@ public abstract class AbstractClassBuilder implements ClassBuilder { @Override public void done() { getVisitor().visitSource(sourceName, debugInfo); + if (debugInfo != null) { + AnnotationVisitor v = + getVisitor().visitAnnotation(JvmAnnotationNames.SOURCE_DEBUG_EXTENSION_DESC, false).visitArray("value"); + for (String part : CodegenUtilKt.splitStringConstant(debugInfo)) { + v.visit(null, part); + } + v.visitEnd(); + } getVisitor().visitEnd(); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt index ae6f91ef5f8..2bb9b75c4d5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt @@ -70,15 +70,22 @@ class AnonymousObjectTransformer( } override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { - if (desc == JvmAnnotationNames.METADATA_DESC) { - // Empty inner class info because no inner classes are used in kotlin.Metadata and its arguments - val innerClassesInfo = FileBasedKotlinClass.InnerClassesInfo() - return FileBasedKotlinClass.convertAnnotationVisitor(metadataReader, desc, innerClassesInfo) - } else if (desc == DEBUG_METADATA_ANNOTATION_ASM_TYPE.descriptor) { - debugMetadataAnnotation = AnnotationNode(desc) - return debugMetadataAnnotation + when (desc) { + JvmAnnotationNames.METADATA_DESC -> { + // Empty inner class info because no inner classes are used in kotlin.Metadata and its arguments + val innerClassesInfo = FileBasedKotlinClass.InnerClassesInfo() + return FileBasedKotlinClass.convertAnnotationVisitor(metadataReader, desc, innerClassesInfo) + } + DEBUG_METADATA_ANNOTATION_ASM_TYPE.descriptor -> { + debugMetadataAnnotation = AnnotationNode(desc) + return debugMetadataAnnotation + } + JvmAnnotationNames.SOURCE_DEBUG_EXTENSION_DESC -> { + // The new value of @SourceDebugExtension will be written along with the new SMAP via ClassBuilder.visitSMAP. + return null + } + else -> return classBuilder.newAnnotation(desc, visible) } - return classBuilder.newAnnotation(desc, visible) } override fun visitMethod( diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/CommonSMAPTestUtil.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/CommonSMAPTestUtil.kt index 4074b02d5dd..89abe3f889f 100644 --- a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/CommonSMAPTestUtil.kt +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/CommonSMAPTestUtil.kt @@ -10,8 +10,10 @@ import org.jetbrains.kotlin.backend.common.output.OutputFile import org.jetbrains.kotlin.codegen.inline.RangeMapping import org.jetbrains.kotlin.codegen.inline.SMAPParser import org.jetbrains.kotlin.codegen.inline.toRange +import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.test.Assertions import org.jetbrains.kotlin.utils.keysToMap +import org.jetbrains.org.objectweb.asm.AnnotationVisitor import org.jetbrains.org.objectweb.asm.ClassReader import org.jetbrains.org.objectweb.asm.ClassVisitor import org.jetbrains.org.objectweb.asm.Opcodes @@ -21,16 +23,55 @@ object CommonSMAPTestUtil { fun extractSMAPFromClasses(outputFiles: Iterable): List { return outputFiles.map { outputFile -> var debugInfo: String? = null + var sdeAnnotationValue: String? = null ClassReader(outputFile.asByteArray()).accept(object : ClassVisitor(Opcodes.API_VERSION) { override fun visitSource(source: String?, debug: String?) { debugInfo = debug } + + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { + if (descriptor != JvmAnnotationNames.SOURCE_DEBUG_EXTENSION_DESC) return super.visitAnnotation(descriptor, visible) + return object : AnnotationVisitor(Opcodes.API_VERSION) { + override fun visitArray(name: String): AnnotationVisitor? { + if (name != "value") return super.visitArray(name) + check(sdeAnnotationValue == null) { outputFile.relativePath } + return object : AnnotationVisitor(Opcodes.API_VERSION) { + val result = mutableListOf() + + override fun visit(name: String?, value: Any?) { + result.add(value as String) + } + + override fun visitEnd() { + sdeAnnotationValue = result.joinToString("") + } + } + } + } + } }, 0) + checkSmapVsAnnotation(outputFile.relativePath, debugInfo, sdeAnnotationValue) + SMAPAndFile(debugInfo, outputFile.sourceFiles.single(), outputFile.relativePath) } } + private fun checkSmapVsAnnotation(relativePath: String, debugInfo: String?, sdeAnnotationValue: String?) { + if (debugInfo == sdeAnnotationValue) return + + if (debugInfo == null) { + error("@SourceDebugExtension is incorrectly generated for a class without SMAP: $relativePath") + } + if (sdeAnnotationValue == null) { + error("Missing @SourceDebugExtension annotation for a class with SMAP: $relativePath") + } + error( + "SMAP and @SourceDebugExtension value differs for $relativePath.\n" + + "SMAP:\n===\n$debugInfo\n===\n@SourceDebugExtension:\n===\n$sdeAnnotationValue\n" + ) + } + fun checkNoConflictMappings(compiledSmap: List?, assertions: Assertions) { if (compiledSmap == null) return 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 96ebb3d96ce..6d22ba52823 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 @@ -250,7 +250,9 @@ class BytecodeListingTextCollectingVisitor( override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? = visitAnnotationImpl { args -> - classAnnotations.add("@" + renderAnnotation(desc, args)) + renderClassAnnotation(desc, args)?.let { + classAnnotations.add("@$it") + } } private fun visitAnnotationImpl(end: (List) -> Unit): AnnotationVisitor? = @@ -300,12 +302,25 @@ class BytecodeListingTextCollectingVisitor( private fun renderAnnotation(desc: String, args: List): String { val name = Type.getType(desc).className - return if (args.isEmpty() || desc == "Lkotlin/Metadata;" || desc == "Lkotlin/coroutines/jvm/internal/DebugMetadata;") - name - else - "$name(${args.joinToString(", ")})" + return renderAnnotationArguments(args, name) } + private fun renderClassAnnotation(desc: String, args: List): String? { + // Don't render @SourceDebugExtension to avoid difference in text dumps of full and light analysis, because the compiler never + // generates it in the light analysis mode (since method bodies are not analyzed and we don't know if there's an inline call there). + if (desc == "Lkotlin/jvm/internal/SourceDebugExtension;") return null + + val name = Type.getType(desc).className + + // Don't render contents of @Metadata/@DebugMetadata because they're binary. + if (desc == "Lkotlin/Metadata;" || desc == "Lkotlin/coroutines/jvm/internal/DebugMetadata;") return name + + return renderAnnotationArguments(args, name) + } + + private fun renderAnnotationArguments(args: List, name: String): String = + if (args.isEmpty()) name else "$name(${args.joinToString(", ")})" + override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array?) { className = name classAccess = access diff --git a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java index c9d636fb7a8..25ffc50f3e7 100644 --- a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java +++ b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java @@ -71,6 +71,8 @@ public final class JvmAnnotationNames { public static final String SERIALIZED_IR_DESC = "L" + JvmClassName.byFqNameWithoutInnerClasses(SERIALIZED_IR_FQ_NAME).getInternalName() + ";"; public static final String SERIALIZED_IR_BYTES_FIELD_NAME = "b"; + public static final String SOURCE_DEBUG_EXTENSION_DESC = "Lkotlin/jvm/internal/SourceDebugExtension;"; + // Just for internal use: there is no such real classes in bytecode public static final FqName ENHANCED_NULLABILITY_ANNOTATION = new FqName("kotlin.jvm.internal.EnhancedNullability"); public static final FqName ENHANCED_MUTABILITY_ANNOTATION = new FqName("kotlin.jvm.internal.EnhancedMutability"); diff --git a/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/SourceDebugExtension.kt b/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/SourceDebugExtension.kt new file mode 100644 index 00000000000..233055af7eb --- /dev/null +++ b/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/SourceDebugExtension.kt @@ -0,0 +1,18 @@ +/* + * 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 kotlin.jvm.internal + +/** + * Provides a copy of the JVM attribute SourceDebugExtension on the class file. + * This annotation exists if and only if there is a SourceDebugExtension attribute on the class. + * To obtain the stored source mapping information, concatenate the strings in [value]. + * This annotation is needed for tools which inspect the Kotlin bytecode via JVMTI, + * which does not always provide access to the SourceDebugExtension attribute. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +@SinceKotlin("1.8") +annotation class SourceDebugExtension(val value: Array) diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index f2283dc353c..d90222ed09b 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -4147,6 +4147,10 @@ public final class kotlin/jvm/internal/ShortSpreadBuilder : kotlin/jvm/internal/ public final fun toArray ()[S } +public abstract interface annotation class kotlin/jvm/internal/SourceDebugExtension : java/lang/annotation/Annotation { + public abstract fun value ()[Ljava/lang/String; +} + public class kotlin/jvm/internal/SpreadBuilder { public fun (I)V public fun add (Ljava/lang/Object;)V