jvm-abi-gen: Support removeDebugInfo option

This option removes debug info, such as:
- SourceFile attribute on classes
- LocalVariableTable, LineNumberTable, SourceDebugExtension attributes
  on inline functions
- @SourceDebugExtension annotation on inline functions

 #KT-33020 Fixed
This commit is contained in:
Alexander Udalov
2023-11-23 23:58:07 +01:00
committed by Space Team
parent 73af0fffa9
commit fe1e2b8b34
19 changed files with 156 additions and 17 deletions
@@ -22,17 +22,28 @@ class JvmAbiCommandLineProcessor : CommandLineProcessor {
"Output path for generated files. This can be either a directory or a jar file.",
true
)
val REMOVE_DEBUG_INFO_OPTION: CliOption =
CliOption(
"removeDebugInfo",
"true/false",
"Remove debug info from the generated class files. False by default. Note that if ABI jars are used for incremental " +
"compilation, it's not safe to remove debug info because debugger will behave incorrectly on non-recompiled call " +
"sites of inline functions.",
false,
)
}
override val pluginId: String
get() = COMPILER_PLUGIN_ID
override val pluginOptions: Collection<CliOption>
get() = listOf(OUTPUT_PATH_OPTION)
get() = listOf(OUTPUT_PATH_OPTION, REMOVE_DEBUG_INFO_OPTION)
override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) {
when (option) {
OUTPUT_PATH_OPTION -> configuration.put(JvmAbiConfigurationKeys.OUTPUT_PATH, value)
REMOVE_DEBUG_INFO_OPTION -> configuration.put(JvmAbiConfigurationKeys.REMOVE_DEBUG_INFO, value == "true")
else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
}
}
@@ -20,7 +20,10 @@ class JvmAbiComponentRegistrar : CompilerPluginRegistrar() {
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
val builderExtension = JvmAbiClassBuilderInterceptor()
val outputExtension = JvmAbiOutputExtension(File(outputPath), builderExtension.abiClassInfo, messageCollector)
val outputExtension = JvmAbiOutputExtension(
File(outputPath), builderExtension.abiClassInfo, messageCollector,
configuration.getBoolean(JvmAbiConfigurationKeys.REMOVE_DEBUG_INFO),
)
ClassGeneratorExtension.registerExtension(builderExtension)
ClassFileFactoryFinalizerExtension.registerExtension(outputExtension)
}
@@ -10,4 +10,6 @@ import org.jetbrains.kotlin.config.CompilerConfigurationKey
object JvmAbiConfigurationKeys {
val OUTPUT_PATH: CompilerConfigurationKey<String> =
CompilerConfigurationKey.create(JvmAbiCommandLineProcessor.OUTPUT_PATH_OPTION.description)
val REMOVE_DEBUG_INFO: CompilerConfigurationKey<Boolean> =
CompilerConfigurationKey.create(JvmAbiCommandLineProcessor.REMOVE_DEBUG_INFO_OPTION.description)
}
@@ -24,11 +24,12 @@ class JvmAbiOutputExtension(
private val outputPath: File,
private val abiClassInfos: Map<String, AbiClassInfo>,
private val messageCollector: MessageCollector,
private val removeDebugInfo: Boolean,
) : ClassFileFactoryFinalizerExtension {
override fun finalizeClassFactory(factory: ClassFileFactory) {
// We need to wait until the end to produce any output in order to strip classes
// from the InnerClasses attributes.
val outputFiles = AbiOutputFiles(abiClassInfos, factory)
val outputFiles = AbiOutputFiles(abiClassInfos, factory, removeDebugInfo)
if (outputPath.extension == "jar") {
// We don't include the runtime or main class in interface jars and always reset time stamps.
CompileEnvironmentUtil.writeToJar(
@@ -47,8 +48,11 @@ class JvmAbiOutputExtension(
private class InnerClassInfo(val name: String, val outerName: String?, val innerName: String?, val access: Int)
private class AbiOutputFiles(val abiClassInfos: Map<String, AbiClassInfo>, val outputFiles: OutputFileCollection) :
OutputFileCollection {
private class AbiOutputFiles(
val abiClassInfos: Map<String, AbiClassInfo>,
val outputFiles: OutputFileCollection,
val removeDebugInfo: Boolean,
) : OutputFileCollection {
override fun get(relativePath: String): OutputFile? {
error("AbiOutputFiles does not implement `get`.")
}
@@ -104,9 +108,23 @@ class JvmAbiOutputExtension(
?: return null
val visitor = super.visitMethod(access, name, descriptor, signature, exceptions)
if (info == AbiMethodInfo.KEEP || access and (Opcodes.ACC_NATIVE or Opcodes.ACC_ABSTRACT) != 0) {
return object : MethodVisitor(Opcodes.API_VERSION, visitor) {
override fun visitLineNumber(line: Int, start: Label?) {
if (!removeDebugInfo) {
super.visitLineNumber(line, start)
}
}
if (info == AbiMethodInfo.KEEP || access and (Opcodes.ACC_NATIVE or Opcodes.ACC_ABSTRACT) != 0)
return visitor
override fun visitLocalVariable(
name: String?, descriptor: String?, signature: String?, start: Label?, end: Label?, index: Int,
) {
if (!removeDebugInfo) {
super.visitLocalVariable(name, descriptor, signature, start, end, index)
}
}
}
}
return object : MethodVisitor(Opcodes.API_VERSION, visitor) {
override fun visitCode() {
@@ -123,13 +141,15 @@ class JvmAbiOutputExtension(
}
}
// Strip source debug extensions if there are no inline functions.
override fun visitSource(source: String?, debug: String?) {
// TODO Normalize and strip unused line numbers from SourceDebugExtensions
if (methodInfo.values.any { it == AbiMethodInfo.KEEP })
super.visitSource(source, debug)
else
super.visitSource(source, null)
when {
removeDebugInfo -> super.visitSource(null, null)
methodInfo.values.none { it == AbiMethodInfo.KEEP } -> {
// Strip SourceDebugExtension attribute if there are no inline functions.
super.visitSource(source, null)
}
else -> super.visitSource(source, debug)
}
}
// Remove inner classes which are not present in the abi jar.
@@ -139,11 +159,15 @@ class JvmAbiOutputExtension(
innerClassInfos[name] = InnerClassInfo(name, outerName, innerName, access)
}
// Strip private declarations from the Kotlin Metadata annotation.
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
// Strip @SourceDebugExtension annotation if we're removing debug info.
if (removeDebugInfo && descriptor == JvmAnnotationNames.SOURCE_DEBUG_EXTENSION_DESC)
return null
val delegate = super.visitAnnotation(descriptor, visible)
if (descriptor != JvmAnnotationNames.METADATA_DESC)
return delegate
// Strip private declarations from the Kotlin Metadata annotation.
return abiMetadataProcessor(delegate)
}
@@ -75,9 +75,12 @@ abstract class BaseJvmAbiTest : TestCase() {
freeArgs = listOf(compilation.srcDir.canonicalPath)
classpath = (abiDependencies + kotlinJvmStdlib).joinToString(File.pathSeparator) { it.canonicalPath }
pluginClasspaths = arrayOf(abiPluginJar.canonicalPath)
pluginOptions = arrayOf(
pluginOptions = listOfNotNull(
abiOption(JvmAbiCommandLineProcessor.OUTPUT_PATH_OPTION.optionName, compilation.abiDir.canonicalPath),
)
abiOption(JvmAbiCommandLineProcessor.REMOVE_DEBUG_INFO_OPTION.optionName, true.toString()).takeIf {
InTextDirectivesUtils.findStringWithPrefixes(directives, "// REMOVE_DEBUG_INFO") != null
}
).toTypedArray()
destination = compilation.destinationDir.canonicalPath
noSourceDebugExtension = InTextDirectivesUtils.findStringWithPrefixes(directives, "// NO_SOURCE_DEBUG_EXTENSION") != null
@@ -55,6 +55,26 @@ public class CompareJvmAbiTestGenerated extends AbstractCompareJvmAbiTest {
runTest("plugins/jvm-abi-gen/testData/compare/constant/");
}
@TestMetadata("debugInfoLineNumberTable")
public void testDebugInfoLineNumberTable() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/debugInfoLineNumberTable/");
}
@TestMetadata("debugInfoLocalVariableTable")
public void testDebugInfoLocalVariableTable() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/debugInfoLocalVariableTable/");
}
@TestMetadata("debugInfoSourceDebugExtension")
public void testDebugInfoSourceDebugExtension() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/debugInfoSourceDebugExtension/");
}
@TestMetadata("debugInfoSourceFile")
public void testDebugInfoSourceFile() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/debugInfoSourceFile/");
}
@TestMetadata("declarationOrderInline")
public void testDeclarationOrderInline() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/declarationOrderInline/");
@@ -105,6 +125,11 @@ public class CompareJvmAbiTestGenerated extends AbstractCompareJvmAbiTest {
runTest("plugins/jvm-abi-gen/testData/compare/privateTypealias/");
}
@TestMetadata("removeDebugInfo")
public void testRemoveDebugInfo() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/removeDebugInfo/");
}
@TestMetadata("returnType")
public void testReturnType() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/returnType/");
@@ -0,0 +1,7 @@
package test
inline fun sum(x: Int, y: Int): Int {
// Comment before local variable
val result = x + y
return result
}
@@ -0,0 +1,7 @@
package test
inline fun sum(x: Int, y: Int): Int {
val result = x + y
// Comment after local variable
return result
}
@@ -0,0 +1,6 @@
package test
inline fun sum(x: Int, y: Int): Int {
val result = x + y
return result
}
@@ -0,0 +1,6 @@
package test
inline fun sum(x: Int, y: Int): Int {
val resultResult = x + y
return resultResult
}
@@ -0,0 +1,6 @@
package test
fun <T> forEach0(list: List<T>, block: (T) -> Unit) {
// Comment before the function
list.forEach(block)
}
@@ -0,0 +1,6 @@
package test
fun <T> forEach0(list: List<T>, block: (T) -> Unit) {
list.forEach(block)
// Comment after the function
}
@@ -0,0 +1,3 @@
package test
class C
@@ -0,0 +1,3 @@
package test
class C
@@ -0,0 +1,7 @@
package test
inline fun <T> forEach0(list: List<T>, block: (T) -> Unit): List<T> {
val list1 = list
list1.forEach(block)
return list1
}
@@ -0,0 +1,3 @@
package test
class C
@@ -0,0 +1 @@
// REMOVE_DEBUG_INFO
@@ -0,0 +1,13 @@
package test
inline fun <T> forEach0(list: List<T>, block: (T) -> Unit): List<T> {
val list2 = list
list2.forEach(block)
return list2
}
@@ -0,0 +1,3 @@
package test
class C