jvm-abi-gen: Remove effectively private classes from ABI

#KT-64590 Fixed
This commit is contained in:
Vladimir Tagakov
2023-12-26 18:11:39 -07:00
committed by Space Team
parent 7cf793f308
commit 11d3ead975
50 changed files with 511 additions and 56 deletions
@@ -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<*>)
@@ -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()) {
@@ -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/");
@@ -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/");
@@ -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
@@ -0,0 +1,7 @@
package test
class SomeClass {
private interface PrivateInterface {
fun d() = "Default implementation of removed interface should not affect abi."
}
}
@@ -0,0 +1 @@
// REMOVE_PRIVATE_CLASSES
@@ -0,0 +1,7 @@
package test
class SomeClass {
private interface PrivateInterface {
fun d(): String
}
}
@@ -0,0 +1,9 @@
package test
class Outer {
class Public : NestedClass.I
private class NestedClass {
interface I
}
}
@@ -0,0 +1 @@
// REMOVE_PRIVATE_CLASSES
@@ -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
@@ -0,0 +1,5 @@
package test
class Class
private class PrivateClass
@@ -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"
@@ -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,2 @@
// INHERIT_MULTIFILE_PARTS
// REMOVE_PRIVATE_CLASSES
@@ -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 @@
// REMOVE_PRIVATE_CLASSES
@@ -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,7 @@
package app
import lib.*
fun runAppAndReturnOk(): String {
return Outer.Public.result()
}
@@ -0,0 +1 @@
// REMOVE_PRIVATE_CLASSES
@@ -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"
}
}
}
@@ -0,0 +1,7 @@
package test
class Outer {
private object Annotations {
annotation class EffectivelyPrivateAnnotation
}
}
@@ -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
}