From 34a49b3f560fe22668935e93e78960255c3965d0 Mon Sep 17 00:00:00 2001 From: pyos Date: Thu, 8 Dec 2022 11:34:39 +0100 Subject: [PATCH] jvm-abi-gen: only keep relevant InnerClasses from current module If a class is not referenced by the ABI class file, its InnerClasses entry should only be kept if it's the current class or a descendant of the current class. --- .../kotlin/jvm/abi/JvmAbiOutputExtension.kt | 37 +++++++++++++++---- .../content/innerClasses/innerClasses.kt | 12 ++++-- .../content/innerClasses/signatures.txt | 26 ++++++++++++- 3 files changed, 62 insertions(+), 13 deletions(-) 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 bed777a15e2..ece0e288665 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 @@ -73,7 +73,7 @@ class JvmAbiOutputExtension( else -> /* abiInfo is AbiClassInfo.Stripped */ { val methodInfo = (abiInfo as AbiClassInfo.Stripped).methodInfo val innerClassInfos = mutableMapOf() - val innerClassesToKeep = abiClassInfos.keys.toMutableSet() + val innerClassesToKeep = mutableSetOf() val writer = ClassWriter(0) val remapper = ClassRemapper(writer, object : Remapper() { override fun map(internalName: String): String = @@ -150,13 +150,11 @@ class JvmAbiOutputExtension( override fun visitEnd() {} }, 0) - innerClassesToKeep.retainAll(innerClassInfos.keys) - for (name in innerClassesToKeep.toList()) { - var info = innerClassInfos[name] - while (info != null) { - info = info.outerName?.takeIf(innerClassesToKeep::add)?.let(innerClassInfos::get) - } - } + innerClassesToKeep.addInnerClasses(innerClassInfos, internalName) + innerClassesToKeep.addOuterClasses(innerClassInfos) + + // Output classes in sorted order so that changes in original ordering due to method bodies, etc. + // don't affect the ABI JAR. for (name in innerClassesToKeep.sorted()) { innerClassInfos[name]?.let { writer.visitInnerClass(it.name, it.outerName, it.innerName, it.access) } } @@ -170,5 +168,28 @@ class JvmAbiOutputExtension( return metadata + classFiles } + + // Outer class infos for a class and all classes transitively nested in it (that are public ABI) + // should be kept in its own class file even if the classes are otherwise unused. + private fun MutableSet.addInnerClasses(innerClassInfos: Map, internalName: String) { + val innerClassesByOuterName = innerClassInfos.values.groupBy { it.outerName } + val stack = mutableListOf(internalName) + while (stack.isNotEmpty()) { + val next = stack.removeLast() + add(next) + // Classes form a tree by nesting, so none of the children have been visited yet. + innerClassesByOuterName[next]?.mapNotNullTo(stack) { it.name.takeIf(abiClassInfos::contains) } + } + } + + // For every class A.B, if its outer class info is kept then so should be A's. + private fun MutableSet.addOuterClasses(innerClassInfos: Map) { + for (name in toList()) { + var info = innerClassInfos[name] + while (info != null) { + info = info.outerName?.takeIf(::add)?.let(innerClassInfos::get) + } + } + } } } diff --git a/plugins/jvm-abi-gen/testData/content/innerClasses/innerClasses.kt b/plugins/jvm-abi-gen/testData/content/innerClasses/innerClasses.kt index 28d0d9b4549..9f287fd2b20 100644 --- a/plugins/jvm-abi-gen/testData/content/innerClasses/innerClasses.kt +++ b/plugins/jvm-abi-gen/testData/content/innerClasses/innerClasses.kt @@ -2,13 +2,19 @@ class InAnnotation {} class InPublicMethod { - fun foo(x: Outer.Middle.Inner) {} + fun foo(x: Outer.Middle.Inner): Class<*> = OuterKt.MiddleKt.InnerKt::class.java } class InPrivateMethod { - private fun foo(x: Outer.Middle.Inner) {} + private fun foo(x: Outer.Middle.Inner): Class<*> = OuterKt.MiddleKt.InnerKt::class.java } class InInlineMethod { - inline fun foo(): Class<*> = Outer.Middle.Inner::class.java + inline fun foo(x: Outer.Middle.Inner): Class<*> = OuterKt.MiddleKt.InnerKt::class.java +} + +class OuterKt { + class MiddleKt { + class InnerKt + } } diff --git a/plugins/jvm-abi-gen/testData/content/innerClasses/signatures.txt b/plugins/jvm-abi-gen/testData/content/innerClasses/signatures.txt index bdc5b748fe6..4d8fd5ebec6 100644 --- a/plugins/jvm-abi-gen/testData/content/innerClasses/signatures.txt +++ b/plugins/jvm-abi-gen/testData/content/innerClasses/signatures.txt @@ -15,8 +15,10 @@ public final class InInlineMethod { // source: 'innerClasses.kt' public inner class Outer$Middle public inner class Outer$Middle$Inner + public final inner class OuterKt$MiddleKt + public final inner class OuterKt$MiddleKt$InnerKt public method (): void - public final @org.jetbrains.annotations.NotNull method foo(): java.lang.Class + public final @org.jetbrains.annotations.NotNull method foo(@org.jetbrains.annotations.NotNull p0: Outer$Middle$Inner): java.lang.Class } @kotlin.Metadata public final class InPrivateMethod { @@ -29,7 +31,7 @@ public final class InPublicMethod { public inner class Outer$Middle public inner class Outer$Middle$Inner public method (): void - public final method foo(@org.jetbrains.annotations.NotNull p0: Outer$Middle$Inner): void + public final @org.jetbrains.annotations.NotNull method foo(@org.jetbrains.annotations.NotNull p0: Outer$Middle$Inner): java.lang.Class } public class Outer$Middle$Inner { // source: 'Outer.java' @@ -48,3 +50,23 @@ public class Outer { public inner class Outer$Middle public method (): void } +@kotlin.Metadata +public final class OuterKt$MiddleKt$InnerKt { + // source: 'innerClasses.kt' + public final inner class OuterKt$MiddleKt + public final inner class OuterKt$MiddleKt$InnerKt + public method (): void +} +@kotlin.Metadata +public final class OuterKt$MiddleKt { + // source: 'innerClasses.kt' + public final inner class OuterKt$MiddleKt + public final inner class OuterKt$MiddleKt$InnerKt + public method (): void +} +@kotlin.Metadata +public final class OuterKt { + // source: 'innerClasses.kt' + public final inner class OuterKt$MiddleKt + public method (): void +} \ No newline at end of file