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