Skip to content

Commit d5affe0

Browse files
authored
Evaluate 'static final Companion' visibility in the outer class to th… (#59)
* Evaluate 'static final Companion' visibility in the outer class to the visibility of the companion class itself Fixes #53
1 parent 5842109 commit d5affe0

File tree

7 files changed

+186
-20
lines changed

7 files changed

+186
-20
lines changed

src/main/kotlin/api/KotlinMetadataSignature.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,11 @@ data class AccessFlags(val access: Int) {
159159
fun getModifierString(): String = getModifiers().joinToString(" ")
160160
}
161161

162+
fun FieldBinarySignature.isCompanionField(outerClassMetadata: KotlinClassMetadata?): Boolean {
163+
if (!access.isFinal || !access.isStatic) return false
164+
val metadata = outerClassMetadata ?: return false
165+
// Non-classes are not affected by the problem
166+
if (metadata !is KotlinClassMetadata.Class) return false
167+
return metadata.toKmClass().companionObject == name
168+
}
169+

src/main/kotlin/api/KotlinMetadataVisibilities.kt

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,33 @@ fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility
121121

122122
fun ClassNode.toClassVisibility() = kotlinMetadata?.toClassVisibility(this)
123123

124-
fun Sequence<ClassNode>.readKotlinVisibilities(): Map<String, ClassVisibility> =
125-
mapNotNull { it.toClassVisibility() }
126-
.associateBy { it.name }
127-
.apply {
128-
values.asSequence().filter { it.isCompanion }.forEach {
129-
val containingClassName = it.name.substringBeforeLast('$')
130-
getValue(containingClassName).companionVisibilities = it
131-
}
132-
133-
values.asSequence().filter { it.facadeClassName != null }.forEach {
134-
getValue(it.facadeClassName!!).partVisibilities.add(it)
124+
fun Map<String, ClassNode>.readKotlinVisibilities(visibilityFilter: (String) -> Boolean = { true }): Map<String, ClassVisibility> =
125+
/*
126+
* Optimized sequence of:
127+
* 1) Map values to visibility
128+
* 2) Filter keys by visibilityFilter
129+
* 3) Post-process each value and set facade/companion
130+
*/
131+
entries
132+
.mapNotNull { (name, classNode) ->
133+
if (!visibilityFilter(name)) return@mapNotNull null
134+
val visibility = classNode.toClassVisibility() ?: return@mapNotNull null
135+
name to visibility
136+
}.toMap().apply {
137+
values.forEach {
138+
updateCompanionInfo(it)
139+
updateFacadeInfo(it)
135140
}
136141
}
142+
143+
private fun Map<String, ClassVisibility>.updateFacadeInfo(it: ClassVisibility) {
144+
if (it.facadeClassName == null) return
145+
getValue(it.facadeClassName).partVisibilities.add(it)
146+
}
147+
148+
private fun Map<String, ClassVisibility>.updateCompanionInfo(it: ClassVisibility) {
149+
if (!it.isCompanion) return
150+
val containingClassName = it.name.substringBeforeLast('$')
151+
getValue(containingClassName).companionVisibilities = it
152+
}
153+

src/main/kotlin/api/KotlinSignaturesLoading.kt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55

66
package kotlinx.validation.api
77

8+
import kotlinx.metadata.jvm.KotlinClassMetadata
89
import kotlinx.validation.*
910
import org.objectweb.asm.*
1011
import org.objectweb.asm.tree.*
1112
import java.io.*
13+
import java.util.*
1214
import java.util.jar.*
1315

1416
@ExternalApi
@@ -26,19 +28,35 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
2628
}
2729
}
2830

29-
val visibilityMapNew = classNodes.readKotlinVisibilities().filterKeys(visibilityFilter)
30-
return classNodes
31+
// Note: map is sorted, so the dump will produce stable result
32+
val classNodeMap = classNodes.associateByTo(TreeMap()) { it.name }
33+
val visibilityMap = classNodeMap.readKotlinVisibilities(visibilityFilter)
34+
return classNodeMap
35+
.values
3136
.map { classNode ->
3237
with(classNode) {
3338
val metadata = kotlinMetadata
34-
val mVisibility = visibilityMapNew[name]
39+
val mVisibility = visibilityMap[name]
3540
val classAccess = AccessFlags(effectiveAccess and Opcodes.ACC_STATIC.inv())
3641
val supertypes = listOf(superName) - "java/lang/Object" + interfaces.sorted()
3742

3843
val fieldSignatures = fields
3944
.map { it.toFieldBinarySignature() }
4045
.filter {
4146
it.isEffectivelyPublic(classAccess, mVisibility)
47+
}.filter {
48+
/*
49+
* Filter out 'public static final Companion' field that doesn't constitutes public API.
50+
* For that we first check if field corresponds to the 'Companion' class and then
51+
* if companion is effectively public by itself, so the 'Companion' field has the same visibility.
52+
*/
53+
if (!it.isCompanionField(classNode.kotlinMetadata)) return@filter true
54+
val outerKClass = (classNode.kotlinMetadata as KotlinClassMetadata.Class).toKmClass()
55+
val companionName = name + "$" + outerKClass.companionObject
56+
// False positive is better than the crash here
57+
val companionClass = classNodeMap[companionName] ?: return@filter true
58+
val visibility = visibilityMap[companionName] ?: return@filter true
59+
companionClass.isEffectivelyPublic(visibility)
4260
}
4361

4462
val allMethods = methods.map { it.toMethodBinarySignature() }
@@ -60,8 +78,6 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
6078
)
6179
}
6280
}
63-
.asIterable()
64-
.sortedBy { it.name }
6581
}
6682

6783
internal fun List<ClassBinarySignature>.filterOutAnnotated(targetAnnotations: Set<String>): List<ClassBinarySignature> {

src/test/kotlin/cases/companions/companions.kt

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ object PublicClasses {
1818
internal companion object
1919
}
2020

21+
class PublishedApiCompanion {
22+
@PublishedApi
23+
internal companion object
24+
}
25+
2126
class PrivateCompanion {
2227
private companion object
2328
}
@@ -33,25 +38,56 @@ object PublicInterfaces {
3338
}
3439
}
3540

41+
object InternalClasses {
42+
internal class PublicCompanion {
43+
companion object
44+
}
45+
46+
internal class ProtectedCompanion {
47+
protected companion object
48+
}
3649

50+
internal abstract class AbstractProtectedCompanion {
51+
protected companion object
52+
}
3753

38-
object InternalClasses {
54+
internal class InternalCompanion {
55+
internal companion object
56+
}
57+
58+
internal class PrivateCompanion {
59+
private companion object
60+
}
61+
}
62+
63+
object PublishedApiClasses {
64+
@PublishedApi
3965
internal class PublicCompanion {
4066
companion object
4167
}
4268

69+
@PublishedApi
4370
internal class ProtectedCompanion {
4471
protected companion object
4572
}
4673

74+
@PublishedApi
4775
internal abstract class AbstractProtectedCompanion {
4876
protected companion object
4977
}
5078

79+
@PublishedApi
5180
internal class InternalCompanion {
5281
internal companion object
5382
}
5483

84+
@PublishedApi
85+
internal class PublishedApiCompanion {
86+
@PublishedApi
87+
internal companion object
88+
}
89+
90+
@PublishedApi
5591
internal class PrivateCompanion {
5692
private companion object
5793
}
@@ -67,7 +103,6 @@ object InternalInterfaces {
67103
}
68104
}
69105

70-
71106
object PrivateClasses {
72107
private class PublicCompanion {
73108
companion object
@@ -85,6 +120,11 @@ object PrivateClasses {
85120
internal companion object
86121
}
87122

123+
private class PublishedApiCompanion {
124+
@PublishedApi
125+
internal companion object
126+
}
127+
88128
private class PrivateCompanion {
89129
private companion object
90130
}

src/test/kotlin/cases/companions/companions.txt

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ protected final class cases/companions/PublicClasses$AbstractProtectedCompanion$
2727
}
2828

2929
public final class cases/companions/PublicClasses$InternalCompanion {
30-
public static final field Companion Lcases/companions/PublicClasses$InternalCompanion$Companion;
3130
public fun <init> ()V
3231
}
3332

@@ -47,12 +46,19 @@ public final class cases/companions/PublicClasses$PublicCompanion {
4746
public final class cases/companions/PublicClasses$PublicCompanion$Companion {
4847
}
4948

49+
public final class cases/companions/PublicClasses$PublishedApiCompanion {
50+
public static final field Companion Lcases/companions/PublicClasses$PublishedApiCompanion$Companion;
51+
public fun <init> ()V
52+
}
53+
54+
public final class cases/companions/PublicClasses$PublishedApiCompanion$Companion {
55+
}
56+
5057
public final class cases/companions/PublicInterfaces {
5158
public static final field INSTANCE Lcases/companions/PublicInterfaces;
5259
}
5360

5461
public abstract interface class cases/companions/PublicInterfaces$PrivateCompanion {
55-
public static final synthetic field Companion Lcases/companions/PublicInterfaces$PrivateCompanion$Companion;
5662
}
5763

5864
public abstract interface class cases/companions/PublicInterfaces$PublicCompanion {
@@ -62,3 +68,43 @@ public abstract interface class cases/companions/PublicInterfaces$PublicCompanio
6268
public final class cases/companions/PublicInterfaces$PublicCompanion$Companion {
6369
}
6470

71+
public final class cases/companions/PublishedApiClasses {
72+
public static final field INSTANCE Lcases/companions/PublishedApiClasses;
73+
}
74+
75+
public abstract class cases/companions/PublishedApiClasses$AbstractProtectedCompanion {
76+
protected static final field Companion Lcases/companions/PublishedApiClasses$AbstractProtectedCompanion$Companion;
77+
public fun <init> ()V
78+
}
79+
80+
protected final class cases/companions/PublishedApiClasses$AbstractProtectedCompanion$Companion {
81+
}
82+
83+
public final class cases/companions/PublishedApiClasses$InternalCompanion {
84+
public fun <init> ()V
85+
}
86+
87+
public final class cases/companions/PublishedApiClasses$PrivateCompanion {
88+
public fun <init> ()V
89+
}
90+
91+
public final class cases/companions/PublishedApiClasses$ProtectedCompanion {
92+
public fun <init> ()V
93+
}
94+
95+
public final class cases/companions/PublishedApiClasses$PublicCompanion {
96+
public static final field Companion Lcases/companions/PublishedApiClasses$PublicCompanion$Companion;
97+
public fun <init> ()V
98+
}
99+
100+
public final class cases/companions/PublishedApiClasses$PublicCompanion$Companion {
101+
}
102+
103+
public final class cases/companions/PublishedApiClasses$PublishedApiCompanion {
104+
public static final field Companion Lcases/companions/PublishedApiClasses$PublishedApiCompanion$Companion;
105+
public fun <init> ()V
106+
}
107+
108+
public final class cases/companions/PublishedApiClasses$PublishedApiCompanion$Companion {
109+
}
110+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
public final class cases/internal/Companion {
2+
public fun <init> ()V
3+
}
4+
15
public final class cases/internal/PublicClass {
26
}
37

8+
public final class cases/internal/PublicClassInternalCompanionWithNameShadowing {
9+
public fun <init> ()V
10+
public static final fun getCompanion ()Lcases/internal/Companion;
11+
}
12+
13+
public final class cases/internal/PublicClassNamedInternalCompanion {
14+
public fun <init> ()V
15+
}
16+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package cases.internal
7+
8+
// Internal companion is not part of public API
9+
// neither should be outer static final companion field
10+
11+
// Named that way so the test ensures that PublicClassInternalCompanionWithNameShadowing does properly differentiate
12+
// 'Companion' static final field and hand-written field with the same type
13+
class Companion {
14+
internal companion object
15+
}
16+
17+
class PublicClassNamedInternalCompanion {
18+
internal companion object Foo
19+
}
20+
21+
class PublicClassInternalCompanionWithNameShadowing {
22+
internal companion object Companion {
23+
@JvmStatic
24+
public val Companion: cases.internal.Companion = Companion()
25+
}
26+
}

0 commit comments

Comments
 (0)