Roman Elizarov | 1f74a2d | 2018-06-29 19:19:45 +0300 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
| 3 | */ |
| 4 | |
Vsevolod Tolstopyatov | 74bcc92 | 2018-05-03 20:07:54 +0300 | [diff] [blame] | 5 | package kotlinx.coroutines.experimental.tools |
| 6 | |
| 7 | import org.objectweb.asm.* |
| 8 | import org.objectweb.asm.tree.* |
| 9 | |
| 10 | val ACCESS_NAMES = mapOf( |
| 11 | Opcodes.ACC_PUBLIC to "public", |
| 12 | Opcodes.ACC_PROTECTED to "protected", |
| 13 | Opcodes.ACC_PRIVATE to "private", |
| 14 | Opcodes.ACC_STATIC to "static", |
| 15 | Opcodes.ACC_FINAL to "final", |
| 16 | Opcodes.ACC_ABSTRACT to "abstract", |
| 17 | Opcodes.ACC_SYNTHETIC to "synthetic", |
| 18 | Opcodes.ACC_INTERFACE to "interface", |
| 19 | Opcodes.ACC_ANNOTATION to "annotation") |
| 20 | |
| 21 | data class ClassBinarySignature( |
| 22 | val name: String, |
| 23 | val superName: String, |
| 24 | val outerName: String?, |
| 25 | val supertypes: List<String>, |
| 26 | val memberSignatures: List<MemberBinarySignature>, |
| 27 | val access: AccessFlags, |
| 28 | val isEffectivelyPublic: Boolean, |
| 29 | val isNotUsedWhenEmpty: Boolean) { |
| 30 | |
| 31 | val signature: String |
| 32 | get() = "${access.getModifierString()} class $name" + if (supertypes.isEmpty()) "" else " : ${supertypes.joinToString()}" |
| 33 | |
| 34 | } |
| 35 | |
| 36 | |
| 37 | interface MemberBinarySignature { |
| 38 | val name: String |
| 39 | val desc: String |
| 40 | val access: AccessFlags |
| 41 | val isPublishedApi: Boolean |
| 42 | |
| 43 | fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?) |
| 44 | = access.isPublic && !(access.isProtected && classAccess.isFinal) |
| 45 | && (findMemberVisibility(classVisibility)?.isPublic(isPublishedApi) ?: true) |
| 46 | |
| 47 | fun findMemberVisibility(classVisibility: ClassVisibility?) |
| 48 | = classVisibility?.members?.get(MemberSignature(name, desc)) |
| 49 | |
| 50 | val signature: String |
| 51 | } |
| 52 | |
| 53 | data class MethodBinarySignature( |
| 54 | override val name: String, |
| 55 | override val desc: String, |
| 56 | override val isPublishedApi: Boolean, |
| 57 | override val access: AccessFlags) : MemberBinarySignature { |
| 58 | override val signature: String |
| 59 | get() = "${access.getModifierString()} fun $name $desc" |
| 60 | |
| 61 | override fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?) |
| 62 | = super.isEffectivelyPublic(classAccess, classVisibility) |
| 63 | && !isAccessOrAnnotationsMethod() |
| 64 | |
| 65 | private fun isAccessOrAnnotationsMethod() = access.isSynthetic && (name.startsWith("access\$") || name.endsWith("\$annotations")) |
| 66 | } |
| 67 | |
| 68 | data class FieldBinarySignature( |
| 69 | override val name: String, |
| 70 | override val desc: String, |
| 71 | override val isPublishedApi: Boolean, |
| 72 | override val access: AccessFlags) : MemberBinarySignature { |
| 73 | override val signature: String |
| 74 | get() = "${access.getModifierString()} field $name $desc" |
| 75 | |
| 76 | override fun findMemberVisibility(classVisibility: ClassVisibility?): MemberVisibility? { |
| 77 | val fieldVisibility = super.findMemberVisibility(classVisibility) ?: return null |
| 78 | |
| 79 | // good case for 'satisfying': fieldVisibility.satisfying { it.isLateInit() }?.let { classVisibility?.findSetterForProperty(it) } |
| 80 | if (fieldVisibility.isLateInit()) { |
| 81 | classVisibility?.findSetterForProperty(fieldVisibility)?.let { return it } |
| 82 | } |
| 83 | return fieldVisibility |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | val MemberBinarySignature.kind: Int get() = when (this) { |
| 88 | is FieldBinarySignature -> 1 |
| 89 | is MethodBinarySignature -> 2 |
| 90 | else -> error("Unsupported $this") |
| 91 | } |
| 92 | |
| 93 | val MEMBER_SORT_ORDER = compareBy<MemberBinarySignature>( |
| 94 | { it.kind }, |
| 95 | { it.name }, |
| 96 | { it.desc } |
| 97 | ) |
| 98 | |
| 99 | |
| 100 | data class AccessFlags(val access: Int) { |
| 101 | val isPublic: Boolean get() = isPublic(access) |
| 102 | val isProtected: Boolean get() = isProtected(access) |
| 103 | val isStatic: Boolean get() = isStatic(access) |
| 104 | val isFinal: Boolean get() = isFinal(access) |
| 105 | val isSynthetic: Boolean get() = isSynthetic(access) |
| 106 | |
| 107 | fun getModifiers(): List<String> = ACCESS_NAMES.entries.mapNotNull { if (access and it.key != 0) it.value else null } |
| 108 | fun getModifierString(): String = getModifiers().joinToString(" ") |
| 109 | } |
| 110 | |
| 111 | fun isPublic(access: Int) = access and Opcodes.ACC_PUBLIC != 0 || access and Opcodes.ACC_PROTECTED != 0 |
| 112 | fun isProtected(access: Int) = access and Opcodes.ACC_PROTECTED != 0 |
| 113 | fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0 |
| 114 | fun isFinal(access: Int) = access and Opcodes.ACC_FINAL != 0 |
| 115 | fun isSynthetic(access: Int) = access and Opcodes.ACC_SYNTHETIC != 0 |
| 116 | |
| 117 | |
| 118 | fun ClassNode.isEffectivelyPublic(classVisibility: ClassVisibility?) = |
| 119 | isPublic(access) |
| 120 | && !isLocal() |
| 121 | && !isWhenMappings() |
| 122 | && (classVisibility?.isPublic(isPublishedApi()) ?: true) |
| 123 | |
| 124 | |
| 125 | val ClassNode.innerClassNode: InnerClassNode? get() = innerClasses.singleOrNull { it.name == name } |
| 126 | fun ClassNode.isLocal() = innerClassNode?.run { innerName == null && outerName == null} ?: false |
| 127 | fun ClassNode.isInner() = innerClassNode != null |
| 128 | fun ClassNode.isWhenMappings() = isSynthetic(access) && name.endsWith("\$WhenMappings") |
| 129 | |
| 130 | val ClassNode.effectiveAccess: Int get() = innerClassNode?.access ?: access |
| 131 | val ClassNode.outerClassName: String? get() = innerClassNode?.outerName |
| 132 | |
| 133 | |
| 134 | const val publishedApiAnnotationName = "kotlin/PublishedApi" |
| 135 | fun ClassNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null |
| 136 | fun MethodNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null |
| 137 | fun FieldNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null |
| 138 | |
| 139 | |
| 140 | private object KotlinClassKind { |
| 141 | const val FILE = 2 |
| 142 | const val SYNTHETIC_CLASS = 3 |
| 143 | const val MULTIPART_FACADE = 4 |
| 144 | |
| 145 | val FILE_OR_MULTIPART_FACADE_KINDS = listOf(FILE, MULTIPART_FACADE) |
| 146 | } |
| 147 | |
| 148 | fun ClassNode.isFileOrMultipartFacade() = kotlinClassKind.let { it != null && it in KotlinClassKind.FILE_OR_MULTIPART_FACADE_KINDS } |
| 149 | fun ClassNode.isDefaultImpls() = isInner() && name.endsWith("\$DefaultImpls") && kotlinClassKind == KotlinClassKind.SYNTHETIC_CLASS |
| 150 | |
| 151 | |
| 152 | val ClassNode.kotlinClassKind: Int? |
| 153 | get() = findAnnotation("kotlin/Metadata", false)?.get("k") as Int? |
| 154 | |
| 155 | fun ClassNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible) |
| 156 | fun MethodNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible) |
| 157 | fun FieldNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) = findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible) |
| 158 | |
| 159 | operator fun AnnotationNode.get(key: String): Any? = values.annotationValue(key) |
| 160 | |
| 161 | private fun List<Any>.annotationValue(key: String): Any? { |
| 162 | for (index in (0 .. size / 2 - 1)) { |
| 163 | if (this[index*2] == key) |
| 164 | return this[index*2 + 1] |
| 165 | } |
| 166 | return null |
| 167 | } |
| 168 | |
| 169 | private fun findAnnotation(annotationName: String, visibleAnnotations: List<AnnotationNode>?, invisibleAnnotations: List<AnnotationNode>?, includeInvisible: Boolean): AnnotationNode? = |
| 170 | visibleAnnotations?.firstOrNull { it.refersToName(annotationName) } ?: |
| 171 | if (includeInvisible) invisibleAnnotations?.firstOrNull { it.refersToName(annotationName) } else null |
| 172 | |
| 173 | fun AnnotationNode.refersToName(name: String) = desc.startsWith('L') && desc.endsWith(';') && desc.regionMatches(1, name, 0, name.length) |