jvm-abi-gen: Remove effectively private classes from ABI
#KT-64590 Fixed
This commit is contained in:
committed by
Space Team
parent
7cf793f308
commit
11d3ead975
@@ -6,19 +6,17 @@
|
||||
package org.jetbrains.kotlin.ir.overrides
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.types.getClass
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
|
||||
fun IrDeclarationWithVisibility.isEffectivelyPrivate(): Boolean {
|
||||
fun DescriptorVisibility.isNonPrivate(): Boolean =
|
||||
this == DescriptorVisibilities.PUBLIC
|
||||
|| this == DescriptorVisibilities.PROTECTED
|
||||
|| this == DescriptorVisibilities.INTERNAL
|
||||
val isNonPrivate = visibility == DescriptorVisibilities.PUBLIC
|
||||
|| visibility == DescriptorVisibilities.PROTECTED
|
||||
|| visibility == DescriptorVisibilities.INTERNAL
|
||||
|
||||
return when {
|
||||
visibility.isNonPrivate() -> parentClassOrNull?.isEffectivelyPrivate() ?: false
|
||||
isNonPrivate -> parentClassOrNull?.isEffectivelyPrivate() ?: false
|
||||
|
||||
visibility == DescriptorVisibilities.INVISIBLE_FAKE -> {
|
||||
val overridesOnlyPrivateDeclarations = (this as? IrOverridableDeclaration<*>)
|
||||
|
||||
+42
-15
@@ -12,7 +12,9 @@ import org.jetbrains.kotlin.codegen.`when`.WhenByEnumsMapping
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.overrides.isEffectivelyPrivate
|
||||
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
|
||||
import org.jetbrains.kotlin.ir.util.isFileClass
|
||||
import org.jetbrains.kotlin.ir.util.primaryConstructor
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||
@@ -28,7 +30,8 @@ enum class AbiMethodInfo {
|
||||
|
||||
sealed class AbiClassInfo {
|
||||
object Public : AbiClassInfo()
|
||||
class Stripped(val methodInfo: Map<Method, AbiMethodInfo>) : AbiClassInfo()
|
||||
class Stripped(val methodInfo: Map<Method, AbiMethodInfo>, val prune: Boolean = false) : AbiClassInfo()
|
||||
object Deleted : AbiClassInfo()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,8 +61,15 @@ sealed class AbiClassInfo {
|
||||
*/
|
||||
class JvmAbiClassBuilderInterceptor(
|
||||
private val removeDataClassCopyIfConstructorIsPrivate: Boolean,
|
||||
private val removePrivateClasses: Boolean,
|
||||
) : ClassGeneratorExtension {
|
||||
val abiClassInfo: MutableMap<String, AbiClassInfo> = mutableMapOf()
|
||||
private var abiClassInfoBuilder = JvmAbiClassInfoBuilder(removePrivateClasses)
|
||||
|
||||
fun buildAbiClassInfoAndReleaseResources(): Map<String, AbiClassInfo> {
|
||||
return abiClassInfoBuilder.buildClassInfo().also {
|
||||
abiClassInfoBuilder = JvmAbiClassInfoBuilder(removePrivateClasses)
|
||||
}
|
||||
}
|
||||
|
||||
override fun generateClass(generator: ClassGenerator, declaration: IrClass?): ClassGenerator =
|
||||
AbiInfoClassGenerator(generator, declaration)
|
||||
@@ -70,14 +80,16 @@ class JvmAbiClassBuilderInterceptor(
|
||||
) : ClassGenerator by delegate {
|
||||
private val isPrivateClass = irClass != null && DescriptorVisibilities.isPrivate(irClass.visibility)
|
||||
private val isDataClass = irClass != null && irClass.isData
|
||||
private val removeClassFromAbi = shouldRemoveFromAbi(irClass, removePrivateClasses)
|
||||
|
||||
@OptIn(UnsafeDuringIrConstructionAPI::class)
|
||||
private val primaryConstructorIsNotInAbi =
|
||||
irClass?.primaryConstructor?.visibility?.let(DescriptorVisibilities::isPrivate) == true
|
||||
|
||||
lateinit var internalName: String
|
||||
lateinit var superInterfaces: List<String>
|
||||
var localOrAnonymousClass = false
|
||||
var publicAbi = false
|
||||
var keepClassAsIs = false
|
||||
val methodInfos = mutableMapOf<Method, AbiMethodInfo>()
|
||||
val maskedMethods = mutableSetOf<Method>() // Methods which should be stripped even if they are marked as KEEP
|
||||
|
||||
@@ -86,11 +98,10 @@ class JvmAbiClassBuilderInterceptor(
|
||||
) {
|
||||
// Always keep annotation classes
|
||||
// TODO: Investigate whether there are cases where we can remove annotation classes from the ABI.
|
||||
if (access and Opcodes.ACC_ANNOTATION != 0) {
|
||||
publicAbi = true
|
||||
}
|
||||
keepClassAsIs = keepClassAsIs || access and Opcodes.ACC_ANNOTATION != 0
|
||||
|
||||
internalName = name
|
||||
superInterfaces = interfaces.asList()
|
||||
delegate.defineClass(version, access, name, signature, superName, interfaces)
|
||||
}
|
||||
|
||||
@@ -102,7 +113,8 @@ class JvmAbiClassBuilderInterceptor(
|
||||
override fun newMethod(
|
||||
declaration: IrFunction?, access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
|
||||
): MethodVisitor {
|
||||
if (publicAbi) {
|
||||
if (keepClassAsIs || removeClassFromAbi) {
|
||||
// We don't care about methods when we remove or keep this class completely.
|
||||
return delegate.newMethod(declaration, access, name, desc, signature, exceptions)
|
||||
}
|
||||
|
||||
@@ -146,32 +158,40 @@ class JvmAbiClassBuilderInterceptor(
|
||||
// Parse the public ABI flag from the Kotlin metadata annotation
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor {
|
||||
val delegate = delegate.visitAnnotation(desc, visible)
|
||||
if (publicAbi || desc != JvmAnnotationNames.METADATA_DESC)
|
||||
if (keepClassAsIs || desc != JvmAnnotationNames.METADATA_DESC)
|
||||
return delegate
|
||||
|
||||
return object : AnnotationVisitor(Opcodes.API_VERSION, delegate) {
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
if ((name == JvmAnnotationNames.METADATA_EXTRA_INT_FIELD_NAME) && (value is Int)) {
|
||||
publicAbi = publicAbi || value and JvmAnnotationNames.METADATA_PUBLIC_ABI_FLAG != 0
|
||||
keepClassAsIs = keepClassAsIs || value and JvmAnnotationNames.METADATA_PUBLIC_ABI_FLAG != 0
|
||||
}
|
||||
super.visit(name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
|
||||
abiClassInfoBuilder.addInnerClass(name, outerName)
|
||||
delegate.visitInnerClass(name, outerName, innerName, access)
|
||||
}
|
||||
|
||||
override fun done(generateSmapCopyToAnnotation: Boolean) {
|
||||
// Remove local or anonymous classes unless they are in the scope of an inline function and
|
||||
// strip non-inline methods from all other classes.
|
||||
when {
|
||||
publicAbi ->
|
||||
abiClassInfo[internalName] = AbiClassInfo.Public
|
||||
!localOrAnonymousClass && !isWhenMappingClass -> {
|
||||
val classInfo = when {
|
||||
keepClassAsIs -> AbiClassInfo.Public
|
||||
removeClassFromAbi -> AbiClassInfo.Deleted
|
||||
localOrAnonymousClass -> AbiClassInfo.Deleted
|
||||
isWhenMappingClass -> AbiClassInfo.Deleted
|
||||
else -> {
|
||||
for (method in maskedMethods) {
|
||||
methodInfos.replace(method, AbiMethodInfo.STRIP)
|
||||
methodInfos[method] = AbiMethodInfo.STRIP
|
||||
}
|
||||
abiClassInfo[internalName] = AbiClassInfo.Stripped(methodInfos)
|
||||
AbiClassInfo.Stripped(methodInfos)
|
||||
}
|
||||
}
|
||||
abiClassInfoBuilder.recordInitialClassInfo(internalName, classInfo, superInterfaces)
|
||||
delegate.done(generateSmapCopyToAnnotation)
|
||||
}
|
||||
|
||||
@@ -179,3 +199,10 @@ class JvmAbiClassBuilderInterceptor(
|
||||
get() = internalName.endsWith(WhenByEnumsMapping.MAPPINGS_CLASS_NAME_POSTFIX)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldRemoveFromAbi(irClass: IrClass?, removePrivateClasses: Boolean): Boolean = when {
|
||||
irClass == null -> false
|
||||
irClass.isFileClass -> false
|
||||
removePrivateClasses -> irClass.isEffectivelyPrivate()
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2024 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 org.jetbrains.kotlin.jvm.abi
|
||||
|
||||
/**
|
||||
* Collects information about kept/stripped/deleted classes, and then adapts it so that it would be consistent if private interfaces
|
||||
* are present in the module. The latter is done only if [removePrivateClasses] is enabled.
|
||||
*
|
||||
* When using this class, call [recordInitialClassInfo]/[addInnerClass] repeatedly until all necessary data is collected, and then call
|
||||
* [buildClassInfo] to get the resulting class infos.
|
||||
*
|
||||
* Private interface is a special case from the ABI standpoint because it is allowed to expose private interface by inheriting a public
|
||||
* class from it, see KT-20088. This code keeps all private interfaces which are implemented (perhaps indirectly) by at least one public
|
||||
* (in terms of ABI) class in the module. Also, in case such private interface is nested, all its outer classes should be kept but pruned.
|
||||
*/
|
||||
internal class JvmAbiClassInfoBuilder(private val removePrivateClasses: Boolean) {
|
||||
private val abiClassInfo = mutableMapOf<String, AbiClassInfo>()
|
||||
private val innerClassToOuter = mutableMapOf<String, String>()
|
||||
private val allSuperInterfaces = mutableMapOf<String, List<String>>()
|
||||
|
||||
fun recordInitialClassInfo(className: String, info: AbiClassInfo, superInterfaces: List<String>) {
|
||||
abiClassInfo[className] = info
|
||||
if (removePrivateClasses) {
|
||||
allSuperInterfaces[className] = superInterfaces
|
||||
}
|
||||
}
|
||||
|
||||
fun addInnerClass(innerClass: String, outerClass: String?) {
|
||||
if (outerClass == null) return
|
||||
if (removePrivateClasses) {
|
||||
innerClassToOuter.getOrPut(innerClass) { outerClass }
|
||||
}
|
||||
}
|
||||
|
||||
fun buildClassInfo(): Map<String, AbiClassInfo> {
|
||||
if (removePrivateClasses) {
|
||||
val visited = hashSetOf<String>()
|
||||
for ((className, info) in abiClassInfo) if (info != AbiClassInfo.Deleted) {
|
||||
for (superInterface in allSuperInterfaces[className].orEmpty()) {
|
||||
keepInterface(superInterface, visited)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return abiClassInfo
|
||||
}
|
||||
|
||||
private fun keepInterface(className: String, visited: MutableSet<String>) {
|
||||
if (!visited.add(className)) return
|
||||
|
||||
abiClassInfo[className] = AbiClassInfo.Public
|
||||
|
||||
for (outerClass in generateSequence(className, innerClassToOuter::get).drop(1)) {
|
||||
if (abiClassInfo[outerClass] == AbiClassInfo.Deleted) {
|
||||
abiClassInfo[outerClass] = AbiClassInfo.Stripped(emptyMap(), prune = true)
|
||||
}
|
||||
}
|
||||
|
||||
for (superInterface in allSuperInterfaces[className].orEmpty()) {
|
||||
keepInterface(superInterface, visited)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,15 @@ class JvmAbiCommandLineProcessor : CommandLineProcessor {
|
||||
" to keep working. Flipping this to true reduces stability of the ABI.",
|
||||
false,
|
||||
)
|
||||
|
||||
val REMOVE_PRIVATE_CLASSES_OPTION: CliOption =
|
||||
CliOption(
|
||||
"removePrivateClasses",
|
||||
"true/false",
|
||||
"Remove private classes from ABI. False by default due to backwards compatibility. If enabled, " +
|
||||
"private top-level classes will no longer be available from Java classes in the same package.",
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
override val pluginId: String
|
||||
@@ -62,6 +71,7 @@ class JvmAbiCommandLineProcessor : CommandLineProcessor {
|
||||
REMOVE_DEBUG_INFO_OPTION,
|
||||
REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE_OPTION,
|
||||
PRESERVE_DECLARATION_ORDER_OPTION,
|
||||
REMOVE_PRIVATE_CLASSES_OPTION,
|
||||
)
|
||||
|
||||
override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) {
|
||||
@@ -73,6 +83,10 @@ class JvmAbiCommandLineProcessor : CommandLineProcessor {
|
||||
value == "true"
|
||||
)
|
||||
PRESERVE_DECLARATION_ORDER_OPTION -> configuration.put(JvmAbiConfigurationKeys.PRESERVE_DECLARATION_ORDER, value == "true")
|
||||
REMOVE_PRIVATE_CLASSES_OPTION -> configuration.put(
|
||||
JvmAbiConfigurationKeys.REMOVE_PRIVATE_CLASSES,
|
||||
value == "true"
|
||||
)
|
||||
else -> throw CliOptionProcessingException("Unknown option: ${option.optionName}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ class JvmAbiComponentRegistrar : CompilerPluginRegistrar() {
|
||||
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
|
||||
configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
|
||||
val removeDataClassCopy = configuration.getBoolean(JvmAbiConfigurationKeys.REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE)
|
||||
|
||||
val builderExtension = JvmAbiClassBuilderInterceptor(removeDataClassCopy)
|
||||
val builderExtension = JvmAbiClassBuilderInterceptor(
|
||||
removeDataClassCopy,
|
||||
configuration.getBoolean(JvmAbiConfigurationKeys.REMOVE_PRIVATE_CLASSES),
|
||||
)
|
||||
val outputExtension = JvmAbiOutputExtension(
|
||||
File(outputPath), builderExtension.abiClassInfo, messageCollector,
|
||||
configuration.getBoolean(JvmAbiConfigurationKeys.REMOVE_DEBUG_INFO),
|
||||
File(outputPath), builderExtension::buildAbiClassInfoAndReleaseResources,
|
||||
messageCollector, configuration.getBoolean(JvmAbiConfigurationKeys.REMOVE_DEBUG_INFO),
|
||||
removeDataClassCopy,
|
||||
configuration.getBoolean(JvmAbiConfigurationKeys.PRESERVE_DECLARATION_ORDER),
|
||||
)
|
||||
|
||||
@@ -16,4 +16,6 @@ object JvmAbiConfigurationKeys {
|
||||
CompilerConfigurationKey.create(JvmAbiCommandLineProcessor.REMOVE_DATA_CLASS_COPY_IF_CONSTRUCTOR_IS_PRIVATE_OPTION.description)
|
||||
val PRESERVE_DECLARATION_ORDER: CompilerConfigurationKey<Boolean> =
|
||||
CompilerConfigurationKey.create(JvmAbiCommandLineProcessor.PRESERVE_DECLARATION_ORDER_OPTION.description)
|
||||
val REMOVE_PRIVATE_CLASSES: CompilerConfigurationKey<Boolean> =
|
||||
CompilerConfigurationKey.create(JvmAbiCommandLineProcessor.REMOVE_PRIVATE_CLASSES_OPTION.description)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ fun abiMetadataProcessor(
|
||||
annotationVisitor: AnnotationVisitor,
|
||||
removeDataClassCopyIfConstructorIsPrivate: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
classesToBeDeleted: Set<String>,
|
||||
pruneClass: Boolean,
|
||||
): AnnotationVisitor =
|
||||
kotlinClassHeaderVisitor { header ->
|
||||
// kotlinx-metadata only supports writing Kotlin metadata of version >= 1.4, so we need to
|
||||
@@ -33,13 +35,18 @@ fun abiMetadataProcessor(
|
||||
KotlinClassMetadata.transform(header) { metadata ->
|
||||
when (metadata) {
|
||||
is KotlinClassMetadata.Class -> {
|
||||
metadata.kmClass.removePrivateDeclarations(removeDataClassCopyIfConstructorIsPrivate, preserveDeclarationOrder)
|
||||
metadata.kmClass.removePrivateDeclarations(
|
||||
removeDataClassCopyIfConstructorIsPrivate,
|
||||
preserveDeclarationOrder,
|
||||
classesToBeDeleted,
|
||||
pruneClass
|
||||
)
|
||||
}
|
||||
is KotlinClassMetadata.FileFacade -> {
|
||||
metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder)
|
||||
metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder, pruneClass)
|
||||
}
|
||||
is KotlinClassMetadata.MultiFileClassPart -> {
|
||||
metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder)
|
||||
metadata.kmPackage.removePrivateDeclarations(preserveDeclarationOrder, pruneClass)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
@@ -150,22 +157,37 @@ private fun AnnotationVisitor.visitKotlinMetadata(header: Metadata) {
|
||||
visitEnd()
|
||||
}
|
||||
|
||||
private fun KmClass.removePrivateDeclarations(removeCopyAlongWithConstructor: Boolean, preserveDeclarationOrder: Boolean) {
|
||||
constructors.removeIf { it.visibility.isPrivate }
|
||||
(this as KmDeclarationContainer).removePrivateDeclarations(copyFunShouldBeDeleted(removeCopyAlongWithConstructor), preserveDeclarationOrder)
|
||||
private fun KmClass.removePrivateDeclarations(
|
||||
removeCopyAlongWithConstructor: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
classesToBeDeleted: Set<String>,
|
||||
pruneClass: Boolean,
|
||||
) {
|
||||
constructors.removeIf { pruneClass || it.visibility.isPrivate }
|
||||
(this as KmDeclarationContainer).removePrivateDeclarations(
|
||||
copyFunShouldBeDeleted(removeCopyAlongWithConstructor),
|
||||
preserveDeclarationOrder,
|
||||
pruneClass,
|
||||
)
|
||||
nestedClasses.removeIf { "$name\$$it" in classesToBeDeleted }
|
||||
companionObject = companionObject.takeUnless { "$name\$$it" in classesToBeDeleted }
|
||||
localDelegatedProperties.clear()
|
||||
// TODO: do not serialize private type aliases once KT-17229 is fixed.
|
||||
}
|
||||
|
||||
private fun KmPackage.removePrivateDeclarations(preserveDeclarationOrder: Boolean) {
|
||||
(this as KmDeclarationContainer).removePrivateDeclarations(false, preserveDeclarationOrder)
|
||||
private fun KmPackage.removePrivateDeclarations(preserveDeclarationOrder: Boolean, pruneClass: Boolean) {
|
||||
(this as KmDeclarationContainer).removePrivateDeclarations(false, preserveDeclarationOrder, pruneClass)
|
||||
localDelegatedProperties.clear()
|
||||
// TODO: do not serialize private type aliases once KT-17229 is fixed.
|
||||
}
|
||||
|
||||
private fun KmDeclarationContainer.removePrivateDeclarations(copyFunShouldBeDeleted: Boolean, preserveDeclarationOrder: Boolean) {
|
||||
functions.removeIf { it.visibility.isPrivate || (copyFunShouldBeDeleted && it.name == "copy") }
|
||||
properties.removeIf { it.visibility.isPrivate }
|
||||
private fun KmDeclarationContainer.removePrivateDeclarations(
|
||||
copyFunShouldBeDeleted: Boolean,
|
||||
preserveDeclarationOrder: Boolean,
|
||||
pruneClass: Boolean,
|
||||
) {
|
||||
functions.removeIf { pruneClass || it.visibility.isPrivate || (copyFunShouldBeDeleted && it.name == "copy") }
|
||||
properties.removeIf { pruneClass || it.visibility.isPrivate }
|
||||
|
||||
if (!preserveDeclarationOrder) {
|
||||
functions.sortWith(compareBy(KmFunction::name, { it.signature.toString() }))
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.io.File
|
||||
|
||||
class JvmAbiOutputExtension(
|
||||
private val outputPath: File,
|
||||
private val abiClassInfos: Map<String, AbiClassInfo>,
|
||||
private val abiClassInfoBuilder: () -> Map<String, AbiClassInfo>,
|
||||
private val messageCollector: MessageCollector,
|
||||
private val removeDebugInfo: Boolean,
|
||||
private val removeDataClassCopyIfConstructorIsPrivate: Boolean,
|
||||
@@ -34,7 +34,7 @@ class JvmAbiOutputExtension(
|
||||
// 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, removeDebugInfo, removeDataClassCopyIfConstructorIsPrivate, preserveDeclarationOrder)
|
||||
AbiOutputFiles(abiClassInfoBuilder(), factory, removeDebugInfo, removeDataClassCopyIfConstructorIsPrivate, preserveDeclarationOrder)
|
||||
if (outputPath.extension == "jar") {
|
||||
// We don't include the runtime or main class in interface jars and always reset time stamps.
|
||||
CompileEnvironmentUtil.writeToJar(
|
||||
@@ -60,6 +60,10 @@ class JvmAbiOutputExtension(
|
||||
val removeCopyAlongWithConstructor: Boolean,
|
||||
val preserveDeclarationOrder: Boolean,
|
||||
) : OutputFileCollection {
|
||||
private val classesToBeDeleted = abiClassInfos.mapNotNullTo(mutableSetOf()) { (className, action) ->
|
||||
className.takeIf { action == AbiClassInfo.Deleted }
|
||||
}
|
||||
|
||||
override fun get(relativePath: String): OutputFile? {
|
||||
error("AbiOutputFiles does not implement `get`.")
|
||||
}
|
||||
@@ -70,19 +74,15 @@ class JvmAbiOutputExtension(
|
||||
}.sortedBy { it.relativePath }
|
||||
|
||||
val classFiles = abiClassInfos.keys.sorted().mapNotNull { internalName ->
|
||||
val outputFile = outputFiles.get("$internalName.class")
|
||||
val abiInfo = abiClassInfos.getValue(internalName)
|
||||
when {
|
||||
// Note that outputFile may be null, e.g., for empty $DefaultImpls classes in the JVM backend.
|
||||
outputFile == null ->
|
||||
null
|
||||
// Note that outputFile may be null, e.g., for empty $DefaultImpls classes in the JVM backend.
|
||||
val outputFile = outputFiles.get("$internalName.class") ?: return@mapNotNull null
|
||||
|
||||
abiInfo is AbiClassInfo.Public ->
|
||||
// Copy verbatim
|
||||
outputFile
|
||||
|
||||
else -> /* abiInfo is AbiClassInfo.Stripped */ {
|
||||
val methodInfo = (abiInfo as AbiClassInfo.Stripped).methodInfo
|
||||
when (val abiInfo = abiClassInfos.getValue(internalName)) {
|
||||
is AbiClassInfo.Deleted -> null
|
||||
is AbiClassInfo.Public -> outputFile // Copy verbatim
|
||||
is AbiClassInfo.Stripped -> {
|
||||
val prune = abiInfo.prune
|
||||
val methodInfo = abiInfo.methodInfo
|
||||
val innerClassesToKeep = mutableSetOf<String>()
|
||||
val noMethodsKept = methodInfo.values.none { it == AbiMethodInfo.KEEP }
|
||||
val writer = ClassWriter(0)
|
||||
@@ -101,9 +101,9 @@ class JvmAbiOutputExtension(
|
||||
name: String?,
|
||||
descriptor: String?,
|
||||
signature: String?,
|
||||
value: Any?
|
||||
value: Any?,
|
||||
): FieldVisitor? {
|
||||
if (access and Opcodes.ACC_PRIVATE != 0)
|
||||
if (prune || access and Opcodes.ACC_PRIVATE != 0)
|
||||
return null
|
||||
return FieldNode(access, name, descriptor, signature, value).also {
|
||||
keptFields += it
|
||||
@@ -117,6 +117,7 @@ class JvmAbiOutputExtension(
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
): MethodVisitor? {
|
||||
if (prune) return null
|
||||
val info = methodInfo[Method(name, descriptor)] ?: return null
|
||||
|
||||
val node = MethodNode(access, name, descriptor, signature, exceptions).also {
|
||||
@@ -133,7 +134,7 @@ class JvmAbiOutputExtension(
|
||||
override fun visitSource(source: String?, debug: String?) {
|
||||
when {
|
||||
removeDebugInfo -> super.visitSource(null, null)
|
||||
noMethodsKept -> {
|
||||
prune || noMethodsKept -> {
|
||||
// Strip SourceDebugExtension attribute if there are no inline functions.
|
||||
super.visitSource(source, null)
|
||||
}
|
||||
@@ -151,14 +152,20 @@ 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) return null
|
||||
if (removeDebugInfo || noMethodsKept || prune) 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, removeCopyAlongWithConstructor, preserveDeclarationOrder)
|
||||
return abiMetadataProcessor(
|
||||
delegate,
|
||||
removeCopyAlongWithConstructor,
|
||||
preserveDeclarationOrder,
|
||||
classesToBeDeleted,
|
||||
prune
|
||||
)
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
@@ -200,7 +207,9 @@ class JvmAbiOutputExtension(
|
||||
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) }
|
||||
innerClassesByOuterName[next]?.mapNotNullTo(stack) { info ->
|
||||
info.name.takeUnless { abiClassInfos[it] == AbiClassInfo.Deleted }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,9 @@ abstract class BaseJvmAbiTest : TestCase() {
|
||||
abiOption(JvmAbiCommandLineProcessor.PRESERVE_DECLARATION_ORDER_OPTION.optionName, true.toString()).takeIf {
|
||||
InTextDirectivesUtils.findStringWithPrefixes(directives, "// PRESERVE_DECLARATION_ORDER") != null
|
||||
},
|
||||
abiOption(JvmAbiCommandLineProcessor.REMOVE_PRIVATE_CLASSES_OPTION.optionName, true.toString()).takeIf {
|
||||
InTextDirectivesUtils.findStringWithPrefixes(directives, "// REMOVE_PRIVATE_CLASSES") != null
|
||||
},
|
||||
).toTypedArray()
|
||||
destination = compilation.destinationDir.canonicalPath
|
||||
noSourceDebugExtension = InTextDirectivesUtils.findStringWithPrefixes(directives, "// NO_SOURCE_DEBUG_EXTENSION") != null
|
||||
@@ -95,6 +98,9 @@ abstract class BaseJvmAbiTest : TestCase() {
|
||||
if (InTextDirectivesUtils.findStringWithPrefixes(directives, "// USE_K2") != null) {
|
||||
useK2 = true
|
||||
}
|
||||
if (InTextDirectivesUtils.findStringWithPrefixes(directives, "// INHERIT_MULTIFILE_PARTS") != null) {
|
||||
inheritMultifileParts = true
|
||||
}
|
||||
}
|
||||
val exitCode = compiler.exec(messageCollector, Services.EMPTY, args)
|
||||
if (exitCode != ExitCode.OK || messageCollector.errors.isNotEmpty()) {
|
||||
|
||||
+30
@@ -140,6 +140,16 @@ public class CompareJvmAbiTestGenerated extends AbstractCompareJvmAbiTest {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/lambdas/");
|
||||
}
|
||||
|
||||
@TestMetadata("multifileClass")
|
||||
public void testMultifileClass() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/multifileClass/");
|
||||
}
|
||||
|
||||
@TestMetadata("nestedPrivateClasses")
|
||||
public void testNestedPrivateClasses() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/nestedPrivateClasses/");
|
||||
}
|
||||
|
||||
@TestMetadata("parameterName")
|
||||
public void testParameterName() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/parameterName/");
|
||||
@@ -150,6 +160,26 @@ public class CompareJvmAbiTestGenerated extends AbstractCompareJvmAbiTest {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/preserveDeclarationOrder/");
|
||||
}
|
||||
|
||||
@TestMetadata("privateInterfaceDefaultImplementation")
|
||||
public void testPrivateInterfaceDefaultImplementation() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/privateInterfaceDefaultImplementation/");
|
||||
}
|
||||
|
||||
@TestMetadata("privateOuterClassForEffectivelyVisibleInterface")
|
||||
public void testPrivateOuterClassForEffectivelyVisibleInterface() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/privateOuterClassForEffectivelyVisibleInterface/");
|
||||
}
|
||||
|
||||
@TestMetadata("privateTopLevelClasses")
|
||||
public void testPrivateTopLevelClasses() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/privateTopLevelClasses/");
|
||||
}
|
||||
|
||||
@TestMetadata("privateTopLevelClassesWithoutOption")
|
||||
public void testPrivateTopLevelClassesWithoutOption() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/privateTopLevelClassesWithoutOption/");
|
||||
}
|
||||
|
||||
@TestMetadata("privateTypealias")
|
||||
public void testPrivateTypealias() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compare/privateTypealias/");
|
||||
|
||||
Generated
+30
@@ -45,6 +45,11 @@ public class CompileAgainstJvmAbiTestGenerated extends AbstractCompileAgainstJvm
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/clinit/");
|
||||
}
|
||||
|
||||
@TestMetadata("conflictingClasses")
|
||||
public void testConflictingClasses() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/conflictingClasses/");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineAnnotationInstantiation")
|
||||
public void testInlineAnnotationInstantiation() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/inlineAnnotationInstantiation/");
|
||||
@@ -100,6 +105,26 @@ public class CompileAgainstJvmAbiTestGenerated extends AbstractCompileAgainstJvm
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/kt-40340/");
|
||||
}
|
||||
|
||||
@TestMetadata("multifileClass")
|
||||
public void testMultifileClass() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/multifileClass/");
|
||||
}
|
||||
|
||||
@TestMetadata("multifileClassOptimized")
|
||||
public void testMultifileClassOptimized() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/multifileClassOptimized/");
|
||||
}
|
||||
|
||||
@TestMetadata("multifileClassOptimizedWithRemovePrivateOption")
|
||||
public void testMultifileClassOptimizedWithRemovePrivateOption() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/multifileClassOptimizedWithRemovePrivateOption/");
|
||||
}
|
||||
|
||||
@TestMetadata("multifileClassWithRemovePrivateOption")
|
||||
public void testMultifileClassWithRemovePrivateOption() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/multifileClassWithRemovePrivateOption/");
|
||||
}
|
||||
|
||||
@TestMetadata("privateAnnotationsFromJavaApp")
|
||||
public void testPrivateAnnotationsFromJavaApp() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/privateAnnotationsFromJavaApp/");
|
||||
@@ -120,6 +145,11 @@ public class CompileAgainstJvmAbiTestGenerated extends AbstractCompileAgainstJvm
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/privateClassesFromJavaLib/");
|
||||
}
|
||||
|
||||
@TestMetadata("privateInterfaceImplementedByPublicClass")
|
||||
public void testPrivateInterfaceImplementedByPublicClass() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/privateInterfaceImplementedByPublicClass/");
|
||||
}
|
||||
|
||||
@TestMetadata("privateOnlyConstructors")
|
||||
public void testPrivateOnlyConstructors() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/compile/privateOnlyConstructors/");
|
||||
|
||||
+5
@@ -55,6 +55,11 @@ public class JvmAbiContentTestGenerated extends AbstractJvmAbiContentTest {
|
||||
runTest("plugins/jvm-abi-gen/testData/content/class/");
|
||||
}
|
||||
|
||||
@TestMetadata("effectivelyPrivateAnnotation")
|
||||
public void testEffectivelyPrivateAnnotation() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/content/effectivelyPrivateAnnotation/");
|
||||
}
|
||||
|
||||
@TestMetadata("innerClasses")
|
||||
public void testInnerClasses() throws Exception {
|
||||
runTest("plugins/jvm-abi-gen/testData/content/innerClasses/");
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package test
|
||||
|
||||
fun f() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package test
|
||||
|
||||
// Removal of function from a multi-file class should result in a different ABI even though the multi-file part class is package-private.
|
||||
@@ -0,0 +1,9 @@
|
||||
package test
|
||||
|
||||
class Class {
|
||||
private companion object {}
|
||||
private class PrivateClass {
|
||||
companion object {}
|
||||
class NestedClass
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// REMOVE_PRIVATE_CLASSES
|
||||
@@ -0,0 +1,3 @@
|
||||
package test
|
||||
|
||||
class Class
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package test
|
||||
|
||||
class SomeClass {
|
||||
private interface PrivateInterface {
|
||||
fun d() = "Default implementation of removed interface should not affect abi."
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
// REMOVE_PRIVATE_CLASSES
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package test
|
||||
|
||||
class SomeClass {
|
||||
private interface PrivateInterface {
|
||||
fun d(): String
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
package test
|
||||
|
||||
class Outer {
|
||||
class Public : NestedClass.I
|
||||
|
||||
private class NestedClass {
|
||||
interface I
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
// REMOVE_PRIVATE_CLASSES
|
||||
plugins/jvm-abi-gen/testData/compare/privateOuterClassForEffectivelyVisibleInterface/sameAbi/test.kt
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
package test
|
||||
|
||||
class Outer {
|
||||
class Public : NestedClass.I
|
||||
|
||||
private class NestedClass {
|
||||
interface I
|
||||
|
||||
fun someFun() = Unit
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package test
|
||||
|
||||
class Class
|
||||
|
||||
private class PrivateClass
|
||||
@@ -0,0 +1 @@
|
||||
// REMOVE_PRIVATE_CLASSES
|
||||
@@ -0,0 +1,3 @@
|
||||
package test
|
||||
|
||||
class Class
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package test
|
||||
|
||||
class Class
|
||||
|
||||
private class PrivateClass
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
package test
|
||||
|
||||
class Class
|
||||
@@ -0,0 +1,5 @@
|
||||
package app
|
||||
|
||||
//UnexpectedInnerClass although declared in a separate module
|
||||
//appear to be an inner class of the class generated from this file
|
||||
fun runAppAndReturnOk() = AppKt.UnexpectedInnerClass.result()
|
||||
@@ -0,0 +1,7 @@
|
||||
package app
|
||||
|
||||
class AppKt {
|
||||
object UnexpectedInnerClass {
|
||||
fun result() = "OK"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package app
|
||||
|
||||
import lib.*
|
||||
|
||||
fun runAppAndReturnOk(): String {
|
||||
f1()
|
||||
if (v1 != "OK") return "Fail 1"
|
||||
f2()
|
||||
if (v2 != "OK") return "Fail 2"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package lib
|
||||
|
||||
typealias T1 = String
|
||||
fun f1() {}
|
||||
val v1: T1 = "OK"
|
||||
@@ -0,0 +1,7 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package lib
|
||||
|
||||
typealias T2 = String
|
||||
fun f2() {}
|
||||
val v2: T2 = "OK"
|
||||
@@ -0,0 +1,12 @@
|
||||
package app
|
||||
|
||||
import lib.*
|
||||
|
||||
fun runAppAndReturnOk(): String {
|
||||
f1()
|
||||
if (v1 != "OK") return "Fail 1"
|
||||
f2()
|
||||
if (v2 != "OK") return "Fail 2"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// INHERIT_MULTIFILE_PARTS
|
||||
@@ -0,0 +1,7 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package lib
|
||||
|
||||
typealias T1 = String
|
||||
fun f1() {}
|
||||
val v1: T1 = "OK"
|
||||
@@ -0,0 +1,7 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package lib
|
||||
|
||||
typealias T2 = String
|
||||
fun f2() {}
|
||||
val v2: T2 = "OK"
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package app
|
||||
|
||||
import lib.*
|
||||
|
||||
fun runAppAndReturnOk(): String {
|
||||
f1()
|
||||
if (v1 != "OK") return "Fail 1"
|
||||
f2()
|
||||
if (v2 != "OK") return "Fail 2"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
// INHERIT_MULTIFILE_PARTS
|
||||
// REMOVE_PRIVATE_CLASSES
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package lib
|
||||
|
||||
typealias T1 = String
|
||||
fun f1() {}
|
||||
val v1: T1 = "OK"
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package lib
|
||||
|
||||
typealias T2 = String
|
||||
fun f2() {}
|
||||
val v2: T2 = "OK"
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package app
|
||||
|
||||
import lib.*
|
||||
|
||||
fun runAppAndReturnOk(): String {
|
||||
f1()
|
||||
if (v1 != "OK") return "Fail 1"
|
||||
f2()
|
||||
if (v2 != "OK") return "Fail 2"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
// REMOVE_PRIVATE_CLASSES
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package lib
|
||||
|
||||
typealias T1 = String
|
||||
fun f1() {}
|
||||
val v1: T1 = "OK"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("Multifile")
|
||||
package lib
|
||||
|
||||
typealias T2 = String
|
||||
fun f2() {}
|
||||
val v2: T2 = "OK"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package app
|
||||
|
||||
import lib.*
|
||||
|
||||
fun runAppAndReturnOk(): String {
|
||||
return Outer.Public.result()
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
// REMOVE_PRIVATE_CLASSES
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package lib
|
||||
|
||||
class Outer {
|
||||
object Public : Private
|
||||
|
||||
private interface Private: Secret.EffectivelyPrivate
|
||||
|
||||
private object Secret {
|
||||
interface EffectivelyPrivate {
|
||||
fun result(): String = "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package test
|
||||
|
||||
class Outer {
|
||||
private object Annotations {
|
||||
annotation class EffectivelyPrivateAnnotation
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
@java.lang.annotation.Retention(value=RUNTIME)
|
||||
@kotlin.Metadata
|
||||
public annotation class test/Outer$Annotations$EffectivelyPrivateAnnotation {
|
||||
// source: 'annotation.kt'
|
||||
private final inner class test/Outer$Annotations
|
||||
public inner class test/Outer$Annotations$EffectivelyPrivateAnnotation
|
||||
}
|
||||
@kotlin.Metadata
|
||||
final class test/Outer$Annotations {
|
||||
// source: 'annotation.kt'
|
||||
private final inner class test/Outer$Annotations
|
||||
public inner class test/Outer$Annotations$EffectivelyPrivateAnnotation
|
||||
public final static @org.jetbrains.annotations.NotNull field INSTANCE: test.Outer$Annotations
|
||||
}
|
||||
@kotlin.Metadata
|
||||
public final class test/Outer {
|
||||
// source: 'annotation.kt'
|
||||
private final inner class test/Outer$Annotations
|
||||
public method <init>(): void
|
||||
}
|
||||
Reference in New Issue
Block a user