diff --git a/libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools/PublicApiDump.kt b/libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools/PublicApiDump.kt index 448b4b93df4..2ff1e93609e 100644 --- a/libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools/PublicApiDump.kt +++ b/libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools/PublicApiDump.kt @@ -19,13 +19,6 @@ fun main(args: Array) { fun JarFile.classEntries() = entries().asSequence().filter { !it.isDirectory && it.name.endsWith(".class") } -data class ClassBinarySignature(val name: String, val superName: String, val outerName: String?, val modifiers: String, val supertypes: List, val memberSignatures: List, val isPublic: Boolean) -data class MemberBinarySignature(val name: String, val signature: String, val isStatic: Boolean) - -val ClassBinarySignature.signature: String - get() = "$modifiers class $name" + if (supertypes.isEmpty()) "" else ": ${supertypes.joinToString()}" - - fun getBinaryAPI(jar: JarFile, visibilityMap: Map): List = getBinaryAPI(jar.classEntries().map { entry -> jar.getInputStream(entry) }, visibilityMap) @@ -36,26 +29,29 @@ fun getBinaryAPI(classStreams: Sequence, visibilityMap: Map.filterOutNonPublic(): List val classByName = associateBy { it.name } fun ClassBinarySignature.isPublicAndAccessible(): Boolean = - isPublic && (outerName == null || classByName[outerName]?.isPublicAndAccessible() ?: true) + isEffectivelyPublic && (outerName == null || classByName[outerName]?.isPublicAndAccessible() ?: true) fun supertypes(superName: String) = generateSequence({ classByName[superName] }, { classByName[it.superName] }) @@ -73,7 +69,7 @@ fun List.filterOutNonPublic(): List if (nonPublicSupertypes.isEmpty()) return this - val inheritedStaticSignatures = nonPublicSupertypes.flatMap { it.memberSignatures.filter { it.isStatic }} + val inheritedStaticSignatures = nonPublicSupertypes.flatMap { it.memberSignatures.filter { it.access.isStatic }} // not covered the case when there is public superclass after chain of private superclasses return this.copy(memberSignatures = memberSignatures + inheritedStaticSignatures, supertypes = supertypes - superName) diff --git a/libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools/asmUtils.kt b/libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools/asmUtils.kt index 5c037067343..52f1b8b55cd 100644 --- a/libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools/asmUtils.kt +++ b/libraries/tools/binary-compatibility-validator/src/main/kotlin/org.jetbrains.kotlin.tools/asmUtils.kt @@ -3,7 +3,6 @@ package org.jetbrains.kotlin.tools import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.* - val ACCESS_NAMES = mapOf( Opcodes.ACC_PUBLIC to "public", Opcodes.ACC_PROTECTED to "protected", @@ -15,49 +14,97 @@ val ACCESS_NAMES = mapOf( Opcodes.ACC_INTERFACE to "interface", Opcodes.ACC_ANNOTATION to "annotation") +data class ClassBinarySignature( + val name: String, + val superName: String, + val outerName: String?, + val supertypes: List, + val memberSignatures: List, + val access: AccessFlags, + val isEffectivelyPublic: Boolean) { -public fun isPublic(access: Int) = access and Opcodes.ACC_PUBLIC != 0 || access and Opcodes.ACC_PROTECTED != 0 -public fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0 -fun getModifiers(access: Int): List = ACCESS_NAMES.entries.mapNotNull { if (access and it.key != 0) it.value else null } -fun getModifierString(access: Int): String = getModifiers(access).joinToString(" ") + val signature: String + get() = "${access.getModifierString()} class $name" + if (supertypes.isEmpty()) "" else ": ${supertypes.joinToString()}" -fun ClassNode.isSynthetic() = access and Opcodes.ACC_SYNTHETIC != 0 -fun MethodNode.isSynthetic() = access and Opcodes.ACC_SYNTHETIC != 0 -fun ClassNode.isPublic() = isPublic(access) -fun MethodNode.isPublic() = isPublic(access) -fun FieldNode.isPublic() = isPublic(access) +} + + +interface MemberBinarySignature { + val name: String + val desc: String + val access: AccessFlags + val isInlineExposed: Boolean + + fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?) + = access.isPublic // && !(access.isProtected && classAccess.isFinal) + && (classVisibility?.members?.get(MemberSignature(name, desc))?.isPublic(isInlineExposed) ?: true) + + val signature: String +} + +data class MethodBinarySignature( + override val name: String, + override val desc: String, + override val isInlineExposed: Boolean, + override val access: AccessFlags) : MemberBinarySignature { + override val signature: String + get() = "${access.getModifierString()} fun $name $desc" + + override fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?) + = super.isEffectivelyPublic(classAccess, classVisibility) + && !isAccessMethod() + + private fun isAccessMethod() = access.isSynthetic && name.startsWith("access\$") +} + +data class FieldBinarySignature( + override val name: String, + override val desc: String, + override val isInlineExposed: Boolean, + override val access: AccessFlags) : MemberBinarySignature { + override val signature: String + get() = "${access.getModifierString()} field $name $desc" + + override fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?) + = super.isEffectivelyPublic(classAccess, classVisibility) + // TODO: lateinit exposed field +} + + + +data class AccessFlags(val access: Int) { + val isPublic: Boolean get() = isPublic(access) + val isProtected: Boolean get() = isProtected(access) + val isStatic: Boolean get() = isStatic(access) + val isFinal: Boolean get() = isFinal(access) + val isSynthetic: Boolean get() = isSynthetic(access) + + fun getModifiers(): List = ACCESS_NAMES.entries.mapNotNull { if (access and it.key != 0) it.value else null } + fun getModifierString(): String = getModifiers().joinToString(" ") +} + +fun isPublic(access: Int) = access and Opcodes.ACC_PUBLIC != 0 || access and Opcodes.ACC_PROTECTED != 0 +fun isProtected(access: Int) = access and Opcodes.ACC_PROTECTED != 0 +fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0 +fun isFinal(access: Int) = access and Opcodes.ACC_FINAL != 0 +fun isSynthetic(access: Int) = access and Opcodes.ACC_SYNTHETIC != 0 fun ClassNode.isEffectivelyPublic(classVisibility: ClassVisibility?) = - isPublic() + isPublic(access) && !isLocal() && !isWhenMappings() && (classVisibility?.isPublic(isInlineExposed()) ?: true) - && !isNonPublicFileOrFacade(classVisibility) - -fun ClassNode.isNonPublicFileOrFacade(classVisibility: ClassVisibility?) = - isFileOrMultipartFacade() - && methods.none { it.isEffectivelyPublic(classVisibility) } - && fields.none { it.isEffectivelyPublic(classVisibility) } -fun MethodNode.isEffectivelyPublic(classVisibility: ClassVisibility?) = - isPublic() - && (classVisibility?.members?.get(MemberSignature(name, desc))?.isPublic(isInlineExposed()) ?: true) - && !isAccessMethod() +val ClassNode.innerClassNode: InnerClassNode? get() = innerClasses.singleOrNull { it.name == name } +fun ClassNode.isLocal() = innerClassNode?.run { innerName == null && outerName == null} ?: false +fun ClassNode.isWhenMappings() = isSynthetic(access) && name.endsWith("\$WhenMappings") -fun FieldNode.isEffectivelyPublic(classVisibility: ClassVisibility?) = - isPublic() - && (classVisibility?.members?.get(MemberSignature(name, desc))?.isPublic(isInlineExposed()) ?: true) -// TODO: lateinit exposed field +val ClassNode.effectiveAccess: Int get() = innerClassNode?.access ?: access +val ClassNode.outerClassName: String? get() = innerClassNode?.outerName -fun ClassNode.isLocal() = innerClasses.filter { it.name == name && it.innerName == null && it.outerName == null }.count() == 1 -fun ClassNode.isWhenMappings() = isSynthetic() && name.endsWith("\$WhenMappings") -fun MethodNode.isAccessMethod() = isSynthetic() && name.startsWith("access\$") - -val ClassNode.effectiveAccess: Int get() = innerClasses.singleOrNull { it.name == name }?.access ?: access - const val inlineExposedAnnotationName = "kotlin/internal/InlineExposed" fun ClassNode.isInlineExposed() = hasAnnotation(inlineExposedAnnotationName, includeInvisible = true) fun MethodNode.isInlineExposed() = hasAnnotation(inlineExposedAnnotationName, includeInvisible = true) @@ -96,7 +143,3 @@ private fun List.annotationValue(key: String): Any? { } return null } - -val ClassNode.outerClassName: String? - get() = innerClasses.singleOrNull { it.name == name }?.outerName -