Exclude private members from ABI classes

This commit is contained in:
Alexey Tsvetkov
2018-11-29 19:46:16 +03:00
parent 33faa00184
commit 903f6bc79b
17 changed files with 291 additions and 12 deletions
@@ -40,7 +40,16 @@ public class ClassBuilderMode {
* Full function bodies
*/
public final static ClassBuilderMode FULL = new ClassBuilderMode(
/* bodies = */ true,
/* bodies = */ true,
/* metadata = */ true,
/* sourceRetention = */ false,
/* generateMultiFileFacadePartClasses = */ true);
/**
* ABI for compilation (non-private signatures + inline function bodies)
*/
public final static ClassBuilderMode ABI = new ClassBuilderMode(
/* bodies = */ true,
/* metadata = */ true,
/* sourceRetention = */ false,
/* generateMultiFileFacadePartClasses = */ true);
@@ -245,7 +245,9 @@ public class ClosureCodegen extends MemberCodegen<KtElement> {
DescriptorSerializer serializer =
DescriptorSerializer.createForLambda(new JvmSerializerExtension(v.getSerializationBindings(), state));
ProtoBuf.Function functionProto = serializer.functionProto(freeLambdaDescriptor).build();
ProtoBuf.Function.Builder builder = serializer.functionProto(freeLambdaDescriptor);
if (builder == null) return;
ProtoBuf.Function functionProto = builder.build();
WriteAnnotationUtilKt.writeKotlinMetadata(v, state, KotlinClassHeader.Kind.SYNTHETIC_CLASS, 0, av -> {
writeAnnotationData(av, serializer, functionProto);
@@ -652,7 +652,8 @@ class CoroutineCodegenForNamedFunction private constructor(
override fun generateKotlinMetadataAnnotation() {
writeKotlinMetadata(v, state, KotlinClassHeader.Kind.SYNTHETIC_CLASS, 0) { av ->
val serializer = DescriptorSerializer.createForLambda(JvmSerializerExtension(v.serializationBindings, state))
val functionProto = serializer.functionProto(createFreeFakeLambdaDescriptor(suspendFunctionJvmView)).build()
val functionProto =
serializer.functionProto(createFreeFakeLambdaDescriptor(suspendFunctionJvmView))?.build() ?: return@writeKotlinMetadata
AsmUtil.writeAnnotationData(av, serializer, functionProto)
}
}
@@ -47,6 +47,13 @@ class JvmSerializerExtension(private val bindings: JvmSerializationBindings, sta
override val metadataVersion = state.metadataVersion
override fun shouldUseTypeTable(): Boolean = useTypeTable
override fun shouldSerializeFunction(descriptor: FunctionDescriptor): Boolean {
return classBuilderMode != ClassBuilderMode.ABI || descriptor.visibility != Visibilities.PRIVATE
}
override fun shouldSerializeProperty(descriptor: PropertyDescriptor): Boolean {
return classBuilderMode != ClassBuilderMode.ABI || descriptor.visibility != Visibilities.PRIVATE
}
override fun serializeClass(
descriptor: ClassDescriptor,
@@ -103,7 +110,7 @@ class JvmSerializerExtension(private val bindings: JvmSerializationBindings, sta
for (localVariable in localVariables) {
val propertyDescriptor = createFreeFakeLocalPropertyDescriptor(localVariable)
val serializer = DescriptorSerializer.createForLambda(this)
proto.addExtension(extension, serializer.propertyProto(propertyDescriptor).build())
proto.addExtension(extension, serializer.propertyProto(propertyDescriptor)?.build() ?: continue)
}
}
@@ -108,8 +108,8 @@ class DescriptorSerializer private constructor(
if (descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) continue
when (descriptor) {
is PropertyDescriptor -> builder.addProperty(propertyProto(descriptor))
is FunctionDescriptor -> builder.addFunction(functionProto(descriptor))
is PropertyDescriptor -> propertyProto(descriptor)?.let { builder.addProperty(it) }
is FunctionDescriptor -> functionProto(descriptor)?.let { builder.addFunction(it) }
}
}
@@ -178,7 +178,9 @@ class DescriptorSerializer private constructor(
return false
}
fun propertyProto(descriptor: PropertyDescriptor): ProtoBuf.Property.Builder {
fun propertyProto(descriptor: PropertyDescriptor): ProtoBuf.Property.Builder? {
if (!extension.shouldSerializeProperty(descriptor)) return null
val builder = ProtoBuf.Property.newBuilder()
val local = createChildSerializer(descriptor)
@@ -281,7 +283,9 @@ class DescriptorSerializer private constructor(
else
descriptor.visibility
fun functionProto(descriptor: FunctionDescriptor): ProtoBuf.Function.Builder {
fun functionProto(descriptor: FunctionDescriptor): ProtoBuf.Function.Builder? {
if (!extension.shouldSerializeFunction(descriptor)) return null
val builder = ProtoBuf.Function.newBuilder()
val local = createChildSerializer(descriptor)
@@ -638,8 +642,8 @@ class DescriptorSerializer private constructor(
for (declaration in sort(members)) {
when (declaration) {
is PropertyDescriptor -> builder.addProperty(propertyProto(declaration))
is FunctionDescriptor -> builder.addFunction(functionProto(declaration))
is PropertyDescriptor -> propertyProto(declaration)?.let { builder.addProperty(it) }
is FunctionDescriptor -> functionProto(declaration)?.let { builder.addFunction(it) }
is TypeAliasDescriptor -> builder.addTypeAlias(typeAliasProto(declaration))
}
}
@@ -22,6 +22,8 @@ abstract class SerializerExtension {
open fun shouldUseTypeTable(): Boolean = false
open fun shouldUseNormalizedVisibility(): Boolean = false
open fun shouldSerializeFunction(descriptor: FunctionDescriptor): Boolean = true
open fun shouldSerializeProperty(descriptor: PropertyDescriptor): Boolean = true
interface ClassMembersProducer {
fun getCallableMembers(classDescriptor: ClassDescriptor): Collection<CallableMemberDescriptor>
+1
View File
@@ -19,6 +19,7 @@ dependencies {
compile(projectTests(":j2k"))
compile(projectTests(":idea:idea-android"))
compile(projectTests(":jps-plugin"))
compile(projectTests(":plugins:jvm-abi-gen"))
compile(projectTests(":plugins:android-extensions-compiler"))
compile(projectTests(":plugins:android-extensions-ide"))
compile(projectTests(":plugins:android-extensions-jps"))
@@ -152,6 +152,7 @@ import org.jetbrains.kotlin.jps.incremental.AbstractJsProtoComparisonTest
import org.jetbrains.kotlin.jps.incremental.AbstractJvmProtoComparisonTest
import org.jetbrains.kotlin.kapt.cli.test.AbstractArgumentParsingTest
import org.jetbrains.kotlin.kapt.cli.test.AbstractKaptToolIntegrationTest
import org.jetbrains.kotlin.jvm.abi.AbstractCompareJvmAbiTest
import org.jetbrains.kotlin.kapt3.test.AbstractClassFileToSourceStubConverterTest
import org.jetbrains.kotlin.kapt3.test.AbstractKotlinKaptContextTest
import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg
@@ -1069,6 +1070,12 @@ fun main(args: Array<String>) {
}
}
testGroup("plugins/jvm-abi-gen/test", "plugins/jvm-abi-gen/testData") {
testClass<AbstractCompareJvmAbiTest> {
model("compare", recursive = false, extension = null)
}
}
testGroup("plugins/kapt3/kapt3-compiler/test", "plugins/kapt3/kapt3-compiler/testData") {
testClass<AbstractClassFileToSourceStubConverterTest> {
model("converter")
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.jvm.abi.asm.AbiClassBuilder
import org.jetbrains.kotlin.modules.TargetId
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingTrace
@@ -64,10 +65,10 @@ class JvmAbiAnalysisHandlerExtension(
private object AbiBinaries : ClassBuilderFactory {
override fun getClassBuilderMode(): ClassBuilderMode =
ClassBuilderMode.FULL
ClassBuilderMode.ABI
override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder =
AbstractClassBuilder.Concrete(ClassWriter(0))
AbiClassBuilder(ClassWriter(0))
override fun asText(builder: ClassBuilder): String =
throw UnsupportedOperationException("AbiBinaries generator asked for text")
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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.asm
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.AbstractClassBuilder
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.FieldVisitor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
internal class AbiClassBuilder(private val cv: ClassVisitor) : AbstractClassBuilder() {
override fun getVisitor(): ClassVisitor = cv
override fun newMethod(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
if (isPrivate(access)) return EMPTY_METHOD_VISITOR
return super.newMethod(origin, access, name, desc, signature, exceptions)
}
override fun newField(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
value: Any?
): FieldVisitor {
if (isPrivate(access)) return EMPTY_FIELD_VISITOR
return super.newField(origin, access, name, desc, signature, value)
}
override fun defineClass(
origin: PsiElement?,
version: Int,
access: Int,
name: String,
signature: String?,
superName: String,
interfaces: Array<out String>
) {
if (isPrivate(access)) return
super.defineClass(origin, version, access, name, signature, superName, interfaces)
}
private fun isPrivate(access: Int): Boolean =
(access and Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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
import org.jetbrains.kotlin.incremental.testingUtils.assertEqualDirectories
import java.io.File
import kotlin.test.assertFails
abstract class AbstractCompareJvmAbiTest : BaseJvmAbiTest() {
fun doTest(path: String) {
val testDir = File(path)
val base = Compilation(testDir, "base").also { make(it) }
if (testDir.resolve("sameAbi").exists()) {
val sameAbi = Compilation(testDir, "sameAbi").also { make(it) }
assertEqualDirectories(sameAbi.abiDir, base.abiDir, forgiveExtraFiles = false)
}
if (testDir.resolve("differentAbi").exists()) {
val differentAbi = Compilation(testDir, "differentAbi").also { make(it) }
assertFails("$base and $differentAbi abi are equal") {
assertEqualDirectories(differentAbi.abiDir, base.abiDir, forgiveExtraFiles = false)
}
}
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.utils.TestMessageCollector
import java.io.File
abstract class BaseJvmAbiTest : TestCase() {
private lateinit var workingDir: File
override fun setUp() {
super.setUp()
workingDir = createTempDir(javaClass.simpleName)
workingDir.deleteOnExit()
}
override fun tearDown() {
workingDir.deleteRecursively()
super.tearDown()
}
private val abiPluginJar = File("dist/kotlinc/lib/jvm-abi-gen.jar")
private fun abiOption(option: String, value: String): String =
"plugin:${JvmAbiCommandLineProcessor.COMPILER_PLUGIN_ID}:$option=$value"
inner class Compilation(val projectDir: File, val name: String) {
val srcDir: File
get() = projectDir.resolve(name)
val destinationDir: File
get() = workingDir.resolve("$name/out")
val abiDir: File
get() = workingDir.resolve("$name/abi")
override fun toString(): String =
"compilation '$name'"
}
fun make(compilation: Compilation) {
check(abiPluginJar.exists()) { "Plugin jar '$abiPluginJar' does not exist" }
check(compilation.srcDir.exists()) { "Source dir '${compilation.srcDir}' does not exist" }
val messageCollector = TestMessageCollector()
val compiler = K2JVMCompiler()
val args = compiler.createArguments().apply {
freeArgs = listOf(compilation.srcDir.canonicalPath)
pluginClasspaths = arrayOf(abiPluginJar.canonicalPath)
pluginOptions = arrayOf(abiOption("outputDir", compilation.abiDir.canonicalPath))
destination = compilation.destinationDir.canonicalPath
}
val exitCode = compiler.exec(messageCollector, Services.EMPTY, args)
if (exitCode != ExitCode.OK || messageCollector.errors.isNotEmpty()) {
val errorLines = listOf("Could not compile $compilation", "Exit code: $exitCode", "Errors:") + messageCollector.errors
error(errorLines.joinToString("\n"))
}
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("plugins/jvm-abi-gen/testData/compare")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class CompareJvmAbiTestGenerated extends AbstractCompareJvmAbiTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInCompare() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("plugins/jvm-abi-gen/testData/compare"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, false);
}
@TestMetadata("classPrivateMemebers")
public void testClassPrivateMemebers() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/classPrivateMemebers/");
}
@TestMetadata("topLevelPrivateMembers")
public void testTopLevelPrivateMembers() throws Exception {
runTest("plugins/jvm-abi-gen/testData/compare/topLevelPrivateMembers/");
}
}
@@ -0,0 +1,15 @@
package test
class A {
val publicVal = 0
fun publicMethod() = 0
internal val internalVal = 0
internal fun internalMethod() = 0
protected val protectedVal = 0
protected fun protectedMethod() = 0
private val privateVal = 0
private fun privateMethod() = 0
}
@@ -0,0 +1,12 @@
package test
class A {
val publicVal = 0
fun publicMethod() = 0
internal val internalVal = 0
internal fun internalMethod() = 0
protected val protectedVal = 0
protected fun protectedMethod() = 0
}
@@ -0,0 +1,13 @@
package test
val publicVal = 0
const val publicConst = 0
fun publicFun() = 0
internal val internalVal = 0
internal const val internalConst = 0
internal fun internalFun() = 0
private val privateVal = 0
private const val privateConst = 0
private fun privateFun() = 0
@@ -0,0 +1,9 @@
package test
val publicVal = 0
const val publicConst = 0
fun publicFun() = 0
internal val internalVal = 0
internal const val internalConst = 0
internal fun internalFun() = 0