diff --git a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiOutputExtension.kt b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiOutputExtension.kt index 781173ed4e7..58e3e2c3847 100644 --- a/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiOutputExtension.kt +++ b/plugins/jvm-abi-gen/src/org/jetbrains/kotlin/jvm/abi/JvmAbiOutputExtension.kt @@ -15,7 +15,9 @@ import org.jetbrains.kotlin.codegen.ClassFileFactory import org.jetbrains.kotlin.codegen.extensions.ClassFileFactoryFinalizerExtension import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.org.objectweb.asm.* +import org.jetbrains.org.objectweb.asm.commons.ClassRemapper import org.jetbrains.org.objectweb.asm.commons.Method +import org.jetbrains.org.objectweb.asm.commons.Remapper import java.io.File class JvmAbiOutputExtension( @@ -43,6 +45,8 @@ class JvmAbiOutputExtension( } } + private class InnerClassInfo(val name: String, val outerName: String?, val innerName: String?, val access: Int) + private class AbiOutputFiles(val abiClassInfos: Map, val outputFiles: OutputFileCollection) : OutputFileCollection { override fun get(relativePath: String): OutputFile? { @@ -68,8 +72,14 @@ class JvmAbiOutputExtension( else -> /* abiInfo is AbiClassInfo.Stripped */ { val methodInfo = (abiInfo as AbiClassInfo.Stripped).methodInfo + val innerClassInfos = mutableMapOf() + val innerClassesToKeep = abiClassInfos.keys.toMutableSet() val writer = ClassWriter(0) - ClassReader(outputFile.asByteArray()).accept(object : ClassVisitor(Opcodes.API_VERSION, writer) { + val remapper = ClassRemapper(writer, object : Remapper() { + override fun map(internalName: String): String = + internalName.also { innerClassesToKeep.add(it) } + }) + ClassReader(outputFile.asByteArray()).accept(object : ClassVisitor(Opcodes.API_VERSION, remapper) { // Strip private fields. override fun visitField( access: Int, @@ -123,10 +133,10 @@ class JvmAbiOutputExtension( } // Remove inner classes which are not present in the abi jar. - override fun visitInnerClass(name: String?, outerName: String?, innerName: String?, access: Int) { - if (name in abiClassInfos.keys) { - super.visitInnerClass(name, outerName, innerName, access) - } + override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) { + // `visitInnerClass` is called before `visitField`/`visitMethod`, so we don't know + // which types are referenced by kept methods yet. + innerClassInfos[name] = InnerClassInfo(name, outerName, innerName, access) } // Strip private declarations from the Kotlin Metadata annotation. @@ -136,7 +146,18 @@ class JvmAbiOutputExtension( return delegate return abiMetadataProcessor(delegate) } + + override fun visitEnd() {} }, 0) + + for (name in innerClassesToKeep.toList()) { + val info = innerClassInfos[name] ?: continue + generateSequence(info) { it.outerName?.takeIf(innerClassesToKeep::add)?.let(innerClassInfos::get) } + .forEach { writer.visitInnerClass(it.name, it.outerName, it.innerName, it.access) } + } + + writer.visitEnd() + SimpleOutputBinaryFile(outputFile.sourceFiles, outputFile.relativePath, writer.toByteArray()) } } diff --git a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/JvmAbiContentTestGenerated.java b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/JvmAbiContentTestGenerated.java index a2c99866b46..94cfe1870c3 100644 --- a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/JvmAbiContentTestGenerated.java +++ b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/JvmAbiContentTestGenerated.java @@ -55,6 +55,11 @@ public class JvmAbiContentTestGenerated extends AbstractJvmAbiContentTest { runTest("plugins/jvm-abi-gen/testData/content/class/"); } + @TestMetadata("innerClasses") + public void testInnerClasses() throws Exception { + runTest("plugins/jvm-abi-gen/testData/content/innerClasses/"); + } + @TestMetadata("kt50005") public void testKt50005() throws Exception { runTest("plugins/jvm-abi-gen/testData/content/kt50005/"); diff --git a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/LegacyJvmAbiContentTestGenerated.java b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/LegacyJvmAbiContentTestGenerated.java index 7c9cc65f226..55a17226bb9 100644 --- a/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/LegacyJvmAbiContentTestGenerated.java +++ b/plugins/jvm-abi-gen/test/org/jetbrains/kotlin/jvm/abi/LegacyJvmAbiContentTestGenerated.java @@ -55,6 +55,11 @@ public class LegacyJvmAbiContentTestGenerated extends AbstractLegacyJvmAbiConten runTest("plugins/jvm-abi-gen/testData/content/class/"); } + @TestMetadata("innerClasses") + public void testInnerClasses() throws Exception { + runTest("plugins/jvm-abi-gen/testData/content/innerClasses/"); + } + @TestMetadata("kt50005") public void testKt50005() throws Exception { runTest("plugins/jvm-abi-gen/testData/content/kt50005/"); diff --git a/plugins/jvm-abi-gen/testData/content/class/signatures.txt b/plugins/jvm-abi-gen/testData/content/class/signatures.txt index 458ff72c3cb..c6e0393eedd 100644 --- a/plugins/jvm-abi-gen/testData/content/class/signatures.txt +++ b/plugins/jvm-abi-gen/testData/content/class/signatures.txt @@ -28,8 +28,8 @@ public final class test/Class$NestedInnerClass$NestedNestedInnerClass { @kotlin.Metadata final class test/Class$NestedInnerClass { // source: 'classes.kt' - private final inner class test/Class$NestedInnerClass public final inner class test/Class$NestedInnerClass$NestedNestedInnerClass + private final inner class test/Class$NestedInnerClass public method (): void } @kotlin.Metadata diff --git a/plugins/jvm-abi-gen/testData/content/innerClasses/Anno.java b/plugins/jvm-abi-gen/testData/content/innerClasses/Anno.java new file mode 100644 index 00000000000..9d6385df819 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/content/innerClasses/Anno.java @@ -0,0 +1,3 @@ +public @interface Anno { + Class value(); +} diff --git a/plugins/jvm-abi-gen/testData/content/innerClasses/Outer.java b/plugins/jvm-abi-gen/testData/content/innerClasses/Outer.java new file mode 100644 index 00000000000..f4a6352e1c4 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/content/innerClasses/Outer.java @@ -0,0 +1,6 @@ +public class Outer { + public static class Middle { + public static class Inner { + } + } +} diff --git a/plugins/jvm-abi-gen/testData/content/innerClasses/directives.txt b/plugins/jvm-abi-gen/testData/content/innerClasses/directives.txt new file mode 100644 index 00000000000..13ed9e2bf23 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/content/innerClasses/directives.txt @@ -0,0 +1 @@ +// IGNORE_BACKEND_LEGACY: JVM_IR diff --git a/plugins/jvm-abi-gen/testData/content/innerClasses/innerClasses.kt b/plugins/jvm-abi-gen/testData/content/innerClasses/innerClasses.kt new file mode 100644 index 00000000000..28d0d9b4549 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/content/innerClasses/innerClasses.kt @@ -0,0 +1,14 @@ +@Anno(Outer.Middle.Inner::class) +class InAnnotation {} + +class InPublicMethod { + fun foo(x: Outer.Middle.Inner) {} +} + +class InPrivateMethod { + private fun foo(x: Outer.Middle.Inner) {} +} + +class InInlineMethod { + inline fun foo(): Class<*> = Outer.Middle.Inner::class.java +} diff --git a/plugins/jvm-abi-gen/testData/content/innerClasses/signatures.txt b/plugins/jvm-abi-gen/testData/content/innerClasses/signatures.txt new file mode 100644 index 00000000000..1691dceda99 --- /dev/null +++ b/plugins/jvm-abi-gen/testData/content/innerClasses/signatures.txt @@ -0,0 +1,50 @@ +public annotation class Anno { + // source: 'Anno.java' + public abstract method value(): java.lang.Class +} +@Anno(value=Outer$Middle$Inner::class) +@kotlin.Metadata +public final class InAnnotation { + // source: 'innerClasses.kt' + public inner class Outer$Middle$Inner + public inner class Outer$Middle + public method (): void +} +@kotlin.Metadata +public final class InInlineMethod { + // source: 'innerClasses.kt' + public inner class Outer$Middle$Inner + public inner class Outer$Middle + public method (): void + public final @org.jetbrains.annotations.NotNull method foo(): java.lang.Class +} +@kotlin.Metadata +public final class InPrivateMethod { + // source: 'innerClasses.kt' + public method (): void +} +@kotlin.Metadata +public final class InPublicMethod { + // source: 'innerClasses.kt' + public inner class Outer$Middle$Inner + public inner class Outer$Middle + public method (): void + public final method foo(@org.jetbrains.annotations.NotNull p0: Outer$Middle$Inner): void +} +public class Outer$Middle$Inner { + // source: 'Outer.java' + public inner class Outer$Middle + public inner class Outer$Middle$Inner + public method (): void +} +public class Outer$Middle { + // source: 'Outer.java' + public inner class Outer$Middle + public inner class Outer$Middle$Inner + public method (): void +} +public class Outer { + // source: 'Outer.java' + public inner class Outer$Middle + public method (): void +}