diff options
Diffstat (limited to 'binary-compatibility-validator/src/asmUtils.kt')
-rw-r--r-- | binary-compatibility-validator/src/asmUtils.kt | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/binary-compatibility-validator/src/asmUtils.kt b/binary-compatibility-validator/src/asmUtils.kt new file mode 100644 index 00000000..b14cb8d5 --- /dev/null +++ b/binary-compatibility-validator/src/asmUtils.kt @@ -0,0 +1,173 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.tools + +import org.objectweb.asm.* +import org.objectweb.asm.tree.* + +val ACCESS_NAMES = mapOf( + Opcodes.ACC_PUBLIC to "public", + Opcodes.ACC_PROTECTED to "protected", + Opcodes.ACC_PRIVATE to "private", + Opcodes.ACC_STATIC to "static", + Opcodes.ACC_FINAL to "final", + Opcodes.ACC_ABSTRACT to "abstract", + Opcodes.ACC_SYNTHETIC to "synthetic", + 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<String>, + val memberSignatures: List<MemberBinarySignature>, + val access: AccessFlags, + val isEffectivelyPublic: Boolean, + val isNotUsedWhenEmpty: Boolean) { + + val signature: String + get() = "${access.getModifierString()} class $name" + if (supertypes.isEmpty()) "" else " : ${supertypes.joinToString()}" + +} + + +interface MemberBinarySignature { + val name: String + val desc: String + val access: AccessFlags + val isPublishedApi: Boolean + + fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?) + = access.isPublic && !(access.isProtected && classAccess.isFinal) + && (findMemberVisibility(classVisibility)?.isPublic(isPublishedApi) ?: true) + + fun findMemberVisibility(classVisibility: ClassVisibility?) + = classVisibility?.members?.get(MemberSignature(name, desc)) + + val signature: String +} + +data class MethodBinarySignature( + override val name: String, + override val desc: String, + override val isPublishedApi: 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) + && !isAccessOrAnnotationsMethod() + + private fun isAccessOrAnnotationsMethod() = access.isSynthetic && (name.startsWith("access\$") || name.endsWith("\$annotations")) +} + +data class FieldBinarySignature( + override val name: String, + override val desc: String, + override val isPublishedApi: Boolean, + override val access: AccessFlags) : MemberBinarySignature { + override val signature: String + get() = "${access.getModifierString()} field $name $desc" + + override fun findMemberVisibility(classVisibility: ClassVisibility?): MemberVisibility? { + val fieldVisibility = super.findMemberVisibility(classVisibility) ?: return null + + // good case for 'satisfying': fieldVisibility.satisfying { it.isLateInit() }?.let { classVisibility?.findSetterForProperty(it) } + if (fieldVisibility.isLateInit()) { + classVisibility?.findSetterForProperty(fieldVisibility)?.let { return it } + } + return fieldVisibility + } +} + +val MemberBinarySignature.kind: Int get() = when (this) { + is FieldBinarySignature -> 1 + is MethodBinarySignature -> 2 + else -> error("Unsupported $this") +} + +val MEMBER_SORT_ORDER = compareBy<MemberBinarySignature>( + { it.kind }, + { it.name }, + { it.desc } +) + + +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<String> = 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(access) + && !isLocal() + && !isWhenMappings() + && (classVisibility?.isPublic(isPublishedApi()) ?: true) + + +val ClassNode.innerClassNode: InnerClassNode? get() = innerClasses.singleOrNull { it.name == name } +fun ClassNode.isLocal() = innerClassNode?.run { innerName == null && outerName == null} ?: false +fun ClassNode.isInner() = innerClassNode != null +fun ClassNode.isWhenMappings() = isSynthetic(access) && name.endsWith("\$WhenMappings") + +val ClassNode.effectiveAccess: Int get() = innerClassNode?.access ?: access +val ClassNode.outerClassName: String? get() = innerClassNode?.outerName + + +const val publishedApiAnnotationName = "kotlin/PublishedApi" +fun ClassNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null +fun MethodNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null +fun FieldNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null + + +private object KotlinClassKind { + const val FILE = 2 + const val SYNTHETIC_CLASS = 3 + const val MULTIPART_FACADE = 4 + + val FILE_OR_MULTIPART_FACADE_KINDS = listOf(FILE, MULTIPART_FACADE) +} + +fun ClassNode.isFileOrMultipartFacade() = kotlinClassKind.let { it != null && it in KotlinClassKind.FILE_OR_MULTIPART_FACADE_KINDS } +fun ClassNode.isDefaultImpls() = isInner() && name.endsWith("\$DefaultImpls") && kotlinClassKind == KotlinClassKind.SYNTHETIC_CLASS + + +val ClassNode.kotlinClassKind: Int? + get() = findAnnotation("kotlin/Metadata", false)?.get("k") as Int? + +fun ClassNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible) +fun MethodNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible) +fun FieldNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible) + +operator fun AnnotationNode.get(key: String): Any? = values.annotationValue(key) + +private fun List<Any>.annotationValue(key: String): Any? { + for (index in (0 .. size / 2 - 1)) { + if (this[index*2] == key) + return this[index*2 + 1] + } + return null +} + +private fun findAnnotation(annotationName: String, visibleAnnotations: List<AnnotationNode>?, invisibleAnnotations: List<AnnotationNode>?, includeInvisible: Boolean): AnnotationNode? = + visibleAnnotations?.firstOrNull { it.refersToName(annotationName) } ?: + if (includeInvisible) invisibleAnnotations?.firstOrNull { it.refersToName(annotationName) } else null + +fun AnnotationNode.refersToName(name: String) = desc.startsWith('L') && desc.endsWith(';') && desc.regionMatches(1, name, 0, name.length)
\ No newline at end of file |