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

com.autonomousapps.internal.kotlin.PublicApiDump.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.AbiExclusions
import com.autonomousapps.internal.asm.ClassReader
import com.autonomousapps.internal.asm.Opcodes
import com.autonomousapps.internal.asm.tree.ClassNode
import com.autonomousapps.internal.utils.annotationTypes
import com.autonomousapps.internal.utils.appendReproducibleNewLine
import com.autonomousapps.internal.utils.filterNotToSet
import com.autonomousapps.internal.utils.genericTypes
import kotlinx.metadata.jvm.JvmFieldSignature
import kotlinx.metadata.jvm.JvmMethodSignature
import java.io.File
import java.io.InputStream
import java.io.PrintStream
import java.util.jar.JarFile

fun main(args: Array) {
  val src = args[0]
  println(src)
  println("------------------\n")
  getBinaryAPI(JarFile(src)).filterOutNonPublic().dump()
}

internal fun JarFile.classEntries() = Sequence { entries().iterator() }.filter {
  !it.isDirectory && it.name.endsWith(".class") && !it.name.startsWith("META-INF/")
}

internal fun getBinaryAPI(jar: JarFile, visibilityFilter: (String) -> Boolean = { true }): List =
  getBinaryAPI(jar.classEntries().map { entry -> jar.getInputStream(entry) }, visibilityFilter)

internal fun getBinaryAPI(classes: Set, visibilityFilter: (String) -> Boolean = { true }): List =
  getBinaryAPI(classes.asSequence().map { it.inputStream() }, visibilityFilter)

internal fun getBinaryAPI(classStreams: Sequence, visibilityFilter: (String) -> Boolean = { true }): List {
  val classNodes = classStreams.map {
    it.use { stream ->
      val classNode = ClassNode()
      ClassReader(stream).accept(classNode, ClassReader.SKIP_CODE)
      classNode
    }
  }.toSet() // eagerly transform this into a Set, because it will be iterated several times

  val moduleInfo = classNodes.find { it.name == "module-info" }
  val exportedPackages = moduleInfo?.module?.exportedPackages()

  val visibilityMapNew = classNodes.readKotlinVisibilities().filterKeys(visibilityFilter)

  return classNodes
      .filter { it != moduleInfo }
      .map { clazz ->
        with(clazz) {
          val metadata = kotlinMetadata
          val mVisibility = visibilityMapNew[name]
          val classAccess = AccessFlags(effectiveAccess and Opcodes.ACC_STATIC.inv())

          val supertypes = listOf(superName) - "java/lang/Object" + interfaces.sorted()

          val memberSignatures = (
              fields.map { field ->
                with(field) {
                  FieldBinarySignature(
                    jvmMember = JvmFieldSignature(name, desc),
                    genericTypes = signature?.genericTypes().orEmpty(),
                    annotations = visibleAnnotations.annotationTypes(),
                    invisibleAnnotations = invisibleAnnotations.annotationTypes(),
                    isPublishedApi = isPublishedApi(),
                    access = AccessFlags(access)
                  )
                }
              } + methods.map { method ->
                with(method) {
                  val parameterAnnotations = visibleParameterAnnotations.orEmpty()
                    .filterNotNull()
                    .flatMap { annos ->
                      annos
                        .filterNotNull()
                        .mapNotNull { it.desc }
                    }

                  val typeAnnotations = visibleTypeAnnotations.orEmpty()
                    .filterNotNull()
                    .map { it.desc }

                  MethodBinarySignature(
                    jvmMember = JvmMethodSignature(name, desc),
                    genericTypes = signature?.genericTypes().orEmpty(),
                    annotations = visibleAnnotations.annotationTypes(),
                    invisibleAnnotations = invisibleAnnotations.annotationTypes(),
                    parameterAnnotations = parameterAnnotations,
                    typeAnnotations = typeAnnotations,
                    isPublishedApi = isPublishedApi(),
                    access = AccessFlags(access),
                    // nb: MethodNode.exceptions is NOT expressed as a type descriptor, rather as a path.
                    // e.g., not `Lcom/example/Foo;`, but just `com/example/Foo`
                    exceptions = exceptions
                  )
                }
              }
            ).filter {
              it.isEffectivelyPublic(classAccess, exportedPackages?.contains(clazz.packageName())?:true, mVisibility)
            }

          val genericTypes = signature?.genericTypes().orEmpty()
            // Strip out JDK classes
            .filterNotToSet { it.startsWith("Ljava/lang") }

          ClassBinarySignature(
            name = name,
            superName = superName,
            outerName = outerClassName,
            supertypes = supertypes,
            genericTypes = genericTypes,
            memberSignatures = memberSignatures,
            access = classAccess,
            isEffectivelyPublic = isEffectivelyPublic(mVisibility),
            isNotUsedWhenEmpty = metadata.isFileOrMultipartFacade() || isDefaultImpls(metadata),
            annotations = visibleAnnotations.annotationTypes(),
            invisibleAnnotations = invisibleAnnotations.annotationTypes(),
            sourceFile = clazz.sourceFile
          )
        }
      }
      .asIterable()
      .sortedBy { it.name }
}

internal fun List.filterOutNonPublic(
  exclusions: AbiExclusions = AbiExclusions.NONE
): List {
  val classByName = associateBy { it.name }

  // Library note - this function (plus the exclusions parameter above) are modified from the original
  // Kotlin sources this was borrowed from.
  fun ClassBinarySignature.isExcluded(): Boolean {
    return (sourceFile?.let(exclusions::excludesPath) ?: false) ||
      exclusions.excludesClass(canonicalName) ||
      annotations.any(exclusions::excludesAnnotation) ||
      invisibleAnnotations.any(exclusions::excludesAnnotation) ||
      memberSignatures.any { it.annotations.any(exclusions::excludesAnnotation) }
  }

  fun ClassBinarySignature.isPublicAndAccessible(): Boolean =
      isEffectivelyPublic &&
          (outerName == null || classByName[outerName]?.let { outerClass ->
            !(this.access.isProtected && outerClass.access.isFinal)
                && outerClass.isPublicAndAccessible()
          } ?: true)

  fun supertypes(superName: String) = generateSequence({ classByName[superName] }, { classByName[it.superName] })

  fun ClassBinarySignature.flattenNonPublicBases(): ClassBinarySignature {

    val nonPublicSupertypes = supertypes(superName).takeWhile { !it.isPublicAndAccessible() }.toList()
    if (nonPublicSupertypes.isEmpty())
      return this

    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)
  }

  return filter {
    !it.isExcluded() && it.isPublicAndAccessible()
  }.map {
    it.flattenNonPublicBases()
  }.filterNot {
    it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty()
  }
}

internal fun List.dump(): PrintStream = dump(to = System.out)

internal fun  List.dump(to: T): T = to.apply {
  [email protected] { classBinarySig ->
    classBinarySig.annotations.forEach { anno ->
      appendReproducibleNewLine("@$anno")
    }
    append(classBinarySig.signature).appendReproducibleNewLine(" {")
    classBinarySig.memberSignatures.sortedWith(MEMBER_SORT_ORDER).forEach { memberBinarySig ->
      memberBinarySig.annotations.forEach { anno ->
        append("\t").appendReproducibleNewLine("@$anno")
      }
      append("\t").appendReproducibleNewLine(memberBinarySig.signature)
      if (memberBinarySig is MethodBinarySignature) {
        if (memberBinarySig.parameterAnnotations.isNotEmpty()) {
          appendReproducibleNewLine("\t- Parameter annotations:")
          memberBinarySig.parameterAnnotations.forEach { anno ->
            appendReproducibleNewLine("\t  - $anno")
          }
        }
        if (memberBinarySig.typeAnnotations.isNotEmpty()) {
          appendReproducibleNewLine("\t- Type annotations:")
          memberBinarySig.typeAnnotations.forEach { anno ->
            appendReproducibleNewLine("\t  - $anno")
          }
        }
      }
    }
    appendReproducibleNewLine("}\n")
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy