From 38eee9bfd88d0cff658b39cc092011e67ae34893 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 10 Dec 2020 04:57:57 -0800 Subject: [PATCH] Properly dump $default counterparts of @PublishedApi methods (#30) --- .../kotlin/api/KotlinMetadataSignature.kt | 18 +++++++++++ .../kotlin/api/KotlinMetadataVisibilities.kt | 4 +-- .../kotlin/api/KotlinSignaturesLoading.kt | 31 +++++++++++++------ src/test/kotlin/cases/default/default.txt | 5 +++ .../cases/default/publishedApiWithDefaults.kt | 9 ++++++ src/test/kotlin/tests/CasesPublicAPITest.kt | 4 --- 6 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 src/test/kotlin/cases/default/publishedApiWithDefaults.kt diff --git a/src/main/kotlin/api/KotlinMetadataSignature.kt b/src/main/kotlin/api/KotlinMetadataSignature.kt index d98f821b..ab653d0f 100644 --- a/src/main/kotlin/api/KotlinMetadataSignature.kt +++ b/src/main/kotlin/api/KotlinMetadataSignature.kt @@ -62,6 +62,24 @@ data class MethodBinarySignature( return super.findMemberVisibility(classVisibility) ?: classVisibility?.let { alternateDefaultSignature(it.name)?.let(it::findMember) } } + /** + * Checks whether the method is a $default counterpart of internal @PublishedApi method + */ + public fun isPublishedApiWithDefaultArguments( + classVisibility: ClassVisibility?, + publishedApiSignatures: Set + ): Boolean { + // Fast-path + findMemberVisibility(classVisibility)?.isInternal() ?: return false + val name = jvmMember.name + if (!name.endsWith("\$default")) return false + // Leverage the knowledge about modified signature + val expectedPublishedApiCounterPart = JvmMethodSignature( + name.removeSuffix("\$default"), + jvmMember.desc.replace( ";ILjava/lang/Object;)", ";)")) + return expectedPublishedApiCounterPart in publishedApiSignatures + } + private fun isAccessOrAnnotationsMethod() = access.isSynthetic && (name.startsWith("access\$") || name.endsWith("\$annotations")) private fun isDummyDefaultConstructor() = access.isSynthetic && name == "" && desc == "(Lkotlin/jvm/internal/DefaultConstructorMarker;)V" diff --git a/src/main/kotlin/api/KotlinMetadataVisibilities.kt b/src/main/kotlin/api/KotlinMetadataVisibilities.kt index 3d8d0e10..53392089 100644 --- a/src/main/kotlin/api/KotlinMetadataVisibilities.kt +++ b/src/main/kotlin/api/KotlinMetadataVisibilities.kt @@ -41,7 +41,7 @@ fun MemberVisibility.isPublic(isPublishedApi: Boolean) = // Assuming isReified implies inline !isReified && isPublic(visibility, isPublishedApi) - +fun MemberVisibility.isInternal(): Boolean = visibility != null && Flag.IS_INTERNAL(visibility) val ClassNode.kotlinMetadata: KotlinClassMetadata? get() { @@ -68,7 +68,7 @@ fun KotlinClassMetadata?.isFileOrMultipartFacade() = fun KotlinClassMetadata?.isSyntheticClass() = this is KotlinClassMetadata.SyntheticClass -fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility? { +fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility { var flags: Flags? = null var _facadeClassName: String? = null val members = mutableListOf() diff --git a/src/main/kotlin/api/KotlinSignaturesLoading.kt b/src/main/kotlin/api/KotlinSignaturesLoading.kt index 6a96314c..d998d3ee 100644 --- a/src/main/kotlin/api/KotlinSignaturesLoading.kt +++ b/src/main/kotlin/api/KotlinSignaturesLoading.kt @@ -28,27 +28,38 @@ public fun Sequence.loadApiFromJvmClasses(visibilityFilter: (String val visibilityMapNew = classNodes.readKotlinVisibilities().filterKeys(visibilityFilter) return classNodes - .map { classNode -> with(classNode) { + .map { classNode -> + with(classNode) { val metadata = kotlinMetadata val mVisibility = visibilityMapNew[name] val classAccess = AccessFlags(effectiveAccess and Opcodes.ACC_STATIC.inv()) - val supertypes = listOf(superName) - "java/lang/Object" + interfaces.sorted() - val memberSignatures = ( - fields.map { it.toFieldBinarySignature() } + - methods.map { it.toMethodBinarySignature() } - ).filter { - it.isEffectivelyPublic(classAccess, mVisibility) - } + val fieldSignatures = fields + .map { it.toFieldBinarySignature() } + .filter { + it.isEffectivelyPublic(classAccess, mVisibility) + } + + val allMethods = methods.map { it.toMethodBinarySignature() } + // Signatures marked with @PublishedApi + val publishedApiSignatures = allMethods.filter { + it.isPublishedApi + }.map { it.jvmMember }.toSet() + val methodSignatures = allMethods + .filter { + it.isEffectivelyPublic(classAccess, mVisibility) || + it.isPublishedApiWithDefaultArguments(mVisibility, publishedApiSignatures) + } ClassBinarySignature( - name, superName, outerClassName, supertypes, memberSignatures, classAccess, + name, superName, outerClassName, supertypes, fieldSignatures + methodSignatures, classAccess, isEffectivelyPublic(mVisibility), metadata.isFileOrMultipartFacade() || isDefaultImpls(metadata), annotations(visibleAnnotations, invisibleAnnotations) ) - }} + } + } .asIterable() .sortedBy { it.name } } diff --git a/src/test/kotlin/cases/default/default.txt b/src/test/kotlin/cases/default/default.txt index ae2b40a2..3b40a33f 100644 --- a/src/test/kotlin/cases/default/default.txt +++ b/src/test/kotlin/cases/default/default.txt @@ -15,3 +15,8 @@ public final class cases/default/InterfaceFunctions$DefaultImpls { public static synthetic fun withSomeDefaults$default (Lcases/default/InterfaceFunctions;ILjava/lang/String;ILjava/lang/Object;)V } +public final class cases/default/PublishedApiWithDefaultsKt { + public static final fun twoDefaults (ILjava/lang/Object;Ljava/lang/String;)V + public static synthetic fun twoDefaults$default (ILjava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)V +} + diff --git a/src/test/kotlin/cases/default/publishedApiWithDefaults.kt b/src/test/kotlin/cases/default/publishedApiWithDefaults.kt new file mode 100644 index 00000000..d42b15c4 --- /dev/null +++ b/src/test/kotlin/cases/default/publishedApiWithDefaults.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ +@file:Suppress("UNUSED_PARAMETER") +package cases.default + +@PublishedApi +internal fun twoDefaults(par0: Int, par1: Any = 1, par2: String? = null) {} diff --git a/src/test/kotlin/tests/CasesPublicAPITest.kt b/src/test/kotlin/tests/CasesPublicAPITest.kt index a2367b2f..47d5ae5a 100644 --- a/src/test/kotlin/tests/CasesPublicAPITest.kt +++ b/src/test/kotlin/tests/CasesPublicAPITest.kt @@ -50,18 +50,14 @@ class CasesPublicAPITest { @Test fun whenMappings() { snapshotAPIAndCompare(testName.methodName) } - private fun snapshotAPIAndCompare(testClassRelativePath: String) { val testClassPaths = baseClassPaths.map { it.resolve(testClassRelativePath) } val testClasses = testClassPaths.flatMap { it.listFiles().orEmpty().asIterable() } check(testClasses.isNotEmpty()) { "No class files are found in paths: $testClassPaths" } val testClassStreams = testClasses.asSequence().filter { it.name.endsWith(".class") }.map { it.inputStream() } - val api = testClassStreams.loadApiFromJvmClasses().filterOutNonPublic() - val target = baseOutputPath.resolve(testClassRelativePath).resolve(testName.methodName + ".txt") - api.dumpAndCompareWith(target) } }