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

com.autonomousapps.internal.asm.kt Maven / Gradle / Ivy

There is a newer version: 2.0.2
Show newest version
package com.autonomousapps.internal

import com.autonomousapps.internal.ClassNames.canonicalize
import com.autonomousapps.internal.asm.*
import com.autonomousapps.internal.utils.METHOD_DESCRIPTOR_REGEX
import com.autonomousapps.internal.utils.efficient
import com.autonomousapps.internal.utils.genericTypes
import org.gradle.api.logging.Logger
import java.util.concurrent.atomic.AtomicReference
import kotlinx.metadata.jvm.Metadata

private var logDebug = true
private const val ASM_VERSION = Opcodes.ASM9

/**
 * This will collect the class name and information about annotations.
 */
internal class ClassNameAndAnnotationsVisitor(private val logger: Logger) : ClassVisitor(ASM_VERSION) {

  private lateinit var className: String
  private lateinit var access: Access
  private var outerClassName: String? = null
  private var superClassName: String? = null
  private val retentionPolicyHolder = AtomicReference("")
  private var isAnnotation = false
  private val methods = mutableSetOf()
  private val innerClasses = mutableSetOf()

  private var methodCount = 0
  private var fieldCount = 0

  // From old ConstantVisitor
  private val constantClasses = mutableSetOf()

  internal fun getAnalyzedClass(): AnalyzedClass {
    val className = this.className
    val access = this.access
    val hasNoMembers = fieldCount == 0 && methodCount == 0
    return AnalyzedClass(
      className = className,
      outerClassName = outerClassName,
      superClassName = superClassName,
      retentionPolicy = retentionPolicyHolder.get(),
      isAnnotation = isAnnotation,
      hasNoMembers = hasNoMembers,
      access = access,
      methods = methods.efficient(),
      innerClasses = innerClasses.efficient(),
      constantClasses = constantClasses
    )
  }

  override fun visit(
    version: Int,
    access: Int,
    name: String,
    signature: String?,
    superName: String?,
    interfaces: Array?
  ) {
    // This _must_ not be canonicalized, unless we also change accesses to be dotty instead of slashy
    superClassName = superName

    className = canonicalize(name)
    if (interfaces?.contains("java/lang/annotation/Annotation") == true) {
      isAnnotation = true
    }
    this.access = Access.fromInt(access)

    val implementsClause = if (interfaces.isNullOrEmpty()) {
      ""
    } else {
      " implements ${interfaces.joinToString(", ")}"
    }
    log("ClassNameAndAnnotationsVisitor#visit: ${this.access} $name extends $superName$implementsClause")
  }

  override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
    if ("Ljava/lang/annotation/Retention;" == descriptor) {
      log("- ClassNameAndAnnotationsVisitor#visitAnnotation ($className): descriptor=$descriptor visible=$visible")
      return RetentionPolicyAnnotationVisitor(logger, className, retentionPolicyHolder)
    }
    return null
  }

  override fun visitMethod(
    access: Int, name: String?, descriptor: String, signature: String?, exceptions: Array?
  ): MethodVisitor? {
    log("- visitMethod: descriptor=$descriptor name=$name signature=$signature")
    if (!("()V" == descriptor && ("" == name || "" == name))) {
      // ignore constructors and static initializers
      methodCount++
      methods.add(Method(descriptor))
    }
    return null
  }

  override fun visitField(
    access: Int, name: String, descriptor: String?, signature: String?, value: Any?
  ): FieldVisitor? {
    log("- visitField: descriptor=$descriptor name=$name signature=$signature value=$value")
    fieldCount++

    // from old ConstantVisitor
    if (isStaticFinal(access)) {
      constantClasses.add(name)
    }

    return null
  }

  override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
    log("- visitInnerClass: name=$name outerName=$outerName innerName=$innerName")
    if (outerName != null) {
      outerClassName = canonicalize(outerName)
    }
    innerClasses.add(canonicalize(name))
  }

  override fun visitSource(source: String?, debug: String?) {
    log("- visitSource: source=$source debug=$debug")
  }

  override fun visitEnd() {
    log("- visitEnd: fieldCount=$fieldCount methodCount=$methodCount")
  }

  private fun log(msg: String) {
    logger.debug(msg)
  }

  private class RetentionPolicyAnnotationVisitor(
    private val logger: Logger,
    private val className: String?,
    private val retentionPolicyHolder: AtomicReference
  ) : AnnotationVisitor(ASM_VERSION) {

    private fun log(msg: String) {
      logger.debug(msg)
    }

    override fun visitEnum(name: String?, descriptor: String?, value: String?) {
      if ("Ljava/lang/annotation/RetentionPolicy;" == descriptor) {
        log("  - RetentionPolicyAnnotationVisitor#visitEnum ($className): $value")
        retentionPolicyHolder.set(value)
      }
    }
  }
}

/**
 * This will collect the class name and the name of all classes used by this class and the methods of this class.
 */
internal class ClassAnalyzer(private val logger: Logger) : ClassVisitor(ASM_VERSION) {

  var source: String? = null
  lateinit var className: String
  val classes = mutableSetOf()

  private val methodAnalyzer = MethodAnalyzer(logger, classes)
  private val fieldAnalyzer = FieldAnalyzer(logger, classes)
  private val annotationAnalyzer = AnnotationAnalyzer(logger, classes)

  fun classes(): Pair> = source to classes

  private fun addClass(className: String?) {
    classes.addClass(className)
  }

  private fun log(msg: String) {
    if (logDebug) {
      logger.debug(msg)
    } else {
      logger.warn(msg)
    }
  }

  override fun visitSource(source: String?, debug: String?) {
    log("- visitSource: source=$source debug=$debug")
    this.source = source
  }

  override fun visit(
    version: Int,
    access: Int,
    name: String,
    signature: String?,
    superName: String?,
    interfaces: Array?
  ) {
    log("ClassAnalyzer#visit: $name extends $superName")
    className = name
    addClass("L$superName;")
    interfaces?.forEach { i ->
      addClass("L$i;")
    }
  }

  override fun visitField(
    access: Int,
    name: String?,
    descriptor: String?,
    signature: String?,
    value: Any?
  ): FieldVisitor {
    log("ClassAnalyzer#visitField: $descriptor $name")
    addClass(descriptor)
    // TODO probably do this for other `visitX` methods as well
    signature?.genericTypes()?.forEach {
      addClass(it)
    }
    return fieldAnalyzer
  }

  override fun visitMethod(
    access: Int,
    name: String?,
    descriptor: String?,
    signature: String?,
    exceptions: Array?
  ): MethodVisitor {
    log("ClassAnalyzer#visitMethod: $name $descriptor")

    descriptor?.let {
      METHOD_DESCRIPTOR_REGEX.findAll(it).forEach { result ->
        addClass(result.value)
      }
    }

    return methodAnalyzer
  }

  override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
    log("ClassAnalyzer#visitAnnotation: descriptor=$descriptor visible=$visible")
    addClass(descriptor)
    return annotationAnalyzer
  }

  override fun visitTypeAnnotation(
    typeRef: Int,
    typePath: TypePath?,
    descriptor: String?,
    visible: Boolean
  ): AnnotationVisitor {
    log("ClassAnalyzer#visitTypeAnnotation: typeRef=$typeRef typePath=$typePath descriptor=$descriptor visible=$visible")
    addClass(descriptor)
    return annotationAnalyzer
  }

  override fun visitEnd() {
    log("\n")
  }
}

private class MethodAnalyzer(
  private val logger: Logger,
  private val classes: MutableSet
) : MethodVisitor(ASM_VERSION) {

  private val annotationAnalyzer = AnnotationAnalyzer(logger, classes)

  private fun addClass(className: String?) {
    classes.addClass(className)
  }

  private fun log(msg: String) {
    if (logDebug) {
      logger.debug(msg)
    } else {
      logger.warn(msg)
    }
  }

  override fun visitTypeInsn(opcode: Int, type: String?) {
    log("- MethodAnalyzer#visitTypeInsn: $type")
    // Type can look like `java/lang/Enum` or `[Lcom/package/Thing;`, which is fucking weird
    addClass(if (type?.startsWith("[") == true) type else "L$type;")
  }

  override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) {
    log("- MethodAnalyzer#visitFieldInsn: $owner.$name $descriptor")
    addClass("L$owner;")
    addClass(descriptor)
  }

  override fun visitMethodInsn(
    opcode: Int,
    owner: String?,
    name: String?,
    descriptor: String?,
    isInterface: Boolean
  ) {
    log("- MethodAnalyzer#visitMethodInsn: $owner.$name $descriptor")
    // Owner can look like `java/lang/Enum` or `[Lcom/package/Thing;`, which is fucking weird
    addClass(if (owner?.startsWith("[") == true) owner else "L$owner;")
    descriptor?.let {
      METHOD_DESCRIPTOR_REGEX.findAll(it).forEach { result ->
        addClass(result.value)
      }
    }
  }

  override fun visitInvokeDynamicInsn(
    name: String?,
    descriptor: String?,
    bootstrapMethodHandle: Handle?,
    vararg bootstrapMethodArguments: Any?
  ) {
    log("- MethodAnalyzer#visitInvokeDynamicInsn: $name $descriptor")
    addClass(descriptor)
  }

  override fun visitLocalVariable(
    name: String?,
    descriptor: String?,
    signature: String?,
    start: Label?,
    end: Label?,
    index: Int
  ) {
    log("- MethodAnalyzer#visitLocalVariable: $name $descriptor")
    addClass(descriptor)
  }

  override fun visitLocalVariableAnnotation(
    typeRef: Int,
    typePath: TypePath?,
    start: Array?,
    end: Array?,
    index: IntArray?,
    descriptor: String?,
    visible: Boolean
  ): AnnotationVisitor {
    log("- MethodAnalyzer#visitLocalVariableAnnotation: $descriptor")
    addClass(descriptor)
    return annotationAnalyzer
  }

  override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
    log("- MethodAnalyzer#visitAnnotation: $descriptor")
    addClass(descriptor)
    return annotationAnalyzer
  }

  override fun visitInsnAnnotation(
    typeRef: Int,
    typePath: TypePath?,
    descriptor: String?,
    visible: Boolean
  ): AnnotationVisitor {
    log("- MethodAnalyzer#visitInsnAnnotation: $descriptor")
    addClass(descriptor)
    return annotationAnalyzer
  }

  override fun visitParameterAnnotation(parameter: Int, descriptor: String?, visible: Boolean): AnnotationVisitor {
    log("- MethodAnalyzer#visitParameterAnnotation: $descriptor")
    addClass(descriptor)
    return annotationAnalyzer
  }

  override fun visitTypeAnnotation(
    typeRef: Int,
    typePath: TypePath?,
    descriptor: String?,
    visible: Boolean
  ): AnnotationVisitor {
    log("- MethodAnalyzer#visitTypeAnnotation: $descriptor")
    addClass(descriptor)
    return annotationAnalyzer
  }

  override fun visitTryCatchBlock(start: Label?, end: Label?, handler: Label?, type: String?) {
    log("- MethodAnalyzer#visitTryCatchBlock: $type")
    addClass("L$type;")
  }

  override fun visitTryCatchAnnotation(
    typeRef: Int,
    typePath: TypePath?,
    descriptor: String?,
    visible: Boolean
  ): AnnotationVisitor {
    log("- MethodAnalyzer#visitTryCatchAnnotation: $descriptor")
    addClass(descriptor)
    return annotationAnalyzer
  }
}

private class AnnotationAnalyzer(
  private val logger: Logger,
  private val classes: MutableSet,
  private val level: Int = 0
) : AnnotationVisitor(ASM_VERSION) {

  private fun addClass(className: String?) {
    classes.addClass(className)
  }

  private fun log(msg: String) {
    if (logDebug) {
      logger.debug(msg)
    } else {
      logger.warn(msg)
    }
  }

  private fun indent() = "  ".repeat(level)

  override fun visit(name: String?, value: Any?) {
    fun getValue(value: Any?): String {
      return if (value is String && value.contains("\n")) {
        ""
      } else {
        value.toString()
      }
    }

    log("${indent()}- AnnotationAnalyzer#visit: name=$name, value=(${value?.javaClass?.simpleName}, ${getValue(value)})")
    if (value is String) {
      METHOD_DESCRIPTOR_REGEX.findAll(value).forEach { result ->
        addClass(result.value)
      }
    } else if (value is Type) {
      addClass(value.descriptor)
    }
  }

  override fun visitEnum(name: String?, descriptor: String?, value: String?) {
    log("${indent()}- AnnotationAnalyzer#visitEnum: name=$name, descriptor=$descriptor, value=$value")
    addClass(descriptor)
  }

  override fun visitAnnotation(name: String?, descriptor: String?): AnnotationVisitor {
    log("${indent()}- AnnotationAnalyzer#visitAnnotation: name=$name, descriptor=$descriptor")
    addClass(descriptor)
    return AnnotationAnalyzer(logger, classes, level + 1)
  }

  override fun visitArray(name: String?): AnnotationVisitor {
    log("${indent()}- AnnotationAnalyzer#visitArray: name=$name")
    return AnnotationAnalyzer(logger, classes, level + 1)
  }
}

private class FieldAnalyzer(
  private val logger: Logger,
  private val classes: MutableSet
) : FieldVisitor(ASM_VERSION) {

  private val annotationAnalyzer = AnnotationAnalyzer(logger, classes)

  private fun log(msg: String) {
    if (logDebug) {
      logger.debug(msg)
    } else {
      logger.warn(msg)
    }
  }

  private fun addClass(className: String?) {
    classes.addClass(className)
  }

  override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
    log("- FieldAnalyzer#visitAnnotation: $descriptor")
    addClass(descriptor)
    return annotationAnalyzer
  }
}

private fun MutableSet.addClass(className: String?) {
  className?.let {
    // Strip array indicators
    it.replace("[", "")
    // Only add class types (not primitives)
    if (it.startsWith("L")) {
      add(it.substring(1, it.length - 1))
    }
  }
}

/* ===================================================================================================
 * Below here used for parsing kotlin.Metadata with the ultimate goal of listing all inline functions.
 * ===================================================================================================
 */

internal class KotlinClassHeaderBuilder {

  var kind: Int = 1
  var metadataVersion: IntArray? = null
  var bytecodeVersion: IntArray? = null
  var data1 = mutableListOf()
  var data2 = mutableListOf()
  var extraString: String? = null
  var packageName: String? = null
  var extraInt: Int = 0

  fun build(): Metadata {
    return Metadata(
      kind = kind,
      metadataVersion = metadataVersion,
      data1 = data1.toTypedArray(),
      data2 = data2.toTypedArray(),
      extraString = extraString,
      packageName = packageName,
      extraInt = extraInt
    )
  }
}

private const val KOTLIN_METADATA = "Lkotlin/Metadata;"

internal class KotlinMetadataVisitor(
  private val logger: Logger
) : ClassVisitor(ASM_VERSION) {

  internal lateinit var className: String
  internal var builder: KotlinClassHeaderBuilder? = null

  private fun log(msg: String) {
    if (logDebug) {
      logger.debug(msg)
    } else {
      logger.warn(msg)
    }
  }

  override fun visit(
    version: Int,
    access: Int,
    name: String,
    signature: String?,
    superName: String?,
    interfaces: Array?
  ) {
    log("KotlinMetadataVisitor#visit: $name extends $superName")
    className = name
  }

  override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
    log("KotlinMetadataVisitor#visitAnnotation: descriptor=$descriptor visible=$visible")
    return if (KOTLIN_METADATA == descriptor) {
      builder = KotlinClassHeaderBuilder()
      KotlinAnnotationVisitor(logger, builder!!)
    } else {
      null
    }
  }

  private class KotlinAnnotationVisitor(
    private val logger: Logger,
    private val builder: KotlinClassHeaderBuilder,
    private val level: Int = 0,
    private val arrayName: String? = null
  ) : AnnotationVisitor(ASM_VERSION) {

    private fun log(msg: String) {
      if (logDebug) {
        logger.debug(msg)
      } else {
        logger.warn(msg)
      }
    }

    private fun indent() = "  ".repeat(level)

    override fun visit(name: String?, value: Any?) {
      log("${indent()}- visit: name=$name, value=${if (value == null) "null" else "..."})")
      when (name) {
        "k" -> builder.kind = value as Int
        "mv" -> builder.metadataVersion = value as IntArray
        "bv" -> builder.bytecodeVersion = value as IntArray
        "xs" -> builder.extraString = value as String
        "pn" -> builder.packageName = value as String
        "xi" -> builder.extraInt = value as Int
      }

      when (arrayName) {
        "d1" -> builder.data1.add(value as String)
        "d2" -> builder.data2.add(value as String)
      }
    }

    override fun visitArray(name: String?): AnnotationVisitor {
      log("${indent()}- visitArray: name=$name")
      return KotlinAnnotationVisitor(logger, builder, level + 1, name)
    }
  }
}

private fun isStaticFinal(access: Int): Boolean =
  access and Opcodes.ACC_STATIC != 0 && access and Opcodes.ACC_FINAL != 0




© 2015 - 2024 Weber Informatics LLC | Privacy Policy