jvm-abi-gen: remove parts of the SMAP used only in stripped methods

This commit is contained in:
Sonya Valchuk
2024-03-08 10:19:26 +00:00
committed by Space Cloud
parent 0005ba47f8
commit 9518416bdb
3 changed files with 50 additions and 29 deletions
@@ -13,6 +13,8 @@ import org.jetbrains.kotlin.cli.common.output.writeAllTo
import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentUtil
import org.jetbrains.kotlin.codegen.ClassFileFactory
import org.jetbrains.kotlin.codegen.extensions.ClassFileFactoryFinalizerExtension
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.visitWithSplitting
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.commons.ClassRemapper
@@ -94,7 +96,11 @@ class JvmAbiOutputExtension(
val prune = abiInfo.prune
val memberInfo = abiInfo.memberInfo
val innerClassesToKeep = mutableSetOf<String>()
val noMethodsKept = memberInfo.none { it.value == AbiMethodInfo.KEEP && it.key is JvmMethodSignature }
var sourceFile: String? = null
var sourceMap: SourceMapCopier? = null
var sourceMapAnnotationPresent = false
val writer = ClassWriter(0)
val remapper = ClassRemapper(writer, object : Remapper() {
override fun map(internalName: String): String =
@@ -105,6 +111,13 @@ class JvmAbiOutputExtension(
private val keptMethods = mutableListOf<MethodNode>()
private val innerClassInfos = mutableMapOf<String, InnerClassInfo>()
override fun visitSource(source: String?, debug: String?) {
sourceFile = source.takeIf { !removeDebugInfo }
sourceMap = debug.takeIf { !removeDebugInfo && !prune }
?.let(SMAPParser::parseOrNull)
?.let { SourceMapCopier(SourceMapper(sourceFile, it), it) }
}
// Strip private fields.
override fun visitField(
access: Int,
@@ -134,21 +147,12 @@ class JvmAbiOutputExtension(
keptMethods += it
}
if (info == AbiMethodInfo.KEEP || access and (Opcodes.ACC_NATIVE or Opcodes.ACC_ABSTRACT) != 0) {
return if (removeDebugInfo) DebugInfoRemovingMethodVisitor(node) else node
}
return BodyStrippingMethodVisitor(node)
}
override fun visitSource(source: String?, debug: String?) {
when {
removeDebugInfo -> super.visitSource(null, null)
prune || noMethodsKept -> {
// Strip SourceDebugExtension attribute if there are no inline functions.
super.visitSource(source, null)
}
else -> super.visitSource(source, debug)
return when {
info != AbiMethodInfo.KEEP && access and (Opcodes.ACC_NATIVE or Opcodes.ACC_ABSTRACT) == 0 ->
BodyStrippingMethodVisitor(node)
removeDebugInfo ->
DebugInfoRemovingMethodVisitor(node)
else -> node
}
}
@@ -160,9 +164,9 @@ class JvmAbiOutputExtension(
}
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
// Strip @SourceDebugExtension annotation if we're removing debug info.
if (descriptor == JvmAnnotationNames.SOURCE_DEBUG_EXTENSION_DESC) {
if (removeDebugInfo || noMethodsKept || prune) return null
sourceMapAnnotationPresent = true
return null
}
val delegate = super.visitAnnotation(descriptor, visible)
@@ -180,19 +184,36 @@ class JvmAbiOutputExtension(
}
override fun visitEnd() {
// Output class members in sorted order so that changes in original ordering don't affect the ABI JAR.
if (!preserveDeclarationOrder) {
// Output class members in sorted order so that changes in original ordering don't affect the ABI JAR.
keptFields.sortWith(compareBy(FieldNode::name, FieldNode::desc))
keptMethods.sortWith(compareBy(MethodNode::name, MethodNode::desc))
}
keptFields
.apply { if (!preserveDeclarationOrder) sortWith(compareBy(FieldNode::name, FieldNode::desc)) }
.forEach { it.accept(cv) }
for (field in keptFields) {
field.accept(cv)
}
keptMethods
.apply { if (!preserveDeclarationOrder) sortWith(compareBy(MethodNode::name, MethodNode::desc)) }
.forEach { it.accept(cv) }
for (method in keptMethods) {
val mv = with(method) { cv.visitMethod(access, name, desc, signature, exceptions?.toTypedArray()) }
// Mapping the line numbers should only be done *after* sorting methods, as otherwise the order
// of inline methods may be visible in their synthetic line numbers.
method.accept(sourceMap?.let { SourceMapCopyingMethodVisitor(it, mv) } ?: mv)
}
val sourceMapText = sourceMap?.parent?.takeIf { !it.isTrivial }
?.let { SMAPBuilder.build(it.resultMappings, backwardsCompatibleSyntax = false) }
// This is technically not the right way to use `ClassVisitor` (`visitSource` should be called before
// `visitMethod` and such), but `ClassWriter` doesn't care, and we're a bit constrained here (see above).
cv.visitSource(sourceFile, sourceMapText)
if (sourceMapAnnotationPresent && sourceMapText != null) {
val av = cv.visitAnnotation(JvmAnnotationNames.SOURCE_DEBUG_EXTENSION_DESC, false)
av.visitWithSplitting("value", sourceMapText)
av.visitEnd()
}
innerClassesToKeep.addInnerClasses(innerClassInfos, internalName)
innerClassesToKeep.addOuterClasses(innerClassInfos)
for (name in innerClassesToKeep.sorted()) {
innerClassInfos[name]?.let { cv.visitInnerClass(it.name, it.outerName, it.innerName, it.access) }
}
@@ -1 +0,0 @@
// IGNORE_BACKEND: JVM_IR
@@ -4,5 +4,6 @@ fun g() = f()
inline fun f() = 1
// This does not change the line numbers in f, but does change the line
// numbers in the SourceDebug extension section which has to be rewritten.
// The line numbers in f are the same, but the SourceDebug extension section
// contains a different line number for the inline function call in g.
// g is not inline, though, so the debugging information for its body is removed.