All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.autonomousapps.internal.kotlin.asmUtils.kt Maven / Gradle / Ivy

There is a newer version: 2.0.2
Show newest version
/*
 * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 *
 * Copied from https://github.com/JetBrains/kotlin/tree/master/libraries/tools/binary-compatibility-validator
 */

package com.autonomousapps.internal.kotlin

import com.autonomousapps.internal.asm.Opcodes
import com.autonomousapps.internal.asm.tree.AnnotationNode
import com.autonomousapps.internal.asm.tree.ClassNode
import com.autonomousapps.internal.asm.tree.FieldNode
import com.autonomousapps.internal.asm.tree.InnerClassNode
import com.autonomousapps.internal.asm.tree.MethodNode
import kotlinx.metadata.jvm.*

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,
    val memberSignatures: List,
    val access: AccessFlags,
    val isEffectivelyPublic: Boolean,
    val isNotUsedWhenEmpty: Boolean,
    val annotations: List,
    val sourceFileLocation: String?
) {
  val canonicalName = name.replace("/", ".")
  val signature: String
    get() = "${access.getModifierString()} class $name" + if (supertypes.isEmpty()) "" else " : ${supertypes.joinToString()}"
}

interface MemberBinarySignature {
  val jvmMember: JvmMemberSignature
  val name: String get() = jvmMember.name
  val desc: String get() = jvmMember.desc
  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?): MemberVisibility? {
    return classVisibility?.findMember(jvmMember)
  }

  val signature: String
}

data class MethodBinarySignature(
    override val jvmMember: JvmMethodSignature,
    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()
          && !isDummyDefaultConstructor()

  override fun findMemberVisibility(classVisibility: ClassVisibility?): MemberVisibility? {
    return super.findMemberVisibility(classVisibility)
        ?: classVisibility?.let { alternateDefaultSignature(it.name)?.let(it::findMember) }
  }

  private fun isAccessOrAnnotationsMethod() = access.isSynthetic && (name.startsWith("access\$") || name.endsWith("\$annotations"))

  private fun isDummyDefaultConstructor() = access.isSynthetic && name == "" && desc == "(Lkotlin/jvm/internal/DefaultConstructorMarker;)V"

  /**
   * Calculates the signature of this method without default parameters
   *
   * Returns `null` if this method isn't an entry point of a function
   * or a constructor with default parameters.
   * Returns an incorrect result, if there are more than 31 default parameters.
   */
  private fun alternateDefaultSignature(className: String): JvmMethodSignature? {
    return when {
      !access.isSynthetic -> null
      name == "" && "ILkotlin/jvm/internal/DefaultConstructorMarker;" in desc ->
        JvmMethodSignature(name, desc.replace("ILkotlin/jvm/internal/DefaultConstructorMarker;", ""))
      name.endsWith("\$default") && "ILjava/lang/Object;)" in desc ->
        JvmMethodSignature(
            name.removeSuffix("\$default"),
            desc.replace("ILjava/lang/Object;)", ")").replace("(L$className;", "(")
        )
      else -> null
    }
  }
}

data class FieldBinarySignature(
    override val jvmMember: JvmFieldSignature,
    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? {
    return super.findMemberVisibility(classVisibility)
        ?: takeIf { access.isStatic }?.let { super.findMemberVisibility(classVisibility?.companionVisibilities) }
  }
}

private val MemberBinarySignature.kind: Int
  get() = when (this) {
    is FieldBinarySignature -> 1
    is MethodBinarySignature -> 2
    else -> error("Unsupported $this")
  }

val MEMBER_SORT_ORDER = compareBy(
    { 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 = 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


fun ClassNode.isDefaultImpls(metadata: KotlinClassMetadata?) = isInner() && name.endsWith("\$DefaultImpls") && metadata.isSyntheticClass()

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.annotationValue(key: String): Any? {
  for (index in (0 until size / 2)) {
    if (this[index * 2] == key)
      return this[index * 2 + 1]
  }
  return null
}

private fun findAnnotation(annotationName: String, visibleAnnotations: List?, invisibleAnnotations: List?, 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)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy