Add JVM ABI K1/K2 consistency tests
This commit is contained in:
committed by
Space Team
parent
5a1fb78fcd
commit
34bac48541
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(commonDependency("org.jetbrains.intellij.deps:asm-all"))
|
||||
implementation("org.apache.commons:commons-text:1.10.0")
|
||||
|
||||
implementation(project(":kotlin-stdlib-jdk8"))
|
||||
implementation(project(":tools:kotlinp"))
|
||||
implementation(project(":kotlinx-metadata-jvm"))
|
||||
implementation(project(":kotlinx-metadata"))
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||
}
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
|
||||
class AndFlag(private val flag: Int, private val name: String) : (Int) -> String? {
|
||||
override fun invoke(p1: Int): String? {
|
||||
return if (p1 and flag != 0) name else null
|
||||
}
|
||||
}
|
||||
|
||||
class AndNotFlag(private val flag: Int, private val name: String) : (Int) -> String? {
|
||||
override fun invoke(p1: Int): String? {
|
||||
return if (p1 and flag == 0) name else null
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.isPrivate() =
|
||||
this and Opcodes.ACC_PRIVATE != 0
|
||||
|
||||
fun Int.isSynthetic() =
|
||||
this and Opcodes.ACC_SYNTHETIC != 0
|
||||
|
||||
fun Int.isBridge() =
|
||||
this and Opcodes.ACC_BRIDGE != 0
|
||||
|
||||
fun Int.classFlags() =
|
||||
flagsList(CLASS_FLAGS)
|
||||
|
||||
fun Int.methodFlags() =
|
||||
flagsList(METHOD_FLAGS)
|
||||
|
||||
fun Int.fieldFlags() =
|
||||
flagsList(FIELD_FLAGS)
|
||||
|
||||
private fun Int.flagsList(flags: List<(Int) -> String?>) =
|
||||
flags.mapNotNull { flag ->
|
||||
flag(this)
|
||||
}.joinToString(prefix = "[", postfix = "]") { it }
|
||||
|
||||
|
||||
val CLASS_FLAGS = listOf(
|
||||
AndNotFlag(Opcodes.ACC_PUBLIC + Opcodes.ACC_PROTECTED + Opcodes.ACC_PRIVATE, "package-private"),
|
||||
AndFlag(Opcodes.ACC_PUBLIC, "public"),
|
||||
AndFlag(Opcodes.ACC_PRIVATE, "private"),
|
||||
AndFlag(Opcodes.ACC_PROTECTED, "protected"),
|
||||
AndFlag(Opcodes.ACC_STATIC, "static"),
|
||||
AndFlag(Opcodes.ACC_FINAL, "final"),
|
||||
AndFlag(Opcodes.ACC_SUPER, "super"),
|
||||
AndFlag(Opcodes.ACC_INTERFACE, "interface"),
|
||||
AndFlag(Opcodes.ACC_ABSTRACT, "abstract"),
|
||||
AndFlag(Opcodes.ACC_SYNTHETIC, "synthetic"),
|
||||
AndFlag(Opcodes.ACC_ANNOTATION, "annotation"),
|
||||
AndFlag(Opcodes.ACC_ENUM, "enum"),
|
||||
AndFlag(Opcodes.ACC_MODULE, "module)"),
|
||||
AndFlag(Opcodes.ACC_DEPRECATED, "deprecated")
|
||||
)
|
||||
|
||||
val METHOD_FLAGS = listOf(
|
||||
AndNotFlag(Opcodes.ACC_PUBLIC + Opcodes.ACC_PROTECTED + Opcodes.ACC_PRIVATE, "package-private"),
|
||||
AndFlag(Opcodes.ACC_PUBLIC, "public"),
|
||||
AndFlag(Opcodes.ACC_PRIVATE, "private"),
|
||||
AndFlag(Opcodes.ACC_PROTECTED, "protected"),
|
||||
AndFlag(Opcodes.ACC_STATIC, "static"),
|
||||
AndFlag(Opcodes.ACC_FINAL, "final"),
|
||||
AndFlag(Opcodes.ACC_SYNCHRONIZED, "synchronized"),
|
||||
AndFlag(Opcodes.ACC_BRIDGE, "bridge"),
|
||||
AndFlag(Opcodes.ACC_VARARGS, "vararg"),
|
||||
AndFlag(Opcodes.ACC_NATIVE, "native"),
|
||||
AndFlag(Opcodes.ACC_ABSTRACT, "abstract"),
|
||||
AndFlag(Opcodes.ACC_STRICT, "strict"),
|
||||
AndFlag(Opcodes.ACC_SYNTHETIC, "synthetic"),
|
||||
AndFlag(Opcodes.ACC_DEPRECATED, "deprecated")
|
||||
)
|
||||
|
||||
val FIELD_FLAGS = listOf(
|
||||
AndNotFlag(Opcodes.ACC_PUBLIC + Opcodes.ACC_PROTECTED + Opcodes.ACC_PRIVATE, "package-private"),
|
||||
AndFlag(Opcodes.ACC_PUBLIC, "public"),
|
||||
AndFlag(Opcodes.ACC_PRIVATE, "private"),
|
||||
AndFlag(Opcodes.ACC_PROTECTED, "protected"),
|
||||
AndFlag(Opcodes.ACC_STATIC, "static"),
|
||||
AndFlag(Opcodes.ACC_FINAL, "final"),
|
||||
AndFlag(Opcodes.ACC_VOLATILE, "volatile"),
|
||||
AndFlag(Opcodes.ACC_TRANSIENT, "transient"),
|
||||
AndFlag(Opcodes.ACC_SYNTHETIC, "synthetic"),
|
||||
AndFlag(Opcodes.ACC_ENUM, "enum"),
|
||||
AndFlag(Opcodes.ACC_DEPRECATED, "deprecated")
|
||||
)
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.ListEntryDiff
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
val IGNORED_ANNOTATIONS = listOf("Lkotlin/Metadata;", "Lkotlin/coroutines/jvm/internal/DebugMetadata;")
|
||||
|
||||
fun compareAnnotations(
|
||||
annotations1: List<AnnotationEntry>,
|
||||
annotations2: List<AnnotationEntry>,
|
||||
): List<ListEntryDiff>? {
|
||||
val anns1Sorted = annotations1.preprocessAnnotations()
|
||||
val anns2Sorted = annotations2.preprocessAnnotations()
|
||||
|
||||
val result = ArrayList<ListEntryDiff>()
|
||||
|
||||
var i1 = 0
|
||||
var i2 = 0
|
||||
val size1 = anns1Sorted.size
|
||||
val size2 = anns2Sorted.size
|
||||
while (i1 < size1 || i2 < size2) {
|
||||
if (i1 < size1 && i2 < size2) {
|
||||
val ann1 = anns1Sorted[i1]
|
||||
val ann2 = anns2Sorted[i2]
|
||||
|
||||
// TODO proper comparison for annotation argument values?
|
||||
if (ann1.fullString() == ann2.fullString()) {
|
||||
++i1
|
||||
++i2
|
||||
} else {
|
||||
when {
|
||||
ann1.desc == ann2.desc -> {
|
||||
++i1
|
||||
++i2
|
||||
result.add(ListEntryDiff(ann1.fullString(), ann2.fullString()))
|
||||
}
|
||||
ann1.desc < ann2.desc -> {
|
||||
++i1
|
||||
result.add(ListEntryDiff(ann1.fullString(), null))
|
||||
}
|
||||
else -> {
|
||||
++i2
|
||||
result.add(ListEntryDiff(null, ann2.fullString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (i1 < size1) {
|
||||
val ann1 = anns1Sorted[i1]
|
||||
++i1
|
||||
result.add(ListEntryDiff(ann1.fullString(), null))
|
||||
} else {
|
||||
val ann2 = anns2Sorted[i2]
|
||||
++i2
|
||||
result.add(ListEntryDiff(null, ann2.fullString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (result.isEmpty()) null else result
|
||||
}
|
||||
|
||||
private fun List<AnnotationEntry>.preprocessAnnotations() =
|
||||
filter { it.desc !in IGNORED_ANNOTATIONS }.sortedBy { it.fullString() }
|
||||
|
||||
private fun AnnotationEntry.shortString(): String =
|
||||
if (values.isEmpty())
|
||||
"@$desc"
|
||||
else
|
||||
"@$desc(...)"
|
||||
|
||||
private fun AnnotationEntry.fullString(): String =
|
||||
if (values.isEmpty())
|
||||
"@$desc"
|
||||
else
|
||||
"@$desc( ${values.joinToString { it.toValueString() }} )"
|
||||
|
||||
private fun Pair<String, Any?>.toValueString(): String =
|
||||
"$first: ${second.toValueString()}"
|
||||
|
||||
private fun Any?.toValueString(): String =
|
||||
when (this) {
|
||||
null -> "NULL"
|
||||
is Type -> "<$descriptor>"
|
||||
is List<*> -> joinToString(separator = ", ", prefix = "#{ ", postfix = " }") { it.toValueString() }
|
||||
is Array<*> -> toList().toValueString()
|
||||
else -> toString()
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.tree.AnnotationNode
|
||||
|
||||
data class AnnotationEntry(val desc: String, val values: List<Pair<String, Any?>>)
|
||||
|
||||
fun List<Any?>?.toAnnotations() =
|
||||
this?.run {
|
||||
mapNotNull { it.toAnnotation() }
|
||||
.sortedBy { it.desc }
|
||||
} ?: emptyList()
|
||||
|
||||
fun Any?.toAnnotation(): AnnotationEntry? {
|
||||
val ann = this as? AnnotationNode ?: return null
|
||||
val annValues = ann.values.orEmpty()
|
||||
val iter = annValues.iterator()
|
||||
val values = ArrayList<Pair<String, Any?>>()
|
||||
while (iter.hasNext()) {
|
||||
val key = iter.next() as String
|
||||
val value = iter.next()!!.toAnnotationArgumentValue()
|
||||
values.add(key to value)
|
||||
}
|
||||
return AnnotationEntry(ann.desc, values.sortedBy { it.first })
|
||||
}
|
||||
|
||||
fun Any.toAnnotationArgumentValue(): Any =
|
||||
when (this) {
|
||||
is Array<*> -> map { it!!.toAnnotationArgumentValue() }
|
||||
is List<*> -> map { it!!.toAnnotationArgumentValue() }
|
||||
is AnnotationNode -> this.toAnnotation()!!
|
||||
else -> this
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.defects.DefectType
|
||||
import org.jetbrains.kotlin.abicmp.defects.VALUE1_A
|
||||
import org.jetbrains.kotlin.abicmp.defects.VALUE2_A
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
interface Checker {
|
||||
val name: String
|
||||
}
|
||||
|
||||
|
||||
abstract class PropertyChecker<T, E>(final override val name: String) : Checker {
|
||||
protected val defectType = DefectType(this.name, "Attribute value mismatch", VALUE1_A, VALUE2_A)
|
||||
|
||||
protected open fun areEqual(value1: T, value2: T) =
|
||||
value1 == value2
|
||||
|
||||
protected open fun valueToHtml(value: T, other: T): String = value?.toString() ?: "NULL"
|
||||
|
||||
protected abstract fun getProperty(node: E): T
|
||||
}
|
||||
|
||||
|
||||
abstract class AnnotationsChecker<N>(
|
||||
final override val name: String,
|
||||
private val annotationsProperty: KProperty1<N, List<Any?>?>,
|
||||
) : Checker {
|
||||
|
||||
protected fun getAnnotations(node: N) =
|
||||
annotationsProperty.get(node).orEmpty().toAnnotations()
|
||||
|
||||
val mismatchDefect = DefectType("${name}.mismatch", "Annotation value mismatch", VALUE1_A, VALUE2_A)
|
||||
val missing1Defect = DefectType("${name}.missing1", "Missing annotation in #1", VALUE2_A)
|
||||
val missing2Defect = DefectType("${name}.missing2", "Missing annotation in #2", VALUE1_A)
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
interface ClassChecker : Checker {
|
||||
fun check(class1: ClassNode, class2: ClassNode, report: ClassReport)
|
||||
}
|
||||
|
||||
abstract class ClassPropertyChecker<T>(name: String) :
|
||||
PropertyChecker<T, ClassNode>("class.$name"),
|
||||
ClassChecker {
|
||||
|
||||
override fun check(class1: ClassNode, class2: ClassNode, report: ClassReport) {
|
||||
val value1 = getProperty(class1)
|
||||
val value2 = getProperty(class2)
|
||||
if (!areEqual(value1, value2)) {
|
||||
report.addPropertyDiff(
|
||||
defectType,
|
||||
NamedDiffEntry(
|
||||
name,
|
||||
valueToHtml(value1, value2),
|
||||
valueToHtml(value2, value1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> classPropertyChecker(name: String, crossinline get: (ClassNode) -> T) =
|
||||
object : ClassPropertyChecker<T>(name) {
|
||||
override fun getProperty(node: ClassNode): T =
|
||||
get(node)
|
||||
}
|
||||
|
||||
fun <T> classPropertyChecker(classProperty: KProperty1<ClassNode, T>) =
|
||||
classPropertyChecker(classProperty.name) { classProperty.get(it) }
|
||||
|
||||
inline fun <T> classPropertyChecker(classProperty: KProperty1<ClassNode, T>, crossinline html: (T) -> String) =
|
||||
object : ClassPropertyChecker<T>(classProperty.name) {
|
||||
override fun getProperty(node: ClassNode): T =
|
||||
classProperty.get(node)
|
||||
|
||||
override fun valueToHtml(value: T, other: T): String =
|
||||
html(value)
|
||||
}
|
||||
|
||||
fun <T> classPropertyChecker(name: String, classProperty: KProperty1<ClassNode, T>) =
|
||||
classPropertyChecker(name) { classProperty.get(it) }
|
||||
|
||||
class ClassAnnotationsChecker(annotationsProperty: KProperty1<ClassNode, List<Any?>?>) :
|
||||
AnnotationsChecker<ClassNode>("class.${annotationsProperty.name}", annotationsProperty),
|
||||
ClassChecker {
|
||||
|
||||
override fun check(class1: ClassNode, class2: ClassNode, report: ClassReport) {
|
||||
val anns1 = getAnnotations(class1)
|
||||
val anns2 = getAnnotations(class2)
|
||||
report.addAnnotationDiffs(this, compareAnnotations(anns1, anns2) ?: return)
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassMetadataReport
|
||||
|
||||
interface ClassMetadataChecker : Checker {
|
||||
fun check(metadata1: KotlinClassMetadata.Class, metadata2: KotlinClassMetadata.Class, report: ClassMetadataReport)
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassMetadataReport
|
||||
|
||||
abstract class ClassMetadataListChecker(name: String) : ClassMetadataChecker {
|
||||
override fun check(metadata1: KotlinClassMetadata.Class, metadata2: KotlinClassMetadata.Class, report: ClassMetadataReport) {
|
||||
val list1 = getList(metadata1)
|
||||
val list2 = getList(metadata2)
|
||||
|
||||
val diff = compareLists(list1.sorted(), list2.sorted()) ?: return
|
||||
|
||||
report.addMembersListDiffs(diff)
|
||||
}
|
||||
|
||||
abstract fun getList(metadata: KotlinClassMetadata.Class): List<String>
|
||||
|
||||
override val name: String = "class.metadata.$name"
|
||||
}
|
||||
|
||||
fun classMetadataListChecker(name: String, listGetter: (KotlinClassMetadata.Class) -> List<String>) =
|
||||
object : ClassMetadataListChecker(name) {
|
||||
override fun getList(metadata: KotlinClassMetadata.Class) = listGetter(metadata)
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassMetadataReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
|
||||
abstract class ClassMetadataPropertyChecker(name: String) : PropertyChecker<String, KotlinClassMetadata.Class>("class.metadata.$name"),
|
||||
ClassMetadataChecker {
|
||||
|
||||
override fun check(metadata1: KotlinClassMetadata.Class, metadata2: KotlinClassMetadata.Class, report: ClassMetadataReport) {
|
||||
val value1 = getProperty(metadata1)
|
||||
val value2 = getProperty(metadata2)
|
||||
if (!areEqual(value1, value2)) {
|
||||
report.addPropertyDiff(NamedDiffEntry(name, valueToHtml(value1, value2), valueToHtml(value2, value1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun classMetadataPropertyChecker(name: String, propertyGetter: (KotlinClassMetadata.Class) -> String) =
|
||||
object : ClassMetadataPropertyChecker(name) {
|
||||
override fun getProperty(node: KotlinClassMetadata.Class) = propertyGetter(node)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.FieldReport
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
class FieldAnnotationsChecker(annotationsProperty: KProperty1<FieldNode, List<Any?>?>) :
|
||||
AnnotationsChecker<FieldNode>("field.${annotationsProperty.name}", annotationsProperty),
|
||||
FieldChecker {
|
||||
|
||||
override fun check(field1: FieldNode, field2: FieldNode, report: FieldReport) {
|
||||
val anns1 = getAnnotations(field1)
|
||||
val anns2 = getAnnotations(field2)
|
||||
val annDiff = compareAnnotations(anns1, anns2) ?: return
|
||||
report.addAnnotationDiffs(this, annDiff)
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.FieldReport
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
interface FieldChecker : Checker {
|
||||
fun check(field1: FieldNode, field2: FieldNode, report: FieldReport)
|
||||
}
|
||||
|
||||
inline fun <T> fieldPropertyChecker(name: String, crossinline get: (FieldNode) -> T) =
|
||||
object : FieldPropertyChecker<T>(name) {
|
||||
override fun getProperty(node: FieldNode): T =
|
||||
get(node)
|
||||
}
|
||||
|
||||
fun <T> fieldPropertyChecker(fieldProperty: KProperty1<FieldNode, T>) =
|
||||
fieldPropertyChecker(fieldProperty.name) { fieldProperty.get(it) }
|
||||
|
||||
fun <T> fieldPropertyChecker(name: String, fieldProperty: KProperty1<FieldNode, T>) =
|
||||
fieldPropertyChecker(name) { fieldProperty.get(it) }
|
||||
|
||||
inline fun <T> fieldPropertyChecker(fieldProperty: KProperty1<FieldNode, T>, crossinline html: (T) -> String) =
|
||||
object : FieldPropertyChecker<T>(fieldProperty.name) {
|
||||
override fun getProperty(node: FieldNode): T =
|
||||
fieldProperty.get(node)
|
||||
|
||||
override fun valueToHtml(value: T, other: T): String =
|
||||
html(value)
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.FieldReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
|
||||
abstract class FieldPropertyChecker<T>(name: String) :
|
||||
PropertyChecker<T, FieldNode>("field.$name"),
|
||||
FieldChecker {
|
||||
|
||||
override fun check(field1: FieldNode, field2: FieldNode, report: FieldReport) {
|
||||
val value1 = getProperty(field1)
|
||||
val value2 = getProperty(field2)
|
||||
if (!areEqual(value1, value2)) {
|
||||
report.addPropertyDiff(
|
||||
defectType,
|
||||
NamedDiffEntry(
|
||||
name,
|
||||
valueToHtml(value1, value2),
|
||||
valueToHtml(value2, value1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.defects.DefectType
|
||||
import org.jetbrains.kotlin.abicmp.defects.FIELD_A
|
||||
import org.jetbrains.kotlin.abicmp.fieldFlags
|
||||
import org.jetbrains.kotlin.abicmp.isSynthetic
|
||||
import org.jetbrains.kotlin.abicmp.listOfNotNull
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.ListEntryDiff
|
||||
import org.jetbrains.kotlin.abicmp.tasks.fieldId
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
|
||||
class FieldsListChecker : ClassChecker {
|
||||
override val name = "class.fields"
|
||||
|
||||
val missing1Defect = DefectType("${name}.missing1", "Missing field in #1", FIELD_A)
|
||||
val missing2Defect = DefectType("${name}.missing2", "Missing field in #2", FIELD_A)
|
||||
|
||||
override fun check(class1: ClassNode, class2: ClassNode, report: ClassReport) {
|
||||
val fields1 = class1.loadFields()
|
||||
val fields2 = class2.loadFields()
|
||||
|
||||
val relevantFieldIds = fields1.keys.union(fields2.keys)
|
||||
.filter {
|
||||
val field1 = fields1[it]
|
||||
val field2 = fields2[it]
|
||||
!(field1 != null && !field1.access.isSynthetic() ||
|
||||
field2 != null && !field2.access.isSynthetic())
|
||||
}.toSet()
|
||||
|
||||
val fieldIds1 = fields1.keys.intersect(relevantFieldIds).sorted()
|
||||
|
||||
val fieldIds2 = fields2.keys.intersect(relevantFieldIds).sorted()
|
||||
|
||||
val listDiff = compareLists(fieldIds1, fieldIds2) ?: return
|
||||
report.addFieldListDiffs(
|
||||
this,
|
||||
listDiff.map {
|
||||
ListEntryDiff(
|
||||
it.value1?.toFieldWithFlags(fields1),
|
||||
it.value2?.toFieldWithFlags(fields2)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.toFieldWithFlags(fields: Map<String, FieldNode>): String {
|
||||
val field = fields[this] ?: return this
|
||||
return "$this ${field.access.fieldFlags()}"
|
||||
}
|
||||
}
|
||||
|
||||
fun ClassNode.loadFields(): Map<String, FieldNode> =
|
||||
fields.listOfNotNull<FieldNode>().filter {
|
||||
(it.access and Opcodes.ACC_PUBLIC) != 0 ||
|
||||
(it.access and Opcodes.ACC_PROTECTED) != 0
|
||||
}.associateBy { it.fieldId() }
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.KmConstructor
|
||||
import kotlinx.metadata.KmFunction
|
||||
import kotlinx.metadata.KmProperty
|
||||
import kotlinx.metadata.KmTypeAlias
|
||||
import org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
|
||||
interface GenericMetadataChecker<T> : Checker {
|
||||
fun check(metadata1: T, metadata2: T, report: MetadataPropertyReport)
|
||||
}
|
||||
|
||||
abstract class GenericMetadataPropertyChecker<T>(name: String) :
|
||||
PropertyChecker<String, T>("class.metadata.$name"),
|
||||
GenericMetadataChecker<T> {
|
||||
|
||||
override fun check(metadata1: T, metadata2: T, report: MetadataPropertyReport) {
|
||||
val value1 = getProperty(metadata1)
|
||||
val value2 = getProperty(metadata2)
|
||||
if (!areEqual(value1, value2)) {
|
||||
report.addPropertyDiff(NamedDiffEntry(name, value1, value2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun constructorMetadataPropertyChecker(name: String, propertyGetter: (KmConstructor) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmConstructor>("constructor.$name") {
|
||||
override fun getProperty(node: KmConstructor) = propertyGetter(node)
|
||||
}
|
||||
|
||||
fun functionMetadataPropertyChecker(name: String, propertyGetter: (KmFunction) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmFunction>("function.$name") {
|
||||
override fun getProperty(node: KmFunction) = propertyGetter(node)
|
||||
}
|
||||
|
||||
fun typeAliasMetadataPropertyChecker(name: String, propertyGetter: (KmTypeAlias) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmTypeAlias>("typeAlias.$name") {
|
||||
override fun getProperty(node: KmTypeAlias) = propertyGetter(node)
|
||||
}
|
||||
|
||||
fun propertyMetadataPropertyChecker(name: String, propertyGetter: (KmProperty) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmProperty>("property.$name") {
|
||||
override fun getProperty(node: KmProperty) = propertyGetter(node)
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.classFlags
|
||||
import org.jetbrains.kotlin.abicmp.defects.DefectType
|
||||
import org.jetbrains.kotlin.abicmp.defects.INNER_CLASS_A
|
||||
import org.jetbrains.kotlin.abicmp.isSynthetic
|
||||
import org.jetbrains.kotlin.abicmp.listOfNotNull
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.ListEntryDiff
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.InnerClassNode
|
||||
|
||||
class InnerClassesListChecker : ClassChecker {
|
||||
override val name = "class.innerClasses"
|
||||
|
||||
val missing1Defect = DefectType("${name}.missing1", "Missing inner class in #1", INNER_CLASS_A)
|
||||
val missing2Defect = DefectType("${name}.missing2", "Missing inner class in #2", INNER_CLASS_A)
|
||||
|
||||
override fun check(class1: ClassNode, class2: ClassNode, report: ClassReport) {
|
||||
val innerClasses1 = class1.loadInnerClasses()
|
||||
val innerClasses2 = class2.loadInnerClasses()
|
||||
|
||||
val relevantInnerClassNames =
|
||||
innerClasses1.keys.union(innerClasses2.keys).filter {
|
||||
val ic1 = innerClasses1[it]
|
||||
val ic2 = innerClasses2[it]
|
||||
ic1 != null && !ic1.access.isSynthetic() ||
|
||||
ic2 != null && ic2.access.isSynthetic()
|
||||
}
|
||||
val innerClassNames1 = innerClasses1.keys.filter { it in relevantInnerClassNames }.sorted()
|
||||
val innerClassNames2 = innerClasses2.keys.filter { it in relevantInnerClassNames }.sorted()
|
||||
|
||||
val listDiff = compareLists(innerClassNames1, innerClassNames2) ?: return
|
||||
|
||||
report.addInnerClassesDiffs(
|
||||
this,
|
||||
listDiff.map {
|
||||
ListEntryDiff(
|
||||
it.value1?.let { v1 -> innerClasses1[v1]?.toInnerClassLine() },
|
||||
it.value2?.let { v2 -> innerClasses2[v2]?.toInnerClassLine() }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun ClassNode.loadInnerClasses(): Map<String, InnerClassNode> =
|
||||
innerClasses.listOfNotNull<InnerClassNode>()
|
||||
.filterNot {
|
||||
it.innerName == null || it.innerName == "WhenMappings" || isSamAdapterName(it.name)
|
||||
}
|
||||
.associateBy { it.name }
|
||||
|
||||
|
||||
private fun InnerClassNode.toInnerClassLine(): String =
|
||||
"INNER_CLASS $name $outerName $innerName ${access.toString(2)} ${access.classFlags()}"
|
||||
}
|
||||
|
||||
fun isSamAdapterName(name: String): Boolean =
|
||||
"\$sam$" in name && name.endsWith("$0")
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.MethodReport
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
class MethodAnnotationsChecker(annotationsProperty: KProperty1<MethodNode, List<Any?>?>) :
|
||||
AnnotationsChecker<MethodNode>("method.${annotationsProperty.name}", annotationsProperty),
|
||||
MethodChecker {
|
||||
|
||||
override fun check(method1: MethodNode, method2: MethodNode, report: MethodReport) {
|
||||
val anns1 = getAnnotations(method1)
|
||||
val anns2 = getAnnotations(method2)
|
||||
val annDiff = compareAnnotations(anns1, anns2) ?: return
|
||||
report.addAnnotationDiffs(this, annDiff)
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.MethodReport
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
const val ignoreMissingNullabilityAnnotationsOnInvisibleMethods = true
|
||||
|
||||
interface MethodChecker : Checker {
|
||||
fun check(method1: MethodNode, method2: MethodNode, report: MethodReport)
|
||||
}
|
||||
|
||||
inline fun <T> methodPropertyChecker(name: String, crossinline get: (MethodNode) -> T) =
|
||||
object : MethodPropertyChecker<T>(name) {
|
||||
override fun getProperty(node: MethodNode): T =
|
||||
get(node)
|
||||
}
|
||||
|
||||
fun <T> methodPropertyChecker(methodProperty: KProperty1<MethodNode, T>) =
|
||||
methodPropertyChecker(methodProperty.name) { methodProperty.get(it) }
|
||||
|
||||
inline fun <T> methodPropertyChecker(methodProperty: KProperty1<MethodNode, T>, crossinline html: (T) -> String) =
|
||||
object : MethodPropertyChecker<T>(methodProperty.name) {
|
||||
override fun getProperty(node: MethodNode): T =
|
||||
methodProperty.get(node)
|
||||
|
||||
override fun valueToHtml(value: T, other: T): String =
|
||||
html(value)
|
||||
}
|
||||
|
||||
fun <T> methodPropertyChecker(name: String, methodProperty: KProperty1<MethodNode, T>) =
|
||||
methodPropertyChecker(name) { methodProperty.get(it) }
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.defects.*
|
||||
import org.jetbrains.kotlin.abicmp.reports.MethodReport
|
||||
import org.jetbrains.org.objectweb.asm.tree.AnnotationNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import kotlin.math.max
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
class MethodParameterAnnotationsChecker(
|
||||
private val parameterAnnotationsProperty: KProperty1<MethodNode, Array<List<AnnotationNode?>?>?>,
|
||||
) : MethodChecker {
|
||||
|
||||
override val name = "method.parameters.${parameterAnnotationsProperty.name}"
|
||||
|
||||
val mismatchDefect = DefectType("${name}.mismatch", "Value parameter annotation mismatch", METHOD_A, VP_INDEX_A, VALUE1_A, VALUE2_A)
|
||||
val missing1Defect = DefectType("${name}.missing1", "Missing value parameter annotation in #1", METHOD_A, VP_INDEX_A, VALUE2_A)
|
||||
val missing2Defect = DefectType("${name}.missing2", "Missing value parameter annotation in #2", METHOD_A, VP_INDEX_A, VALUE1_A)
|
||||
|
||||
override fun check(method1: MethodNode, method2: MethodNode, report: MethodReport) {
|
||||
val paramAnnsList1 = parameterAnnotationsProperty.get(method1)?.toList().orEmpty()
|
||||
val paramAnnsList2 = parameterAnnotationsProperty.get(method2)?.toList().orEmpty()
|
||||
for (i in 0 until max(paramAnnsList1.size, paramAnnsList2.size)) {
|
||||
val anns1 = paramAnnsList1.getOrElse(i) { emptyList() }.toAnnotations()
|
||||
val anns2 = paramAnnsList2.getOrElse(i) { emptyList() }.toAnnotations()
|
||||
val annDiff = compareAnnotations(anns1, anns2) ?: continue
|
||||
report.addValueParameterAnnotationDiffs(this, i, annDiff)
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.MethodReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
|
||||
abstract class MethodPropertyChecker<T>(name: String) :
|
||||
PropertyChecker<T, MethodNode>("method.$name"),
|
||||
MethodChecker {
|
||||
|
||||
override fun check(method1: MethodNode, method2: MethodNode, report: MethodReport) {
|
||||
val value1 = getProperty(method1)
|
||||
val value2 = getProperty(method2)
|
||||
if (!areEqual(value1, value2)) {
|
||||
report.addPropertyDiff(
|
||||
defectType,
|
||||
NamedDiffEntry(
|
||||
name,
|
||||
valueToHtml(value1, value2),
|
||||
valueToHtml(value2, value1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.defects.DefectType
|
||||
import org.jetbrains.kotlin.abicmp.defects.METHOD_A
|
||||
import org.jetbrains.kotlin.abicmp.isSynthetic
|
||||
import org.jetbrains.kotlin.abicmp.listOfNotNull
|
||||
import org.jetbrains.kotlin.abicmp.methodFlags
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.ListEntryDiff
|
||||
import org.jetbrains.kotlin.abicmp.tasks.methodId
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
|
||||
class MethodsListChecker : ClassChecker {
|
||||
override val name = "class.methods"
|
||||
|
||||
val missing1Defect = DefectType("${name}.missing1", "Missing method in #1", METHOD_A)
|
||||
val missing2Defect = DefectType("${name}.missing2", "Missing method in #2", METHOD_A)
|
||||
|
||||
override fun check(class1: ClassNode, class2: ClassNode, report: ClassReport) {
|
||||
val methods1 = class1.loadMethods()
|
||||
val methods2 = class2.loadMethods()
|
||||
|
||||
val relevantMethodIds = methods1.keys.union(methods2.keys)
|
||||
.filter {
|
||||
val method1 = methods1[it]
|
||||
val method2 = methods2[it]
|
||||
acceptNonSyntheticMethods(method1, method2)
|
||||
}.toSet()
|
||||
|
||||
val methodIds1 = methods1.keys.intersect(relevantMethodIds).sorted()
|
||||
val methodIds2 = methods2.keys.intersect(relevantMethodIds).sorted()
|
||||
|
||||
val listDiff = compareLists(methodIds1, methodIds2) ?: return
|
||||
report.addMethodListDiffs(
|
||||
this,
|
||||
listDiff.map {
|
||||
ListEntryDiff(
|
||||
it.value1?.toMethodWithFlags(methods1),
|
||||
it.value2?.toMethodWithFlags(methods2)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun acceptNonSyntheticMethods(method1: MethodNode?, method2: MethodNode?) =
|
||||
method1 != null && !method1.access.isSynthetic() ||
|
||||
method2 != null && !method2.access.isSynthetic()
|
||||
|
||||
private fun String.toMethodWithFlags(methods: Map<String, MethodNode>): String {
|
||||
val method = methods[this] ?: return this
|
||||
return "$this ${method.access.methodFlags()}"
|
||||
}
|
||||
}
|
||||
|
||||
fun ClassNode.loadMethods(): Map<String, MethodNode> =
|
||||
methods.listOfNotNull<MethodNode>().filter {
|
||||
(it.access and Opcodes.ACC_PUBLIC) != 0 ||
|
||||
(it.access and Opcodes.ACC_PROTECTED) != 0
|
||||
}.associateBy { it.methodId() }
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.MultiFileClassFacadeMetadataReport
|
||||
|
||||
interface MultiFileClassFacadeMetadataChecker : Checker {
|
||||
fun check(
|
||||
metadata1: KotlinClassMetadata.MultiFileClassFacade,
|
||||
metadata2: KotlinClassMetadata.MultiFileClassFacade,
|
||||
report: MultiFileClassFacadeMetadataReport,
|
||||
)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.MultiFileClassFacadeMetadataReport
|
||||
|
||||
abstract class MultiFileClassFacadeMetadataListChecker(name: String) : MultiFileClassFacadeMetadataChecker {
|
||||
override fun check(
|
||||
metadata1: KotlinClassMetadata.MultiFileClassFacade,
|
||||
metadata2: KotlinClassMetadata.MultiFileClassFacade,
|
||||
report: MultiFileClassFacadeMetadataReport,
|
||||
) {
|
||||
val list1 = getList(metadata1)
|
||||
val list2 = getList(metadata2)
|
||||
|
||||
val diff = compareLists(list1.sorted(), list2.sorted()) ?: return
|
||||
|
||||
report.addMembersListDiffs(diff)
|
||||
}
|
||||
|
||||
abstract fun getList(metadata: KotlinClassMetadata.MultiFileClassFacade): List<String>
|
||||
|
||||
override val name: String = "class.metadata.$name"
|
||||
}
|
||||
|
||||
fun multiFileClassFacadeMetadataListChecker(name: String, listGetter: (KotlinClassMetadata.MultiFileClassFacade) -> List<String>) =
|
||||
object : MultiFileClassFacadeMetadataListChecker(name) {
|
||||
override fun getList(metadata: KotlinClassMetadata.MultiFileClassFacade) = listGetter(metadata)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.MultiFileClassPartMetadataReport
|
||||
|
||||
interface MultiFileClassPartMetadataChecker : Checker {
|
||||
fun check(
|
||||
metadata1: KotlinClassMetadata.MultiFileClassPart,
|
||||
metadata2: KotlinClassMetadata.MultiFileClassPart,
|
||||
report: MultiFileClassPartMetadataReport,
|
||||
)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.MultiFileClassPartMetadataReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
|
||||
abstract class MultiFileClassPartMetadataPropertyChecker(name: String) :
|
||||
PropertyChecker<String, KotlinClassMetadata.MultiFileClassPart>("class.metadata.$name"), MultiFileClassPartMetadataChecker {
|
||||
|
||||
override fun check(
|
||||
metadata1: KotlinClassMetadata.MultiFileClassPart,
|
||||
metadata2: KotlinClassMetadata.MultiFileClassPart,
|
||||
report: MultiFileClassPartMetadataReport,
|
||||
) {
|
||||
val value1 = getProperty(metadata1)
|
||||
val value2 = getProperty(metadata2)
|
||||
if (!areEqual(value1, value2)) {
|
||||
report.addPropertyDiff(NamedDiffEntry(name, value1, value2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun multiFileClassPartMetadataPropertyChecker(name: String, propertyGetter: (KotlinClassMetadata.MultiFileClassPart) -> String) =
|
||||
object : MultiFileClassPartMetadataPropertyChecker(name) {
|
||||
override fun getProperty(node: KotlinClassMetadata.MultiFileClassPart) = propertyGetter(node)
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.KmPackage
|
||||
import org.jetbrains.kotlin.abicmp.reports.PackageMetadataReport
|
||||
|
||||
interface PackageMetadataChecker : Checker {
|
||||
fun check(metadata1: KmPackage, metadata2: KmPackage, report: PackageMetadataReport)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.KmPackage
|
||||
import org.jetbrains.kotlin.abicmp.reports.PackageMetadataReport
|
||||
|
||||
abstract class PackageMetadataListChecker(name: String) : PackageMetadataChecker {
|
||||
override fun check(
|
||||
metadata1: KmPackage,
|
||||
metadata2: KmPackage,
|
||||
report: PackageMetadataReport,
|
||||
) {
|
||||
val list1 = getList(metadata1)
|
||||
val list2 = getList(metadata2)
|
||||
|
||||
val diff = compareLists(list1.sorted(), list2.sorted()) ?: return
|
||||
|
||||
report.addMembersListDiffs(diff)
|
||||
}
|
||||
|
||||
abstract fun getList(metadata: KmPackage): List<String>
|
||||
|
||||
override val name: String = "class.metadata.$name"
|
||||
}
|
||||
|
||||
fun fileFacadeMetadataListChecker(name: String, listGetter: (KmPackage) -> List<String>) =
|
||||
object : PackageMetadataListChecker(name) {
|
||||
override fun getList(metadata: KmPackage) = listGetter(metadata)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.SyntheticClassMetadataReport
|
||||
|
||||
interface SyntheticClassMetadataChecker : Checker {
|
||||
fun check(
|
||||
metadata1: KotlinClassMetadata.SyntheticClass,
|
||||
metadata2: KotlinClassMetadata.SyntheticClass,
|
||||
report: SyntheticClassMetadataReport,
|
||||
)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
import org.jetbrains.kotlin.abicmp.reports.SyntheticClassMetadataReport
|
||||
|
||||
abstract class SyntheticClassMetadataPropertyChecker(name: String) :
|
||||
PropertyChecker<String, KotlinClassMetadata.SyntheticClass>("class.metadata.$name"), SyntheticClassMetadataChecker {
|
||||
|
||||
override fun check(
|
||||
metadata1: KotlinClassMetadata.SyntheticClass,
|
||||
metadata2: KotlinClassMetadata.SyntheticClass,
|
||||
report: SyntheticClassMetadataReport,
|
||||
) {
|
||||
val value1 = getProperty(metadata1)
|
||||
val value2 = getProperty(metadata2)
|
||||
if (!areEqual(value1, value2)) {
|
||||
report.addPropertyDiff(NamedDiffEntry(name, value1, value2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun syntheticClassMetadataPropertyChecker(name: String, propertyGetter: (KotlinClassMetadata.SyntheticClass) -> String) =
|
||||
object : SyntheticClassMetadataPropertyChecker(name) {
|
||||
override fun getProperty(node: KotlinClassMetadata.SyntheticClass) = propertyGetter(node)
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.ListEntryDiff
|
||||
|
||||
fun compareLists(list1: List<String>, list2: List<String>): List<ListEntryDiff>? {
|
||||
val result = ArrayList<ListEntryDiff>()
|
||||
var i1 = 0
|
||||
var i2 = 0
|
||||
while (i1 < list1.size || i2 < list2.size) {
|
||||
val s1 = list1.getOrNull(i1)
|
||||
val s2 = list2.getOrNull(i2)
|
||||
|
||||
if (s1 == s2) {
|
||||
++i1
|
||||
++i2
|
||||
continue
|
||||
}
|
||||
|
||||
when {
|
||||
s1 == null && s2 == null ->
|
||||
break // really should not happen
|
||||
s1 == null -> {
|
||||
result.add(ListEntryDiff(null, s2))
|
||||
++i2
|
||||
}
|
||||
s2 == null -> {
|
||||
result.add(ListEntryDiff(s1, null))
|
||||
++i1
|
||||
}
|
||||
s1 < s2 -> {
|
||||
result.add(ListEntryDiff(s1, null))
|
||||
++i1
|
||||
}
|
||||
s1 > s2 -> {
|
||||
result.add(ListEntryDiff(null, s2))
|
||||
++i2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (result.isEmpty()) null else result
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import kotlinx.metadata.*
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import kotlinx.metadata.jvm.getterSignature
|
||||
import kotlinx.metadata.jvm.localDelegatedProperties
|
||||
import kotlinx.metadata.jvm.signature
|
||||
import org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport
|
||||
import org.jetbrains.kotlin.abicmp.tasks.GenericMetadataTask
|
||||
import org.jetbrains.kotlin.kotlinp.*
|
||||
|
||||
fun loadProperties(container: KmDeclarationContainer) = container.properties.associateBy { it.getterSignature?.toString() ?: it.name }
|
||||
|
||||
fun loadConstructors(clazz: KotlinClassMetadata.Class) = clazz.kmClass.constructors.associateBy { it.signature.toString() }
|
||||
|
||||
fun loadFunctions(container: KmDeclarationContainer) = container.functions.associateBy { (it.signature ?: it.name).toString() }
|
||||
|
||||
fun loadTypeAliases(container: KmDeclarationContainer) = container.typeAliases.associateBy { it.name }
|
||||
|
||||
fun loadLocalDelegatedProperties(kmPackage: KmPackage) =
|
||||
kmPackage.localDelegatedProperties.associateBy { it.getterSignature?.toString() ?: it.name }
|
||||
|
||||
fun loadLocalDelegatedProperties(clazz: KotlinClassMetadata.Class) =
|
||||
clazz.kmClass.localDelegatedProperties.associateBy { it.getterSignature?.toString() ?: it.name }
|
||||
|
||||
fun List<KmVersionRequirement>.stringifyRelevantRequirements() =
|
||||
// older versions of requirements are redundant for compiler with version 1.9 or newer
|
||||
filter { it.version.major >= 2 || (it.version.major == 1 && it.version.minor >= 8) }
|
||||
.map(::printVersionRequirement).sorted().joinToString(prefix = "[", postfix = "]")
|
||||
|
||||
fun List<KmType>.stringifyTypeListSorted() = map(::printType).sorted().joinToString(prefix = "[", postfix = "]")
|
||||
|
||||
fun List<KmTypeParameter>.stringifyTypeParameters() = joinToString(prefix = "<", postfix = ">") { typeParam ->
|
||||
printTypeParameter(
|
||||
typeParam,
|
||||
KotlinpSettings(
|
||||
isVerbose = true,
|
||||
sortDeclarations = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun List<KmValueParameter>.stringifyValueParameters() = joinToString(prefix = "(", postfix = ")", transform = ::printValueParameter)
|
||||
|
||||
fun List<KmAnnotation>.stringifyAnnotations() = joinToString(prefix = "[", postfix = "]", transform = ::renderAnnotation)
|
||||
|
||||
inline fun <R, T> checkMetadataMembers(
|
||||
metadata1: R,
|
||||
metadata2: R,
|
||||
checkers: List<GenericMetadataChecker<T>>,
|
||||
reportBuilder: (String) -> MetadataPropertyReport,
|
||||
membersGetter: (R) -> Map<String, T>,
|
||||
) {
|
||||
val membersMetadata1 = membersGetter(metadata1)
|
||||
val membersMetadata2 = membersGetter(metadata2)
|
||||
val commonIds = membersMetadata1.keys.intersect(membersMetadata2.keys).sorted()
|
||||
for (id in commonIds) {
|
||||
val member1 = membersMetadata1[id]!!
|
||||
val member2 = membersMetadata2[id]!!
|
||||
GenericMetadataTask(member1, member2, reportBuilder(id), checkers).run()
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.defects
|
||||
|
||||
class Defect(val location: Location, val info: DefectInfo)
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.defects
|
||||
|
||||
class DefectAttribute(val id: String, val htmlId: String)
|
||||
|
||||
class DefectType(
|
||||
val id: String,
|
||||
val messageText: String,
|
||||
vararg val requiredAttributes: DefectAttribute,
|
||||
) : Comparable<DefectType> {
|
||||
override fun compareTo(other: DefectType): Int =
|
||||
id.compareTo(other.id)
|
||||
|
||||
override fun hashCode(): Int = id.hashCode()
|
||||
}
|
||||
|
||||
class DefectInfo(
|
||||
val type: DefectType,
|
||||
val attributes: Map<DefectAttribute, String>,
|
||||
) : Comparable<DefectInfo> {
|
||||
init {
|
||||
for (requiredAttribute in type.requiredAttributes) {
|
||||
if (requiredAttribute !in attributes) {
|
||||
throw IllegalArgumentException(
|
||||
"Missing required attribute ${requiredAttribute.id} for defect type ${type.id}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(key: DefectAttribute) = attributes[key]
|
||||
|
||||
override fun compareTo(other: DefectInfo): Int =
|
||||
when (val typeCmp = type.compareTo(other.type)) {
|
||||
0 -> compareAttributes(other)
|
||||
else -> typeCmp
|
||||
}
|
||||
|
||||
private fun compareAttributes(other: DefectInfo): Int {
|
||||
for ((key, value) in attributes) {
|
||||
val otherValue = other[key] ?: return 1
|
||||
when (val valueCmp = value.compareTo(otherValue)) {
|
||||
0 -> continue
|
||||
else -> return valueCmp
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is DefectInfo && compareTo(other) == 0
|
||||
|
||||
override fun hashCode(): Int =
|
||||
type.hashCode()
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.defects
|
||||
|
||||
sealed class Location : Comparable<Location> {
|
||||
abstract val jarFileName: String
|
||||
open val className: String? get() = null
|
||||
open val methodName: String? get() = null
|
||||
open val fieldName: String? get() = null
|
||||
|
||||
enum class Kind {
|
||||
JAR_FILE, CLASS, METHOD, FIELD
|
||||
}
|
||||
|
||||
abstract val kind: Kind
|
||||
|
||||
override fun compareTo(other: Location): Int =
|
||||
compareValuesBy(
|
||||
this, other,
|
||||
{ it.kind },
|
||||
{ it.jarFileName },
|
||||
{ it.className },
|
||||
{ it.methodName },
|
||||
{ it.fieldName }
|
||||
)
|
||||
|
||||
data class JarFile(
|
||||
override val jarFileName: String,
|
||||
) : Location() {
|
||||
override val kind get() = Kind.JAR_FILE
|
||||
}
|
||||
|
||||
data class Class(
|
||||
override val jarFileName: String,
|
||||
override val className: String,
|
||||
) : Location() {
|
||||
override val kind get() = Kind.CLASS
|
||||
fun method(methodName: String) = Method(jarFileName, className, methodName)
|
||||
fun field(fieldName: String) = Field(jarFileName, className, fieldName)
|
||||
}
|
||||
|
||||
data class Method(
|
||||
override val jarFileName: String,
|
||||
override val className: String,
|
||||
override val methodName: String,
|
||||
) : Location() {
|
||||
override val kind get() = Kind.METHOD
|
||||
}
|
||||
|
||||
data class Field(
|
||||
override val jarFileName: String,
|
||||
override val className: String,
|
||||
override val fieldName: String,
|
||||
) : Location() {
|
||||
override val kind get() = Kind.FIELD
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.defects
|
||||
|
||||
val JAR_FILE1_A = DefectAttribute("JAR_FILE1", "jarFile1")
|
||||
val JAR_FILE2_A = DefectAttribute("JAR_FILE2", "jarFile2")
|
||||
val CLASS_A = DefectAttribute("CLASS", "class")
|
||||
val METHOD_A = DefectAttribute("METHOD", "method")
|
||||
val FIELD_A = DefectAttribute("FIELD", "field")
|
||||
val VALUE1_A = DefectAttribute("VALUE1", "value1")
|
||||
val VALUE2_A = DefectAttribute("VALUE2", "value2")
|
||||
val VP_INDEX_A = DefectAttribute("VP_INDEX", "vpIndex")
|
||||
val INNER_CLASS_A = DefectAttribute("INNER_CLASS", "innerClass")
|
||||
val METADATA_A = DefectAttribute("METADATA", "metadata")
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp
|
||||
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
import java.io.PrintWriter
|
||||
|
||||
const val NON_BREAKING_HYPHEN = "‑"
|
||||
|
||||
fun String?.escapeHtml(): String {
|
||||
if (this == null) return "NULL"
|
||||
return StringEscapeUtils.escapeHtml4(this)
|
||||
}
|
||||
|
||||
fun Any?.toHtmlString(): String {
|
||||
if (this == null) return "NULL"
|
||||
return StringEscapeUtils.escapeHtml4(toString()).replace("\n", "<br>")
|
||||
}
|
||||
|
||||
fun PrintWriter.tag(tagName: String) {
|
||||
print("<$tagName/>")
|
||||
}
|
||||
|
||||
inline fun PrintWriter.tag(tagName: String, body: () -> Unit) {
|
||||
print("<$tagName>")
|
||||
body()
|
||||
println("</$tagName>")
|
||||
}
|
||||
|
||||
fun PrintWriter.tag(tagName: String, content: String) {
|
||||
println("<$tagName>$content</$tagName>")
|
||||
}
|
||||
|
||||
inline fun PrintWriter.table(body: () -> Unit) {
|
||||
tag("table", body)
|
||||
}
|
||||
|
||||
fun PrintWriter.tableHeader(vararg headers: String) {
|
||||
tag("tr") {
|
||||
for (header in headers) {
|
||||
tag("th", header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PrintWriter.tableData(vararg data: String) {
|
||||
tag("tr") {
|
||||
for (d in data) {
|
||||
tag("td", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PrintWriter.tableDataWithClass(tdClass: String, vararg data: String) {
|
||||
println("<tr>")
|
||||
for (d in data) {
|
||||
println("<td class=\"$tdClass\">$d</td>")
|
||||
}
|
||||
println("</tr>")
|
||||
}
|
||||
|
||||
fun Any.tag(tagName: String) =
|
||||
"<$tagName>$this</$tagName>"
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.PrintWriter
|
||||
|
||||
class ClassMetadataReport(
|
||||
private val classInternalName: String,
|
||||
val header1: String,
|
||||
val header2: String,
|
||||
) : ComparisonReport {
|
||||
|
||||
private val membersDiffList = ArrayList<DiffEntry>()
|
||||
|
||||
private val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
|
||||
private val constructorReports = ArrayList<MetadataPropertyReport>()
|
||||
private val functionReports = ArrayList<MetadataPropertyReport>()
|
||||
private val propertyReports = ArrayList<MetadataPropertyReport>()
|
||||
private val typeAliasReports = ArrayList<MetadataPropertyReport>()
|
||||
private val localDelegatedPropertyReport = ArrayList<MetadataPropertyReport>()
|
||||
|
||||
override fun isEmpty() =
|
||||
membersDiffList.isEmpty()
|
||||
&& propertyDiffs.isEmpty()
|
||||
&& propertyReports.areAllEmpty()
|
||||
&& constructorReports.areAllEmpty()
|
||||
&& functionReports.areAllEmpty()
|
||||
&& typeAliasReports.areAllEmpty()
|
||||
&& localDelegatedPropertyReport.areAllEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
if (isEmpty()) return
|
||||
|
||||
output.tag("h2", "CLASS METADATA $classInternalName")
|
||||
|
||||
output.listDiff(header1, header2, membersDiffList)
|
||||
output.propertyDiffTable(header1, header2, propertyDiffs)
|
||||
|
||||
for (report in listOf(
|
||||
constructorReports,
|
||||
functionReports,
|
||||
propertyReports,
|
||||
typeAliasReports,
|
||||
localDelegatedPropertyReport
|
||||
).flatten()) {
|
||||
report.writeAsHtml(output)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun constructorReport(id: String) = MetadataPropertyReport("CONSTRUCTOR $id", header1, header2).also { constructorReports.add(it) }
|
||||
|
||||
fun functionReport(id: String) = MetadataPropertyReport("FUNCTION $id", header1, header2).also { functionReports.add(it) }
|
||||
|
||||
fun propertyReport(id: String) = MetadataPropertyReport("PROPERTY $id", header1, header2).also { propertyReports.add(it) }
|
||||
|
||||
fun typeAliasReport(id: String) = MetadataPropertyReport("TYPE ALIAS $id", header1, header2).also { typeAliasReports.add(it) }
|
||||
|
||||
fun localDelegatedPropertyReport(id: String) =
|
||||
MetadataPropertyReport("LOCAL DELEGATED PROPERTY $id", header1, header2).also { localDelegatedPropertyReport.add(it) }
|
||||
|
||||
fun addMembersListDiffs(diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
membersDiffList.add(diff.toDiffEntry())
|
||||
}
|
||||
}
|
||||
|
||||
fun addPropertyDiff(diff: NamedDiffEntry) {
|
||||
propertyDiffs.add(diff)
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendClassMetadataReport() {
|
||||
if (isNotEmpty()) {
|
||||
node("CLASS METADATA") {
|
||||
appendDiffEntries(header1, header2, membersDiffList)
|
||||
|
||||
appendNamedDiffEntries(header1, header2, propertyDiffs, "Property")
|
||||
|
||||
for (report in listOf(
|
||||
constructorReports,
|
||||
functionReports,
|
||||
propertyReports,
|
||||
typeAliasReports,
|
||||
localDelegatedPropertyReport
|
||||
).flatten()) {
|
||||
with(report) { appendReport() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.checkers.ClassAnnotationsChecker
|
||||
import org.jetbrains.kotlin.abicmp.checkers.FieldsListChecker
|
||||
import org.jetbrains.kotlin.abicmp.checkers.InnerClassesListChecker
|
||||
import org.jetbrains.kotlin.abicmp.checkers.MethodsListChecker
|
||||
import org.jetbrains.kotlin.abicmp.defects.*
|
||||
import org.jetbrains.kotlin.abicmp.escapeHtml
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintWriter
|
||||
|
||||
class ClassReport(
|
||||
private val location: Location.Class,
|
||||
val classInternalName: String,
|
||||
val header1: String,
|
||||
val header2: String,
|
||||
private val defectReport: DefectReport,
|
||||
) : ComparisonReport {
|
||||
|
||||
private val infoParagraphs = ArrayList<String>()
|
||||
|
||||
private val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
private val annotationDiffs = ArrayList<NamedDiffEntry>()
|
||||
private val innerClassesDiffs = ArrayList<DiffEntry>()
|
||||
private val methodListDiffs = ArrayList<DiffEntry>()
|
||||
private val fieldListDiffs = ArrayList<DiffEntry>()
|
||||
private val metadataDiffs = ArrayList<DiffEntry>()
|
||||
|
||||
private val methodReports = ArrayList<MethodReport>()
|
||||
private val fieldReports = ArrayList<FieldReport>()
|
||||
|
||||
private var classMetadataReport: ClassMetadataReport? = null
|
||||
private var fileFacadeMetadataReport: PackageMetadataReport? = null
|
||||
private var multiFileClassFacadeMetadataReport: MultiFileClassFacadeMetadataReport? = null
|
||||
private var multiFileClassPartMetadataReport: MultiFileClassPartMetadataReport? = null
|
||||
private var syntheticClassMetadataReport: SyntheticClassMetadataReport? = null
|
||||
|
||||
private val ComparisonReport?.isNullOrEmpty get() = this == null || this.isEmpty()
|
||||
|
||||
override fun isEmpty(): Boolean =
|
||||
metadataDiffs.isEmpty() &&
|
||||
propertyDiffs.isEmpty() &&
|
||||
annotationDiffs.isEmpty() &&
|
||||
innerClassesDiffs.isEmpty() &&
|
||||
methodListDiffs.isEmpty() &&
|
||||
getFilteredMethodReports().isEmpty() &&
|
||||
fieldListDiffs.isEmpty() &&
|
||||
getFilteredFieldReports().isEmpty()
|
||||
&& classMetadataReport.isNullOrEmpty
|
||||
&& fileFacadeMetadataReport.isNullOrEmpty
|
||||
&& multiFileClassFacadeMetadataReport.isNullOrEmpty
|
||||
&& multiFileClassPartMetadataReport.isNullOrEmpty
|
||||
&& syntheticClassMetadataReport.isNullOrEmpty
|
||||
|
||||
fun addInfo(info: String) {
|
||||
infoParagraphs.add(info)
|
||||
}
|
||||
|
||||
inline fun info(fm: PrintWriter.() -> Unit) {
|
||||
val bytes = ByteArrayOutputStream()
|
||||
val ps = PrintWriter(bytes)
|
||||
ps.fm()
|
||||
ps.close()
|
||||
addInfo(String(bytes.toByteArray()))
|
||||
}
|
||||
|
||||
private fun DefectType.report(vararg attributes: Pair<DefectAttribute, String>) {
|
||||
defectReport.report(this, location, *attributes)
|
||||
}
|
||||
|
||||
fun addPropertyDiff(defectType: DefectType, diff: NamedDiffEntry) {
|
||||
propertyDiffs.add(diff)
|
||||
defectType.report(VALUE1_A to diff.value1, VALUE2_A to diff.value2)
|
||||
}
|
||||
|
||||
fun addAnnotationDiffs(checker: ClassAnnotationsChecker, diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
annotationDiffs.add(NamedDiffEntry(checker.name, diff.value1 ?: "---", diff.value2 ?: "---"))
|
||||
when {
|
||||
diff.value1 != null && diff.value2 != null ->
|
||||
checker.mismatchDefect.report(VALUE1_A to diff.value1, VALUE2_A to diff.value2)
|
||||
diff.value1 == null && diff.value2 != null ->
|
||||
checker.missing1Defect.report(VALUE2_A to diff.value2)
|
||||
diff.value1 != null && diff.value2 == null ->
|
||||
checker.missing2Defect.report(VALUE1_A to diff.value1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addInnerClassesDiffs(checker: InnerClassesListChecker, diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
innerClassesDiffs.add(diff.toDiffEntry())
|
||||
reportMissing(diff, checker.missing1Defect, checker.missing2Defect, INNER_CLASS_A)
|
||||
}
|
||||
}
|
||||
|
||||
fun addMethodListDiffs(checker: MethodsListChecker, diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
methodListDiffs.add(diff.toDiffEntry())
|
||||
reportMissing(diff, checker.missing1Defect, checker.missing2Defect, METHOD_A)
|
||||
}
|
||||
}
|
||||
|
||||
fun addFieldListDiffs(checker: FieldsListChecker, diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
fieldListDiffs.add(diff.toDiffEntry())
|
||||
reportMissing(diff, checker.missing1Defect, checker.missing2Defect, FIELD_A)
|
||||
}
|
||||
}
|
||||
|
||||
fun addMetadataDiff(diff: ListEntryDiff) {
|
||||
metadataDiffs.add(diff.toDiffEntry())
|
||||
val missing1Defect = DefectType("class.metadata.missing1", "Missing metadata in #1", METADATA_A)
|
||||
val missing2Defect = DefectType("class.metadata.missing2", "Missing metadata in #2", METADATA_A)
|
||||
reportMissing(diff, missing1Defect, missing2Defect, METADATA_A)
|
||||
}
|
||||
|
||||
private fun reportMissing(diff: ListEntryDiff, missing1: DefectType, missing2: DefectType, attr: DefectAttribute) {
|
||||
when {
|
||||
diff.value1 == null && diff.value2 != null ->
|
||||
missing1.report(attr to diff.value2)
|
||||
diff.value1 != null && diff.value2 == null ->
|
||||
missing2.report(attr to diff.value1)
|
||||
}
|
||||
}
|
||||
|
||||
fun classMetadataReport() = ClassMetadataReport(classInternalName, header1, header2).also { this.classMetadataReport = it }
|
||||
|
||||
fun fileFacadeMetadataReport() = PackageMetadataReport(classInternalName, header1, header2).also { this.fileFacadeMetadataReport = it }
|
||||
|
||||
fun multiFileClassFacadeMetadataReport() =
|
||||
MultiFileClassFacadeMetadataReport(classInternalName, header1, header2).also { this.multiFileClassFacadeMetadataReport = it }
|
||||
|
||||
fun multiFileClassPartMetadataReport() =
|
||||
MultiFileClassPartMetadataReport(classInternalName, header1, header2).also { this.multiFileClassPartMetadataReport = it }
|
||||
|
||||
fun syntheticMetadataReport() =
|
||||
SyntheticClassMetadataReport(classInternalName, header1, header2).also { this.syntheticClassMetadataReport = it }
|
||||
|
||||
fun methodReport(methodId: String): MethodReport =
|
||||
MethodReport(location.method(methodId), methodId, header1, header2, defectReport)
|
||||
.also { methodReports.add(it) }
|
||||
|
||||
fun fieldReport(fieldId: String): FieldReport =
|
||||
FieldReport(location.field(fieldId), fieldId, header1, header2, defectReport)
|
||||
.also { fieldReports.add(it) }
|
||||
|
||||
private fun getFilteredMethodReports() =
|
||||
methodReports.filter { !it.isEmpty() }.sortedBy { it.methodId }
|
||||
|
||||
private fun getFilteredFieldReports() =
|
||||
fieldReports.filter { !it.isEmpty() }.sortedBy { it.fieldId }
|
||||
|
||||
fun TextTreeBuilderContext.appendClassReport() {
|
||||
node("CLASS $classInternalName") {
|
||||
classMetadataReport?.run { appendClassMetadataReport() }
|
||||
fileFacadeMetadataReport?.run { appendPackageMetadataReport() }
|
||||
multiFileClassFacadeMetadataReport?.run { appendMultiFileClassFacadeReport() }
|
||||
multiFileClassPartMetadataReport?.run { appendMultiFileClassPartReport() }
|
||||
syntheticClassMetadataReport?.run { appendSyntheticClassMetadataReport() }
|
||||
|
||||
appendNamedDiffEntries(header1, header2, propertyDiffs, "Property")
|
||||
appendNamedDiffEntries(header1, header2, annotationDiffs, "Annotation")
|
||||
|
||||
|
||||
appendDiffEntries(header1, header2, innerClassesDiffs)
|
||||
appendDiffEntries(header1, header2, methodListDiffs)
|
||||
appendDiffEntries(header1, header2, metadataDiffs)
|
||||
|
||||
for (mr in getFilteredMethodReports()) {
|
||||
with(mr) { appendMethodReport() }
|
||||
}
|
||||
|
||||
appendDiffEntries(header1, header2, fieldListDiffs)
|
||||
|
||||
for (fr in getFilteredFieldReports()) {
|
||||
with(fr) { appendFieldReport() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
output.tag("h1", "CLASS " + classInternalName.escapeHtml())
|
||||
|
||||
for (info in infoParagraphs) {
|
||||
output.tag("p", info)
|
||||
}
|
||||
|
||||
output.propertyDiffTable(header1, header2, propertyDiffs)
|
||||
|
||||
output.annotationDiffTable(header1, header2, annotationDiffs)
|
||||
|
||||
output.listDiff(header1, header2, innerClassesDiffs)
|
||||
|
||||
output.listDiff(header1, header2, methodListDiffs)
|
||||
|
||||
output.listDiff(header1, header2, metadataDiffs)
|
||||
|
||||
classMetadataReport?.run { writeAsHtml(output) }
|
||||
fileFacadeMetadataReport?.run { writeAsHtml(output) }
|
||||
multiFileClassFacadeMetadataReport?.run { writeAsHtml(output) }
|
||||
multiFileClassPartMetadataReport?.run { writeAsHtml(output) }
|
||||
syntheticClassMetadataReport?.run { writeAsHtml(output) }
|
||||
|
||||
for (mr in getFilteredMethodReports()) {
|
||||
mr.writeAsHtml(output)
|
||||
}
|
||||
|
||||
output.listDiff(header1, header2, fieldListDiffs)
|
||||
|
||||
for (fr in getFilteredFieldReports()) {
|
||||
fr.writeAsHtml(output)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import java.io.PrintWriter
|
||||
|
||||
interface ComparisonReport {
|
||||
fun isEmpty(): Boolean
|
||||
fun writeAsHtml(output: PrintWriter)
|
||||
}
|
||||
|
||||
fun ComparisonReport.isNotEmpty() = !isEmpty()
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.defects.*
|
||||
|
||||
class DefectReport {
|
||||
val defects: MutableList<Defect> = ArrayList()
|
||||
|
||||
fun report(type: DefectType, location: Location, vararg attributes: Pair<DefectAttribute, String>) {
|
||||
defects.add(Defect(location, DefectInfo(type, attributes.toMap())))
|
||||
}
|
||||
|
||||
fun isEmpty() = defects.isEmpty()
|
||||
}
|
||||
|
||||
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
class DiffEntry(val value1: String, val value2: String)
|
||||
|
||||
class NamedDiffEntry(val name: String, val value1: String, val value2: String)
|
||||
|
||||
class ListEntryDiff(val value1: String?, val value2: String?)
|
||||
|
||||
class ListDiff(val diff1: List<String>, val diff2: List<String>)
|
||||
|
||||
class TextDiffEntry(val lines1: List<String>, val lines2: List<String>)
|
||||
|
||||
fun ListEntryDiff.toDiffEntry() =
|
||||
DiffEntry(value1 ?: "---", value2 ?: "---")
|
||||
|
||||
fun ListEntryDiff.toNamedDiffEntry(name: String) =
|
||||
NamedDiffEntry(name, value1 ?: "---", value2 ?: "---")
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.checkers.FieldAnnotationsChecker
|
||||
import org.jetbrains.kotlin.abicmp.defects.*
|
||||
import org.jetbrains.kotlin.abicmp.escapeHtml
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintWriter
|
||||
|
||||
class FieldReport(
|
||||
private val location: Location.Field,
|
||||
val fieldId: String,
|
||||
val header1: String,
|
||||
val header2: String,
|
||||
private val defectReport: DefectReport,
|
||||
) : ComparisonReport {
|
||||
private val infoParagraphs = ArrayList<String>()
|
||||
|
||||
private val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
private val annotationDiffs = ArrayList<NamedDiffEntry>()
|
||||
|
||||
fun addInfo(info: String) {
|
||||
infoParagraphs.add(info)
|
||||
}
|
||||
|
||||
inline fun info(fm: PrintWriter.() -> Unit) {
|
||||
val bytes = ByteArrayOutputStream()
|
||||
val ps = PrintWriter(bytes)
|
||||
ps.fm()
|
||||
ps.close()
|
||||
addInfo(String(bytes.toByteArray()))
|
||||
}
|
||||
|
||||
private fun DefectType.report(vararg attributes: Pair<DefectAttribute, String>) {
|
||||
defectReport.report(this, location, *attributes)
|
||||
defectReport.report(this, location, *attributes)
|
||||
}
|
||||
|
||||
fun addPropertyDiff(defectType: DefectType, diff: NamedDiffEntry) {
|
||||
propertyDiffs.add(diff)
|
||||
defectType.report(VALUE1_A to diff.value1, VALUE2_A to diff.value2)
|
||||
}
|
||||
|
||||
fun addAnnotationDiffs(checker: FieldAnnotationsChecker, diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
annotationDiffs.add(diff.toNamedDiffEntry(checker.name))
|
||||
when {
|
||||
diff.value1 != null && diff.value2 != null ->
|
||||
checker.mismatchDefect.report(VALUE1_A to diff.value1, VALUE2_A to diff.value2)
|
||||
diff.value1 == null && diff.value2 != null ->
|
||||
checker.missing1Defect.report(VALUE2_A to diff.value2)
|
||||
diff.value1 != null && diff.value2 == null ->
|
||||
checker.missing2Defect.report(VALUE1_A to diff.value1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean = propertyDiffs.isEmpty() && annotationDiffs.isEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
output.tag("h2", "> FIELD " + fieldId.escapeHtml())
|
||||
|
||||
for (info in infoParagraphs) {
|
||||
output.tag("p", info)
|
||||
}
|
||||
|
||||
output.propertyDiffTable(header1, header2, propertyDiffs)
|
||||
output.annotationDiffTable(header1, header2, annotationDiffs)
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendFieldReport() {
|
||||
node("FIELD $fieldId") {
|
||||
appendNamedDiffEntries(header1, header2, propertyDiffs, "Property")
|
||||
appendNamedDiffEntries(header1, header2, annotationDiffs, "Annotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.defects.*
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintWriter
|
||||
|
||||
val MISSING_CLASS1_D = DefectType("jar.missingClass1", "Missing class in #1", CLASS_A, JAR_FILE1_A)
|
||||
val MISSING_CLASS2_D = DefectType("jar.missingClass2", "Missing class in #2", CLASS_A, JAR_FILE2_A)
|
||||
|
||||
class JarReport(
|
||||
private val header: String,
|
||||
private val header1: String,
|
||||
private val header2: String,
|
||||
private val jarFileName1: String,
|
||||
private val jarFileName2: String,
|
||||
) : ComparisonReport {
|
||||
private val infoParagraphs = ArrayList<String>()
|
||||
|
||||
private val classReports = ArrayList<ClassReport>()
|
||||
|
||||
private val missingClassNames1 = HashSet<String>()
|
||||
private val missingClassNames2 = HashSet<String>()
|
||||
|
||||
val defectReport = DefectReport()
|
||||
|
||||
fun classLocation(classInternalName: String) =
|
||||
Location.Class(jarFileName1, classInternalName)
|
||||
|
||||
fun addInfo(info: String) {
|
||||
infoParagraphs.add(info)
|
||||
}
|
||||
|
||||
inline fun info(fm: PrintWriter.() -> Unit) {
|
||||
val bytes = ByteArrayOutputStream()
|
||||
val ps = PrintWriter(bytes)
|
||||
ps.fm()
|
||||
ps.close()
|
||||
addInfo(String(bytes.toByteArray()))
|
||||
}
|
||||
|
||||
fun classReport(classInternalName: String) =
|
||||
ClassReport(classLocation(classInternalName), classInternalName, header1, header2, defectReport)
|
||||
.also { classReports.add(it) }
|
||||
|
||||
private fun getFilteredClassReports(): List<ClassReport> =
|
||||
classReports.filter { !it.isEmpty() }.sortedBy { it.classInternalName }
|
||||
|
||||
private val jar1Location = Location.JarFile(jarFileName1)
|
||||
private val jar2Location = Location.JarFile(jarFileName2)
|
||||
|
||||
fun addMissingClassName1(classInternalName: String) {
|
||||
missingClassNames1.add(classInternalName)
|
||||
defectReport.report(
|
||||
MISSING_CLASS1_D,
|
||||
jar1Location,
|
||||
CLASS_A to classInternalName,
|
||||
JAR_FILE1_A to jarFileName1
|
||||
)
|
||||
}
|
||||
|
||||
fun addMissingClassName2(classInternalName: String) {
|
||||
missingClassNames2.add(classInternalName)
|
||||
defectReport.report(
|
||||
MISSING_CLASS2_D,
|
||||
jar2Location,
|
||||
CLASS_A to classInternalName,
|
||||
JAR_FILE2_A to jarFileName2
|
||||
)
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean =
|
||||
defectReport.isEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
output.tag("h1", header)
|
||||
|
||||
for (info in infoParagraphs) {
|
||||
output.tag("p", info)
|
||||
}
|
||||
|
||||
for (classReport in getFilteredClassReports()) {
|
||||
classReport.writeAsHtml(output)
|
||||
}
|
||||
|
||||
writeMissingClasses(output, jarFileName1, missingClassNames1)
|
||||
writeMissingClasses(output, jarFileName2, missingClassNames2)
|
||||
}
|
||||
|
||||
private fun writeMissingClasses(output: PrintWriter, name: String, missing: Collection<String>) {
|
||||
if (missing.isNotEmpty()) {
|
||||
output.tag("p", "Classes missing in $name: <b>${missing.size}</b>")
|
||||
output.tag("ul") {
|
||||
for (className in missing) {
|
||||
output.tag("li", className)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.PrintWriter
|
||||
|
||||
class MetadataPropertyReport(val id: String, val header1: String, val header2: String) : ComparisonReport {
|
||||
|
||||
private val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
|
||||
override fun isEmpty() = propertyDiffs.isEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
if (isEmpty()) return
|
||||
output.tag("h3", id)
|
||||
output.propertyDiffTable(header1, header2, propertyDiffs)
|
||||
}
|
||||
|
||||
fun addPropertyDiff(diff: NamedDiffEntry) {
|
||||
propertyDiffs.add(diff)
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendReport() {
|
||||
if (!isEmpty()) {
|
||||
node(id) {
|
||||
appendNamedDiffEntries(header1, header2, propertyDiffs, "Property")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.checkers.MethodAnnotationsChecker
|
||||
import org.jetbrains.kotlin.abicmp.checkers.MethodParameterAnnotationsChecker
|
||||
import org.jetbrains.kotlin.abicmp.defects.*
|
||||
import org.jetbrains.kotlin.abicmp.escapeHtml
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintWriter
|
||||
|
||||
class MethodReport(
|
||||
private val location: Location.Method,
|
||||
val methodId: String,
|
||||
val header1: String,
|
||||
val header2: String,
|
||||
private val defectReport: DefectReport,
|
||||
) : ComparisonReport {
|
||||
private val infoParagraphs = ArrayList<String>()
|
||||
|
||||
private val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
private val annotationDiffs = ArrayList<NamedDiffEntry>()
|
||||
|
||||
fun addInfo(info: String) {
|
||||
infoParagraphs.add(info)
|
||||
}
|
||||
|
||||
inline fun info(fm: PrintWriter.() -> Unit) {
|
||||
val bytes = ByteArrayOutputStream()
|
||||
val ps = PrintWriter(bytes)
|
||||
ps.fm()
|
||||
ps.close()
|
||||
addInfo(String(bytes.toByteArray()))
|
||||
}
|
||||
|
||||
private fun DefectType.report(vararg attributes: Pair<DefectAttribute, String>) {
|
||||
defectReport.report(this, location, *attributes)
|
||||
}
|
||||
|
||||
fun addPropertyDiff(defectType: DefectType, diff: NamedDiffEntry) {
|
||||
propertyDiffs.add(diff)
|
||||
defectType.report(VALUE1_A to diff.value1, VALUE2_A to diff.value2)
|
||||
}
|
||||
|
||||
fun addAnnotationDiffs(checker: MethodAnnotationsChecker, diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
annotationDiffs.add(diff.toNamedDiffEntry(checker.name))
|
||||
when {
|
||||
diff.value1 != null && diff.value2 != null ->
|
||||
checker.mismatchDefect.report(VALUE1_A to diff.value1, VALUE2_A to diff.value2)
|
||||
diff.value1 == null && diff.value2 != null ->
|
||||
checker.missing1Defect.report(VALUE2_A to diff.value2)
|
||||
diff.value1 != null && diff.value2 == null ->
|
||||
checker.missing2Defect.report(VALUE1_A to diff.value1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addValueParameterAnnotationDiffs(checker: MethodParameterAnnotationsChecker, index: Int, diffs: List<ListEntryDiff>) {
|
||||
val diffEntryName = "${checker.name}.p$index"
|
||||
for (diff in diffs) {
|
||||
annotationDiffs.add(diff.toNamedDiffEntry(diffEntryName))
|
||||
when {
|
||||
diff.value1 != null && diff.value2 != null ->
|
||||
checker.mismatchDefect.report(
|
||||
VP_INDEX_A to index.toString(),
|
||||
VALUE1_A to diff.value1,
|
||||
VALUE2_A to diff.value2
|
||||
)
|
||||
diff.value1 == null && diff.value2 != null ->
|
||||
checker.missing1Defect.report(
|
||||
METHOD_A to methodId,
|
||||
VP_INDEX_A to index.toString(),
|
||||
VALUE2_A to diff.value2
|
||||
)
|
||||
diff.value1 != null && diff.value2 == null ->
|
||||
checker.missing2Defect.report(
|
||||
METHOD_A to methodId,
|
||||
VP_INDEX_A to index.toString(),
|
||||
VALUE1_A to diff.value1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean = propertyDiffs.isEmpty() && annotationDiffs.isEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
output.tag("h2", "> METHOD " + methodId.escapeHtml())
|
||||
|
||||
for (info in infoParagraphs) {
|
||||
output.tag("p", info)
|
||||
}
|
||||
|
||||
output.propertyDiffTable(header1, header2, propertyDiffs)
|
||||
output.annotationDiffTable(header1, header2, annotationDiffs)
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendMethodReport() {
|
||||
node("METHOD $methodId") {
|
||||
appendNamedDiffEntries(header1, header2, propertyDiffs, "Property")
|
||||
appendNamedDiffEntries(header1, header2, annotationDiffs, "Annotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.PrintWriter
|
||||
|
||||
class MultiFileClassFacadeMetadataReport(private val classInternalName: String, val header1: String, val header2: String) :
|
||||
ComparisonReport {
|
||||
|
||||
private val membersDiffList = ArrayList<DiffEntry>()
|
||||
|
||||
override fun isEmpty() = membersDiffList.isEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
if (isEmpty()) return
|
||||
output.tag("h2", "MULTIFILE CLASS FACADE METADATA $classInternalName")
|
||||
output.listDiff(header1, header2, membersDiffList)
|
||||
}
|
||||
|
||||
fun addMembersListDiffs(diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
membersDiffList.add(diff.toDiffEntry())
|
||||
}
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendMultiFileClassFacadeReport() {
|
||||
if (isNotEmpty()) {
|
||||
node("MULTIFILE FILE FACADE METADATA") {
|
||||
appendDiffEntries(header1, header2, membersDiffList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.PrintWriter
|
||||
|
||||
class MultiFileClassPartMetadataReport(private val classInternalName: String, val header1: String, val header2: String) : ComparisonReport {
|
||||
|
||||
private val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
private var packageReport: PackageMetadataReport? = null
|
||||
override fun isEmpty() = propertyDiffs.isEmpty() && packageReport.isNullOrEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
if (isEmpty()) return
|
||||
|
||||
output.tag("h2", "MULTIFILE CLASS PART METADATA $classInternalName")
|
||||
|
||||
output.propertyDiffTable(header1, header2, propertyDiffs)
|
||||
|
||||
packageReport?.run { writeAsHtml(output) }
|
||||
}
|
||||
|
||||
fun packageReport() = PackageMetadataReport(classInternalName, header1, header2).also { packageReport = it }
|
||||
|
||||
fun addPropertyDiff(diff: NamedDiffEntry) {
|
||||
propertyDiffs.add(diff)
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendMultiFileClassPartReport() {
|
||||
if (isNotEmpty()) {
|
||||
node("MULTIFILE FILE PART METADATA") {
|
||||
appendNamedDiffEntries(header1, header2, propertyDiffs, "Property")
|
||||
|
||||
packageReport?.run { appendPackageMetadataReport() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.PrintWriter
|
||||
|
||||
class PackageMetadataReport(private val classInternalName: String, val header1: String, val header2: String) : ComparisonReport {
|
||||
|
||||
private val membersDiffList = ArrayList<DiffEntry>()
|
||||
|
||||
private val functionReports = ArrayList<MetadataPropertyReport>()
|
||||
private val propertyReports = ArrayList<MetadataPropertyReport>()
|
||||
private val typeAliasReports = ArrayList<MetadataPropertyReport>()
|
||||
private val localDelegatedPropertyReport = ArrayList<MetadataPropertyReport>()
|
||||
|
||||
|
||||
override fun isEmpty() =
|
||||
membersDiffList.isEmpty()
|
||||
&& functionReports.areAllEmpty()
|
||||
&& propertyReports.areAllEmpty()
|
||||
&& typeAliasReports.areAllEmpty()
|
||||
&& localDelegatedPropertyReport.areAllEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
if (isEmpty()) return
|
||||
|
||||
output.tag("h2", "PACKAGE METADATA $classInternalName")
|
||||
output.listDiff(header1, header2, membersDiffList)
|
||||
|
||||
for (report in listOf(
|
||||
functionReports,
|
||||
propertyReports,
|
||||
typeAliasReports,
|
||||
localDelegatedPropertyReport
|
||||
).flatten()) {
|
||||
report.writeAsHtml(output)
|
||||
}
|
||||
}
|
||||
|
||||
fun addMembersListDiffs(diffs: List<ListEntryDiff>) {
|
||||
for (diff in diffs) {
|
||||
membersDiffList.add(diff.toDiffEntry())
|
||||
}
|
||||
}
|
||||
|
||||
fun functionReport(id: String) = MetadataPropertyReport("FUNCTION $id", header1, header2).also { functionReports.add(it) }
|
||||
|
||||
fun propertyReport(id: String) = MetadataPropertyReport("PROPERTY $id", header1, header2).also { propertyReports.add(it) }
|
||||
|
||||
fun typeAliasReport(id: String) = MetadataPropertyReport("TYPE ALIAS $id", header1, header2).also { typeAliasReports.add(it) }
|
||||
|
||||
fun localDelegatedPropertyReport(id: String) =
|
||||
MetadataPropertyReport("LOCAL DELEGATED PROPERTY $id", header1, header2).also { localDelegatedPropertyReport.add(it) }
|
||||
|
||||
fun TextTreeBuilderContext.appendPackageMetadataReport() {
|
||||
if (isNotEmpty()) {
|
||||
node("PACKAGE METADATA") {
|
||||
appendDiffEntries(header1, header2, membersDiffList)
|
||||
|
||||
for (report in listOf(
|
||||
functionReports,
|
||||
propertyReports,
|
||||
typeAliasReports,
|
||||
localDelegatedPropertyReport
|
||||
).flatten()) {
|
||||
with(report) { appendReport() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.*
|
||||
import org.jetbrains.kotlin.abicmp.defects.DefectInfo
|
||||
import org.jetbrains.kotlin.abicmp.defects.Location
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
|
||||
class SummaryReport {
|
||||
private val defectsByInfo: MutableMap<DefectInfo, MutableSet<Location>> = HashMap()
|
||||
|
||||
fun add(defectReport: DefectReport) {
|
||||
for (defect in defectReport.defects) {
|
||||
defectsByInfo.getOrPut(defect.info) { HashSet() }.add(defect.location)
|
||||
}
|
||||
}
|
||||
|
||||
fun totalUnique() = defectsByInfo.keys.size
|
||||
|
||||
fun totalDefects() = defectsByInfo.values.sumOf { it.size }
|
||||
|
||||
fun writeReport(outputFile: File) {
|
||||
PrintWriter(outputFile).use { out ->
|
||||
out.tag("html") {
|
||||
out.tag("head") {
|
||||
out.tag("style", REPORT_CSS)
|
||||
}
|
||||
out.tag("body") {
|
||||
out.writeReportBody()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun PrintWriter.writeReportBody() {
|
||||
tag("p") {
|
||||
println("Total defects: ${totalDefects().tag("b")}, unique: ${totalUnique().tag("b")}")
|
||||
}
|
||||
|
||||
for (info in defectsByInfo.keys.sorted()) {
|
||||
val locations = defectsByInfo[info]!!
|
||||
writeDefectInfo(info)
|
||||
writeLocations(locations.toList().sorted())
|
||||
}
|
||||
}
|
||||
|
||||
private fun PrintWriter.writeDefectInfo(info: DefectInfo) {
|
||||
tag("p") {
|
||||
tag("h2") {
|
||||
println("[${info.type.id}] ${info.type.messageText}")
|
||||
}
|
||||
}
|
||||
table {
|
||||
for ((attr, value) in info.attributes) {
|
||||
tableData(attr.htmlId, value.toHtmlString().withTag("code"))
|
||||
}
|
||||
}
|
||||
println(" <br/>")
|
||||
}
|
||||
|
||||
private fun PrintWriter.writeLocations(locations: List<Location>) {
|
||||
table {
|
||||
when (locations.first()) {
|
||||
is Location.JarFile ->
|
||||
tableHeader("jar")
|
||||
is Location.Class ->
|
||||
tableHeader("jar", "class")
|
||||
is Location.Method ->
|
||||
tableHeader("jar", "class", "method")
|
||||
is Location.Field ->
|
||||
tableHeader("jar", "class", "field")
|
||||
}
|
||||
for (location in locations) {
|
||||
when (location) {
|
||||
is Location.JarFile ->
|
||||
tableDataWithClass(
|
||||
"location",
|
||||
location.jarFileName.replace("-", NON_BREAKING_HYPHEN)
|
||||
)
|
||||
is Location.Class ->
|
||||
tableDataWithClass(
|
||||
"location",
|
||||
location.jarFileName.replace("-", NON_BREAKING_HYPHEN),
|
||||
location.className.withTag("code")
|
||||
)
|
||||
is Location.Method ->
|
||||
tableDataWithClass(
|
||||
"location",
|
||||
location.jarFileName.replace("-", NON_BREAKING_HYPHEN),
|
||||
location.className.withTag("code"),
|
||||
location.methodName.withTag("code")
|
||||
)
|
||||
is Location.Field ->
|
||||
tableDataWithClass(
|
||||
"location",
|
||||
location.jarFileName.replace("-", NON_BREAKING_HYPHEN),
|
||||
location.className.withTag("code"),
|
||||
location.fieldName.withTag("code")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.PrintWriter
|
||||
|
||||
class SyntheticClassMetadataReport(private val classInternalName: String, val header1: String, val header2: String) : ComparisonReport {
|
||||
|
||||
private val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
|
||||
private var functionReport: MetadataPropertyReport? = null
|
||||
|
||||
override fun isEmpty() = propertyDiffs.isEmpty() && functionReport.isNullOrEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
if (isEmpty()) return
|
||||
output.tag("h2", "SYNTHETIC CLASS METADATA $classInternalName")
|
||||
functionReport?.run { writeAsHtml(output) }
|
||||
}
|
||||
|
||||
fun functionReport() = MetadataPropertyReport("function", header1, header2).also { functionReport = it }
|
||||
|
||||
fun addPropertyDiff(diff: NamedDiffEntry) {
|
||||
propertyDiffs.add(diff)
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendSyntheticClassMetadataReport() {
|
||||
if (isNotEmpty()) {
|
||||
node("SYNTHETIC CLASS METADATA") {
|
||||
appendNamedDiffEntries(header1, header2, propertyDiffs, "Property")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
const val REPORT_CSS = """
|
||||
table, th, td {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
padding: 2px;
|
||||
}
|
||||
th {
|
||||
background: #B0B0B0;
|
||||
}
|
||||
td {
|
||||
background: #EFEFB0;
|
||||
vertical-align: top;
|
||||
}
|
||||
td.location {
|
||||
background: #E0E0E0;
|
||||
vertical-align: top;
|
||||
}
|
||||
hr {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
"""
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import java.io.PrintWriter
|
||||
|
||||
class TextTreeBuilderContext(val ident: String, val out: PrintWriter)
|
||||
|
||||
private fun String.addIdent(ident: String) = "${ident}${replace("\n", "\n$ident")}"
|
||||
|
||||
fun dumpTree(out: PrintWriter, body: TextTreeBuilderContext.() -> Unit) {
|
||||
TextTreeBuilderContext("", out).also(body)
|
||||
out.flush()
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.node(header: String, body: TextTreeBuilderContext.() -> Unit = {}) {
|
||||
out.println(header.addIdent(ident))
|
||||
TextTreeBuilderContext(ident + "\t", out).also(body)
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendDiffEntries(header1: String, header2: String, diffs: ArrayList<DiffEntry>) {
|
||||
if (diffs.isNotEmpty()) {
|
||||
for (diff in diffs) {
|
||||
node(header1) {
|
||||
node(diff.value1)
|
||||
}
|
||||
node(header2) {
|
||||
node(diff.value2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendNamedDiffEntries(header1: String, header2: String, diffEntries: ArrayList<NamedDiffEntry>, title: String) {
|
||||
if (diffEntries.isNotEmpty()) {
|
||||
for (entry in diffEntries) {
|
||||
node("$title: ${entry.name}") {
|
||||
node(header1) {
|
||||
node(entry.value1)
|
||||
}
|
||||
node(header2) {
|
||||
node(entry.value2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.reports
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.*
|
||||
import java.io.PrintWriter
|
||||
|
||||
fun String.withTag(tagName: String) = "<$tagName>$this</$tagName>"
|
||||
|
||||
fun PrintWriter.propertyDiffTable(header1: String, header2: String, propertyDiffs: List<NamedDiffEntry>) {
|
||||
if (propertyDiffs.isNotEmpty()) {
|
||||
table {
|
||||
tableHeader("Property", header1, header2)
|
||||
for (pd in propertyDiffs) {
|
||||
tableData(pd.name, pd.value1.toHtmlString().withTag("code"), pd.value2.toHtmlString().withTag("code"))
|
||||
}
|
||||
}
|
||||
println(" ")
|
||||
}
|
||||
}
|
||||
|
||||
fun PrintWriter.annotationDiffTable(header1: String, header2: String, annotationDiffs: List<NamedDiffEntry>) {
|
||||
if (annotationDiffs.isNotEmpty()) {
|
||||
table {
|
||||
tableHeader("Annotation", header1, header2)
|
||||
for (ad in annotationDiffs) {
|
||||
tableData(ad.name, ad.value1.escapeHtml().withTag("code"), ad.value2.escapeHtml().withTag("code"))
|
||||
}
|
||||
}
|
||||
println(" ")
|
||||
}
|
||||
}
|
||||
|
||||
fun PrintWriter.listDiff(header1: String, header2: String, listDiffs: List<DiffEntry>) {
|
||||
if (listDiffs.isNotEmpty()) {
|
||||
table {
|
||||
tableHeader(header1, header2)
|
||||
for (me in listDiffs) {
|
||||
tableData(me.value1.escapeHtml().withTag("code"), me.value2.escapeHtml().withTag("code"))
|
||||
}
|
||||
}
|
||||
println(" ")
|
||||
}
|
||||
}
|
||||
|
||||
fun ComparisonReport?.isNullOrEmpty() = this == null || isEmpty()
|
||||
|
||||
fun List<ComparisonReport>.areAllEmpty() = !any { it.isNotEmpty() }
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import kotlinx.metadata.ExperimentalContextReceivers
|
||||
import kotlinx.metadata.hasConstant
|
||||
import kotlinx.metadata.isVar
|
||||
import kotlinx.metadata.jvm.*
|
||||
import kotlinx.metadata.visibility
|
||||
import org.jetbrains.kotlin.abicmp.*
|
||||
import org.jetbrains.kotlin.abicmp.checkers.*
|
||||
import org.jetbrains.kotlin.kotlinp.*
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
|
||||
private val allClassCheckers = listOf(
|
||||
classPropertyChecker(ClassNode::version),
|
||||
classPropertyChecker(ClassNode::access) { v -> "${v.toString(2)} ${v.classFlags()}" },
|
||||
classPropertyChecker("internalName", ClassNode::name),
|
||||
classPropertyChecker(ClassNode::signature),
|
||||
classPropertyChecker("superClassInternalName", ClassNode::superName),
|
||||
classPropertyChecker("superInterfaces") { (it.interfaces as List<String>).sorted() },
|
||||
classPropertyChecker(ClassNode::sourceFile),
|
||||
classPropertyChecker(ClassNode::outerClass),
|
||||
classPropertyChecker(ClassNode::outerMethod),
|
||||
classPropertyChecker(ClassNode::outerMethodDesc),
|
||||
ClassAnnotationsChecker(ClassNode::visibleAnnotations),
|
||||
ClassAnnotationsChecker(ClassNode::invisibleAnnotations),
|
||||
InnerClassesListChecker(),
|
||||
MethodsListChecker(),
|
||||
FieldsListChecker()
|
||||
)
|
||||
|
||||
private val allMethodCheckers = listOf(
|
||||
methodPropertyChecker(MethodNode::access) { v -> "${v.toString(2)} ${v.methodFlags()}" },
|
||||
methodPropertyChecker("methodName", MethodNode::name),
|
||||
methodPropertyChecker(MethodNode::desc),
|
||||
methodPropertyChecker(MethodNode::signature),
|
||||
methodPropertyChecker("exceptions") { it.exceptions.listOfNotNull<String>().sorted() },
|
||||
methodPropertyChecker("annotationDefault") { it.annotationDefault?.toAnnotationArgumentValue() },
|
||||
MethodAnnotationsChecker(MethodNode::visibleAnnotations),
|
||||
MethodAnnotationsChecker(MethodNode::invisibleAnnotations),
|
||||
MethodParameterAnnotationsChecker(MethodNode::visibleParameterAnnotations),
|
||||
MethodParameterAnnotationsChecker(MethodNode::invisibleParameterAnnotations)
|
||||
)
|
||||
|
||||
private val allConstructorMetadataCheckers = listOf(
|
||||
constructorMetadataPropertyChecker("versionRequirements") { it.versionRequirements.stringifyRelevantRequirements() },
|
||||
constructorMetadataPropertyChecker("modifiers") { printConstructorModifiers(it) },
|
||||
constructorMetadataPropertyChecker("valueParameters") { it.valueParameters.stringifyValueParameters() }
|
||||
)
|
||||
|
||||
private val allFunctionMetadataCheckers = listOf(
|
||||
functionMetadataPropertyChecker("lambdaClassOriginName") { it.lambdaClassOriginName.toString() },
|
||||
functionMetadataPropertyChecker("versionRequirements") { it.versionRequirements.stringifyRelevantRequirements() },
|
||||
functionMetadataPropertyChecker("contextReceiverTypes") {
|
||||
@OptIn(ExperimentalContextReceivers::class)
|
||||
it.contextReceiverTypes.stringifyTypeListSorted()
|
||||
},
|
||||
functionMetadataPropertyChecker("modifiers") { printFunctionModifiers(it) },
|
||||
functionMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() },
|
||||
functionMetadataPropertyChecker("receiverParameterType") {
|
||||
it.receiverParameterType?.let { type -> printType(type) } ?: PROPERTY_VAL_STUB
|
||||
},
|
||||
functionMetadataPropertyChecker("valueParameters") { it.valueParameters.stringifyValueParameters() },
|
||||
functionMetadataPropertyChecker("contract") {
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
it.contract?.let { contract -> printContract(contract) } ?: PROPERTY_VAL_STUB
|
||||
}
|
||||
)
|
||||
|
||||
private val allPropertyMetadataCheckers = listOf(
|
||||
propertyMetadataPropertyChecker("versionRequirements") { it.versionRequirements.stringifyRelevantRequirements() },
|
||||
propertyMetadataPropertyChecker("fieldSignature") { it.fieldSignature?.toString() ?: PROPERTY_VAL_STUB },
|
||||
propertyMetadataPropertyChecker("getterSignature") { it.getterSignature?.toString() ?: PROPERTY_VAL_STUB },
|
||||
propertyMetadataPropertyChecker("setterSignature") { it.setterSignature?.toString() ?: PROPERTY_VAL_STUB },
|
||||
propertyMetadataPropertyChecker("syntheticMethodForAnnotations") { it.syntheticMethodForAnnotations?.toString() ?: PROPERTY_VAL_STUB },
|
||||
propertyMetadataPropertyChecker("syntheticMethodForDelegate") { it.syntheticMethodForDelegate?.toString() ?: PROPERTY_VAL_STUB },
|
||||
propertyMetadataPropertyChecker("isMovedFromInterfaceCompanion") { it.isMovedFromInterfaceCompanion.toString() },
|
||||
propertyMetadataPropertyChecker("contextReceiverTypes") {
|
||||
@OptIn(ExperimentalContextReceivers::class)
|
||||
it.contextReceiverTypes.stringifyTypeListSorted()
|
||||
},
|
||||
propertyMetadataPropertyChecker("modifiers") { printPropertyModifiers(it) },
|
||||
propertyMetadataPropertyChecker("isVar") { it.isVar.toString() },
|
||||
propertyMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() },
|
||||
propertyMetadataPropertyChecker("receiverParameterType") {
|
||||
it.receiverParameterType?.let { type -> printType(type) } ?: PROPERTY_VAL_STUB
|
||||
},
|
||||
propertyMetadataPropertyChecker("returnType") { printType(it.returnType) },
|
||||
propertyMetadataPropertyChecker("hasConstant") { it.hasConstant.toString() },
|
||||
propertyMetadataPropertyChecker("getterModifiers") { printPropertyAccessorModifiers(it.getter) },
|
||||
propertyMetadataPropertyChecker("setterModifiers") {
|
||||
it.setter?.let { setter -> printPropertyAccessorModifiers(setter) } ?: PROPERTY_VAL_STUB
|
||||
},
|
||||
propertyMetadataPropertyChecker("setterValueParameter") {
|
||||
it.setterParameter?.let { param -> printValueParameter(param) } ?: PROPERTY_VAL_STUB
|
||||
}
|
||||
)
|
||||
|
||||
private val allTypeAliasMetadataCheckers = listOf(
|
||||
typeAliasMetadataPropertyChecker("versionRequirements") { it.versionRequirements.stringifyRelevantRequirements() },
|
||||
typeAliasMetadataPropertyChecker("annotations") { it.annotations.stringifyAnnotations() },
|
||||
typeAliasMetadataPropertyChecker("visibility") { it.visibility.toString() },
|
||||
typeAliasMetadataPropertyChecker("typeParameters") { it.typeParameters.stringifyTypeParameters() },
|
||||
typeAliasMetadataPropertyChecker("underlyingType") { printType(it.underlyingType) },
|
||||
typeAliasMetadataPropertyChecker("expandedType") { printType(it.expandedType) }
|
||||
)
|
||||
|
||||
private val allFieldCheckers = listOf(
|
||||
fieldPropertyChecker(FieldNode::access) { v -> "${v.toString(2)} ${v.fieldFlags()}" },
|
||||
fieldPropertyChecker("fieldName", FieldNode::name),
|
||||
fieldPropertyChecker(FieldNode::desc),
|
||||
fieldPropertyChecker(FieldNode::signature),
|
||||
fieldPropertyChecker("initialValue", FieldNode::value),
|
||||
FieldAnnotationsChecker(FieldNode::visibleAnnotations),
|
||||
FieldAnnotationsChecker(FieldNode::invisibleAnnotations)
|
||||
)
|
||||
|
||||
private val allClassMetadataCheckers = listOf(
|
||||
classMetadataListChecker("constructors") { loadConstructors(it).keys.toList() },
|
||||
classMetadataListChecker("functions") { loadFunctions(it.kmClass).keys.toList() },
|
||||
classMetadataListChecker("properties") { loadProperties(it.kmClass).keys.toList() },
|
||||
classMetadataListChecker("typeAliases") { it.kmClass.typeAliases.map { typeAlias -> typeAlias.name } },
|
||||
classMetadataListChecker("nestedClasses") { it.kmClass.nestedClasses },
|
||||
classMetadataListChecker("enumEntries") { it.kmClass.enumEntries },
|
||||
classMetadataListChecker("sealedSubclasses") { it.kmClass.sealedSubclasses },
|
||||
classMetadataListChecker("localDelegatedProperties") { loadLocalDelegatedProperties(it).keys.toList() },
|
||||
|
||||
classMetadataPropertyChecker("typeParameters") { it.kmClass.typeParameters.stringifyTypeParameters() },
|
||||
classMetadataPropertyChecker("superTypes") {
|
||||
it.kmClass.supertypes.map { type -> printType(type) }.sorted().joinToString(prefix = "[", postfix = "]")
|
||||
},
|
||||
classMetadataPropertyChecker("companionObject") { it.kmClass.companionObject.toString() },
|
||||
classMetadataPropertyChecker("inlineClassUnderlyingPropertyName") { it.kmClass.inlineClassUnderlyingPropertyName.toString() },
|
||||
classMetadataPropertyChecker("inlineClassUnderlyingType") {
|
||||
it.kmClass.inlineClassUnderlyingType?.let { type -> printType(type) } ?: "---"
|
||||
},
|
||||
classMetadataPropertyChecker("contextReceiverTypes") {
|
||||
@OptIn(ExperimentalContextReceivers::class)
|
||||
it.kmClass.contextReceiverTypes.stringifyTypeListSorted()
|
||||
},
|
||||
classMetadataPropertyChecker("versionRequirements") { it.kmClass.versionRequirements.stringifyRelevantRequirements() }
|
||||
)
|
||||
|
||||
private val allPackageMetadataCheckers = listOf(
|
||||
fileFacadeMetadataListChecker("functions") { loadFunctions(it).keys.toList() },
|
||||
fileFacadeMetadataListChecker("properties") { loadProperties(it).keys.toList() },
|
||||
fileFacadeMetadataListChecker("typeAliases") { loadTypeAliases(it).keys.toList() },
|
||||
fileFacadeMetadataListChecker("localDelegatedProperties") { loadLocalDelegatedProperties(it).keys.toList() }
|
||||
)
|
||||
|
||||
private val allMultifileClassFacadeMetadataCheckers = listOf(
|
||||
multiFileClassFacadeMetadataListChecker("partClassNames") { it.partClassNames }
|
||||
)
|
||||
|
||||
private val allMultifileClassPartMetadataCheckers = listOf(
|
||||
multiFileClassPartMetadataPropertyChecker("facadeClassName") { it.facadeClassName }
|
||||
)
|
||||
|
||||
private val allSyntheticClassMetadataCheckers = listOf(
|
||||
syntheticClassMetadataPropertyChecker("isLambda") { it.isLambda.toString() },
|
||||
syntheticClassMetadataPropertyChecker("function") {
|
||||
it.kmLambda?.function?.let { function ->
|
||||
printFunction(
|
||||
function,
|
||||
KotlinpSettings(
|
||||
isVerbose = true,
|
||||
sortDeclarations = true
|
||||
)
|
||||
)
|
||||
} ?: PROPERTY_VAL_STUB
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CheckerConfigurationBuilder {
|
||||
private val enabledExclusively = HashSet<String>()
|
||||
private val disabled = HashSet<String>()
|
||||
|
||||
fun enableExclusively(name: String) {
|
||||
enabledExclusively.add(name)
|
||||
}
|
||||
|
||||
fun disable(name: String) {
|
||||
disabled.add(name)
|
||||
}
|
||||
|
||||
fun build() = CheckerConfiguration(enabledExclusively, disabled)
|
||||
}
|
||||
|
||||
inline fun checkerConfiguration(b: CheckerConfigurationBuilder.() -> Unit): CheckerConfiguration {
|
||||
val builder = CheckerConfigurationBuilder()
|
||||
builder.b()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
class CheckerConfiguration(private val enabledExclusively: Set<String>, private val disabled: Set<String>) {
|
||||
|
||||
private fun <T : Checker> List<T>.filterOutDisabled() = filter { it.isEnabled() }
|
||||
|
||||
val enabledClassCheckers: List<ClassChecker> = allClassCheckers.filterOutDisabled()
|
||||
val enabledMethodCheckers: List<MethodChecker> = allMethodCheckers.filterOutDisabled()
|
||||
val enabledFieldCheckers: List<FieldChecker> = allFieldCheckers.filterOutDisabled()
|
||||
val enabledPropertyMetadataCheckers = allPropertyMetadataCheckers.filterOutDisabled()
|
||||
val enabledConstructorMetadataCheckers = allConstructorMetadataCheckers.filterOutDisabled()
|
||||
val enabledFunctionMetadataCheckers = allFunctionMetadataCheckers.filterOutDisabled()
|
||||
val enabledTypeAliasMetadataCheckers = allTypeAliasMetadataCheckers.filterOutDisabled()
|
||||
val enabledClassMetadataCheckers = allClassMetadataCheckers.filterOutDisabled()
|
||||
val enabledPackageMetadataCheckers = allPackageMetadataCheckers.filterOutDisabled()
|
||||
val enabledMultifileClassFacadeMetadataCheckers = allMultifileClassFacadeMetadataCheckers.filterOutDisabled()
|
||||
val enabledMultifileClassPartMetadataCheckers = allMultifileClassPartMetadataCheckers.filterOutDisabled()
|
||||
val enabledAllSyntheticClassMetadataCheckers = allSyntheticClassMetadataCheckers.filterOutDisabled()
|
||||
|
||||
private fun Checker.isEnabled(): Boolean {
|
||||
if (enabledExclusively.isNotEmpty() && name !in enabledExclusively) return false
|
||||
return name !in disabled
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.checkers.*
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassMetadataReport
|
||||
|
||||
class ClassMetadataTask(
|
||||
private val configuration: CheckerConfiguration,
|
||||
private val metadata1: KotlinClassMetadata.Class,
|
||||
private val metadata2: KotlinClassMetadata.Class,
|
||||
private val report: ClassMetadataReport,
|
||||
) : Runnable {
|
||||
override fun run() {
|
||||
for (checker in configuration.enabledClassMetadataCheckers) {
|
||||
checker.check(metadata1, metadata2, report)
|
||||
}
|
||||
|
||||
checkMetadataMembers(
|
||||
metadata1,
|
||||
metadata2,
|
||||
configuration.enabledConstructorMetadataCheckers,
|
||||
report::constructorReport,
|
||||
::loadConstructors
|
||||
)
|
||||
|
||||
checkMetadataMembers(
|
||||
metadata1,
|
||||
metadata2,
|
||||
configuration.enabledFunctionMetadataCheckers,
|
||||
report::functionReport
|
||||
) { loadFunctions(it.kmClass) }
|
||||
|
||||
checkMetadataMembers(
|
||||
metadata1,
|
||||
metadata2,
|
||||
configuration.enabledPropertyMetadataCheckers,
|
||||
report::propertyReport
|
||||
) { loadProperties(it.kmClass) }
|
||||
|
||||
checkMetadataMembers(
|
||||
metadata1,
|
||||
metadata2,
|
||||
configuration.enabledTypeAliasMetadataCheckers,
|
||||
report::typeAliasReport
|
||||
) { loadTypeAliases(it.kmClass) }
|
||||
|
||||
checkMetadataMembers(
|
||||
metadata1,
|
||||
metadata2,
|
||||
configuration.enabledPropertyMetadataCheckers,
|
||||
report::localDelegatedPropertyReport
|
||||
) { loadLocalDelegatedProperties(it) }
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.checkers.loadFields
|
||||
import org.jetbrains.kotlin.abicmp.checkers.loadMethods
|
||||
import org.jetbrains.kotlin.abicmp.classFlags
|
||||
import org.jetbrains.kotlin.abicmp.isSynthetic
|
||||
import org.jetbrains.kotlin.abicmp.reports.ClassReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.ListEntryDiff
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import org.jetbrains.kotlin.kotlinp.Kotlinp
|
||||
import org.jetbrains.kotlin.kotlinp.KotlinpSettings
|
||||
import org.jetbrains.kotlin.kotlinp.readKotlinClassHeader
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
|
||||
class ClassTask(
|
||||
private val checkerConfiguration: CheckerConfiguration,
|
||||
private val class1: ClassNode,
|
||||
private val class2: ClassNode,
|
||||
private val report: ClassReport,
|
||||
) : Runnable {
|
||||
|
||||
override fun run() {
|
||||
addClassInfo()
|
||||
checkMetadata()
|
||||
|
||||
for (checker in checkerConfiguration.enabledClassCheckers) {
|
||||
checker.check(class1, class2, report)
|
||||
}
|
||||
|
||||
checkMethods()
|
||||
checkFields()
|
||||
}
|
||||
|
||||
private fun checkMetadata() {
|
||||
|
||||
fun ClassNode.getMetadata(): KotlinClassMetadata? {
|
||||
val classWriter = ClassWriter(0)
|
||||
accept(classWriter)
|
||||
val classReader = ClassReader(classWriter.toByteArray())
|
||||
return classReader.readKotlinClassHeader()?.run { KotlinClassMetadata.readStrict(this) }
|
||||
}
|
||||
|
||||
val kotlinp = Kotlinp(KotlinpSettings(isVerbose = false, sortDeclarations = true))
|
||||
val metadata1 = class1.getMetadata()
|
||||
val metadata2 = class2.getMetadata()
|
||||
|
||||
if (metadata1 == null && metadata2 == null) return
|
||||
|
||||
if (metadata1 == null || metadata2 == null) {
|
||||
report.addMetadataDiff(
|
||||
ListEntryDiff(
|
||||
metadata1?.run { kotlinp.renderClassFile(metadata1) },
|
||||
metadata2?.run { kotlinp.renderClassFile(metadata2) })
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (metadata1::class != metadata2::class) {
|
||||
report.addMetadataDiff(ListEntryDiff(metadata1::class.simpleName, metadata2::class.simpleName))
|
||||
return
|
||||
}
|
||||
|
||||
when (metadata1) {
|
||||
is KotlinClassMetadata.Class -> ClassMetadataTask(
|
||||
checkerConfiguration,
|
||||
metadata1,
|
||||
metadata2 as KotlinClassMetadata.Class,
|
||||
report.classMetadataReport()
|
||||
).run()
|
||||
is KotlinClassMetadata.FileFacade -> PackageMetadataTask(
|
||||
checkerConfiguration,
|
||||
metadata1.kmPackage,
|
||||
(metadata2 as KotlinClassMetadata.FileFacade).kmPackage,
|
||||
report.fileFacadeMetadataReport()
|
||||
).run()
|
||||
is KotlinClassMetadata.MultiFileClassFacade -> MultiFileClassFacadeMetadataTask(
|
||||
checkerConfiguration,
|
||||
metadata1,
|
||||
metadata2 as KotlinClassMetadata.MultiFileClassFacade,
|
||||
report.multiFileClassFacadeMetadataReport()
|
||||
)
|
||||
is KotlinClassMetadata.MultiFileClassPart -> MultiFileClassPartMetadataTask(
|
||||
checkerConfiguration,
|
||||
metadata1,
|
||||
metadata2 as KotlinClassMetadata.MultiFileClassPart,
|
||||
report.multiFileClassPartMetadataReport()
|
||||
)
|
||||
is KotlinClassMetadata.SyntheticClass -> SyntheticMetadataTask(
|
||||
checkerConfiguration,
|
||||
metadata1,
|
||||
metadata2 as KotlinClassMetadata.SyntheticClass,
|
||||
report.syntheticMetadataReport()
|
||||
)
|
||||
is KotlinClassMetadata.Unknown -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addClassInfo() {
|
||||
report.info {
|
||||
tag("p") {
|
||||
tag("b", report.header1)
|
||||
println(": ${class1.access.classFlags()}")
|
||||
}
|
||||
tag("p") {
|
||||
tag("b", report.header2)
|
||||
println(": ${class2.access.classFlags()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkMethods() {
|
||||
val methods1 = class1.loadMethods()
|
||||
val methods2 = class2.loadMethods()
|
||||
|
||||
val commonIds = methods1.keys.intersect(methods2.keys).sorted()
|
||||
for (id in commonIds) {
|
||||
val method1 = methods1[id]!!
|
||||
val method2 = methods2[id]!!
|
||||
if (method1.access.isSynthetic() && method2.access.isSynthetic()) continue
|
||||
val methodReport = report.methodReport(id)
|
||||
MethodTask(checkerConfiguration, method1, method2, methodReport).run()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFields() {
|
||||
val fields1 = class1.loadFields()
|
||||
val fields2 = class2.loadFields()
|
||||
|
||||
val commonIds = fields1.keys.intersect(fields2.keys).sorted()
|
||||
for (id in commonIds) {
|
||||
val field1 = fields1[id]!!
|
||||
val field2 = fields2[id]!!
|
||||
if (field1.access.isSynthetic() && field2.access.isSynthetic()) continue
|
||||
val fieldReport = report.fieldReport(id)
|
||||
FieldTask(checkerConfiguration, field1, field2, fieldReport).run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MethodNode.methodId() = "$name$desc"
|
||||
|
||||
fun FieldNode.fieldId() = "$name:$desc"
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.SummaryReport
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
import java.util.jar.JarFile
|
||||
|
||||
class DirTask(
|
||||
private val dir1: File,
|
||||
private val dir2: File,
|
||||
private val id1: String?,
|
||||
private val id2: String?,
|
||||
private val header1: String,
|
||||
private val header2: String,
|
||||
private val reportDir: File,
|
||||
private val checkerConfiguration: CheckerConfiguration = checkerConfiguration {},
|
||||
) : Runnable {
|
||||
|
||||
private val executor = Executors.newWorkStealingPool()
|
||||
|
||||
private val lastNameIndex = HashMap<String, Int>()
|
||||
private val tasks = ArrayList<Pair<JarTask, Future<*>>>()
|
||||
|
||||
override fun run() {
|
||||
println("Comparing directories: $dir1, $dir2")
|
||||
walkRecursively(dir1, dir2)
|
||||
val summary = SummaryReport()
|
||||
tasks.forEach { (jarTask, future) ->
|
||||
future.get()
|
||||
summary.add(jarTask.defectReport)
|
||||
}
|
||||
|
||||
val summaryFile = File(reportDir, "SUMMARY.html")
|
||||
println("Writing summary: $summaryFile")
|
||||
summary.writeReport(summaryFile)
|
||||
println("Done, ${summary.totalDefects()} defects, ${summary.totalUnique()} unique")
|
||||
}
|
||||
|
||||
private fun walkRecursively(subdir1: File, subdir2: File) {
|
||||
val files1 = subdir1.listFiles() ?: return
|
||||
for (file1 in files1) {
|
||||
val file2 = File(subdir2, file1.name.replaceIfNotNull(id1, id2))
|
||||
if (file1.canRead() && file2.exists() && file2.canRead()) {
|
||||
if (file1.isDirectory) {
|
||||
if (file2.isDirectory) {
|
||||
println("Comparing subdirectories: $file1, $file2")
|
||||
walkRecursively(file1, file2)
|
||||
}
|
||||
} else if (file1.name.endsWith(".jar")) {
|
||||
println("Comparing jars: $file1, $file2")
|
||||
val index0 = lastNameIndex.getOrElse(file1.name) { 0 }
|
||||
val index = index0 + 1
|
||||
lastNameIndex[file1.name] = index
|
||||
val jarTaskHeader = file1.name.replaceIfNotNull(id1, "").replace(".jar", "")
|
||||
val reportFile = File(reportDir, "$jarTaskHeader-REPORT-$index.html")
|
||||
val jarTask = JarTask(
|
||||
jarTaskHeader,
|
||||
JarFile(file1), JarFile(file2),
|
||||
header1, header2,
|
||||
reportFile, checkerConfiguration
|
||||
)
|
||||
tasks.add(jarTask to executor.submit(jarTask))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.replaceIfNotNull(pattern: String?, replaceWith: String?) =
|
||||
if (pattern != null && replaceWith != null)
|
||||
replace(pattern, replaceWith)
|
||||
else
|
||||
this
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.fieldFlags
|
||||
import org.jetbrains.kotlin.abicmp.reports.FieldReport
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import org.jetbrains.org.objectweb.asm.tree.FieldNode
|
||||
|
||||
class FieldTask(
|
||||
private val checkerConfiguration: CheckerConfiguration,
|
||||
private val field1: FieldNode,
|
||||
private val field2: FieldNode,
|
||||
private val report: FieldReport,
|
||||
) : Runnable {
|
||||
|
||||
override fun run() {
|
||||
addFieldInfo()
|
||||
|
||||
for (checker in checkerConfiguration.enabledFieldCheckers) {
|
||||
checker.check(field1, field2, report)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addFieldInfo() {
|
||||
report.info {
|
||||
tag("p") {
|
||||
tag("b", report.header1)
|
||||
println(": ${field1.access.fieldFlags()}")
|
||||
}
|
||||
tag("p") {
|
||||
tag("b", report.header2)
|
||||
println(": ${field2.access.fieldFlags()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.checkers.GenericMetadataChecker
|
||||
import org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport
|
||||
|
||||
class GenericMetadataTask<T>(
|
||||
private val metadata1: T,
|
||||
private val metadata2: T,
|
||||
private val report: MetadataPropertyReport,
|
||||
private val checkers: List<GenericMetadataChecker<T>>
|
||||
) : Runnable {
|
||||
override fun run() {
|
||||
for (checker in checkers) {
|
||||
checker.check(metadata1, metadata2, report)
|
||||
}
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.checkers.isSamAdapterName
|
||||
import org.jetbrains.kotlin.abicmp.classFlags
|
||||
import org.jetbrains.kotlin.abicmp.isSynthetic
|
||||
import org.jetbrains.kotlin.abicmp.listOfNotNull
|
||||
import org.jetbrains.kotlin.abicmp.reports.JarReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.REPORT_CSS
|
||||
import org.jetbrains.kotlin.abicmp.reports.isNotEmpty
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.InnerClassNode
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.PrintWriter
|
||||
import java.util.jar.JarFile
|
||||
|
||||
class JarTask(
|
||||
header: String,
|
||||
private val jarFile1: JarFile,
|
||||
private val jarFile2: JarFile,
|
||||
private val header1: String,
|
||||
private val header2: String,
|
||||
private val outputFile: File,
|
||||
private val checkerConfiguration: CheckerConfiguration,
|
||||
) : Runnable {
|
||||
|
||||
private val ignoreAnonymousOrLocalClasses = true
|
||||
private val ignoreJavaClasses = true
|
||||
|
||||
private val report = JarReport(header, header1, header2, jarFile1.name, jarFile2.name)
|
||||
|
||||
val defectReport get() = report.defectReport
|
||||
|
||||
private var totalDiffs = 0
|
||||
|
||||
private val names1 = HashSet<String>()
|
||||
|
||||
override fun run() {
|
||||
println("Comparing jars: ${jarFile1.name}, ${jarFile2.name}")
|
||||
|
||||
addJarsInfo()
|
||||
|
||||
checkJarFile1()
|
||||
checkJarFile2()
|
||||
|
||||
writeReportIfRequired()
|
||||
}
|
||||
|
||||
private fun checkJarFile1() {
|
||||
jarFile1.stream().forEach { entry1 ->
|
||||
val name1 = entry1.name
|
||||
if (name1.endsWith(".class")) {
|
||||
names1.add(name1)
|
||||
val entry2 = jarFile2.getEntry(name1)
|
||||
if (entry2 == null) {
|
||||
val classNode = parseClassNode(jarFile1.getInputStream(entry1))
|
||||
if (!classNode.shouldBeIgnored()) {
|
||||
report.addMissingClassName2("$name1 ${classNode.access.classFlags()}")
|
||||
++totalDiffs
|
||||
}
|
||||
} else {
|
||||
val class1 = parseClassNode(jarFile1.getInputStream(entry1))
|
||||
val class2 = parseClassNode(jarFile2.getInputStream(entry2))
|
||||
|
||||
if (!class1.shouldBeIgnored() || !class2.shouldBeIgnored()) {
|
||||
println("Comparing classes: ${class1.name}")
|
||||
val classReport = report.classReport(class1.name)
|
||||
val classTask = ClassTask(checkerConfiguration, class1, class2, classReport)
|
||||
classTask.run()
|
||||
if (classReport.isNotEmpty()) {
|
||||
++totalDiffs
|
||||
}
|
||||
} else {
|
||||
println("Skipping $name1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseClassNode(input: InputStream): ClassNode =
|
||||
ClassNode().also { ClassReader(input).accept(it, ClassReader.SKIP_CODE) }
|
||||
|
||||
private fun checkJarFile2() {
|
||||
jarFile2.stream().forEach { entry2 ->
|
||||
val name2 = entry2.name
|
||||
if (name2.endsWith(".class") && name2 !in names1) {
|
||||
val classNode = parseClassNode(jarFile2.getInputStream(entry2))
|
||||
if (!classNode.shouldBeIgnored()) {
|
||||
report.addMissingClassName1("$name2 ${classNode.access.classFlags()}")
|
||||
++totalDiffs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeReportIfRequired() {
|
||||
if (report.isNotEmpty()) {
|
||||
PrintWriter(outputFile).use { out ->
|
||||
out.tag("html") {
|
||||
out.tag("head") {
|
||||
out.printCss()
|
||||
}
|
||||
out.tag("body") {
|
||||
report.writeAsHtml(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun PrintWriter.printCss() {
|
||||
tag("style", REPORT_CSS)
|
||||
}
|
||||
|
||||
private fun ClassNode.shouldBeIgnored(): Boolean {
|
||||
if (ignoreJavaClasses && isJavaClass()) return true
|
||||
if (access.isSynthetic()) return true
|
||||
if (ignoreAnonymousOrLocalClasses && isAnonymousOrLocalClass()) return true
|
||||
return false
|
||||
}
|
||||
|
||||
private fun ClassNode.isAnonymousOrLocalClass() =
|
||||
isSamAdapterName(name) ||
|
||||
innerClasses.listOfNotNull<InnerClassNode>().any {
|
||||
it.name == this.name && it.outerName == null
|
||||
}
|
||||
|
||||
private fun ClassNode.isJavaClass() =
|
||||
sourceFile != null && sourceFile.endsWith(".java")
|
||||
|
||||
private fun addJarsInfo() {
|
||||
report.info {
|
||||
tag("p") {
|
||||
tag("b", header1)
|
||||
println(": ${jarFile1.name}")
|
||||
}
|
||||
tag("p") {
|
||||
tag("b", header2)
|
||||
println(": ${jarFile2.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.methodFlags
|
||||
import org.jetbrains.kotlin.abicmp.reports.MethodReport
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
|
||||
class MethodTask(
|
||||
private val checkerConfiguration: CheckerConfiguration,
|
||||
private val method1: MethodNode,
|
||||
private val method2: MethodNode,
|
||||
private val report: MethodReport,
|
||||
) : Runnable {
|
||||
|
||||
override fun run() {
|
||||
addMethodInfo()
|
||||
|
||||
for (checker in checkerConfiguration.enabledMethodCheckers) {
|
||||
checker.check(method1, method2, report)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMethodInfo() {
|
||||
report.info {
|
||||
tag("p") {
|
||||
tag("b", report.header1)
|
||||
println(": ${method1.access.methodFlags()}")
|
||||
}
|
||||
tag("p") {
|
||||
tag("b", report.header2)
|
||||
println(": ${method2.access.methodFlags()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.MultiFileClassFacadeMetadataReport
|
||||
|
||||
class MultiFileClassFacadeMetadataTask(
|
||||
private val configuration: CheckerConfiguration,
|
||||
private val metadata1: KotlinClassMetadata.MultiFileClassFacade,
|
||||
private val metadata2: KotlinClassMetadata.MultiFileClassFacade,
|
||||
private val report: MultiFileClassFacadeMetadataReport,
|
||||
) : Runnable {
|
||||
override fun run() {
|
||||
for (checker in configuration.enabledMultifileClassFacadeMetadataCheckers) {
|
||||
checker.check(metadata1, metadata2, report)
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.MultiFileClassPartMetadataReport
|
||||
|
||||
class MultiFileClassPartMetadataTask(
|
||||
private val configuration: CheckerConfiguration,
|
||||
private val metadata1: KotlinClassMetadata.MultiFileClassPart,
|
||||
private val metadata2: KotlinClassMetadata.MultiFileClassPart,
|
||||
private val report: MultiFileClassPartMetadataReport,
|
||||
) : Runnable {
|
||||
override fun run() {
|
||||
for (checker in configuration.enabledMultifileClassPartMetadataCheckers) {
|
||||
checker.check(metadata1, metadata2, report)
|
||||
}
|
||||
PackageMetadataTask(configuration, metadata1.kmPackage, metadata2.kmPackage, report.packageReport()).run()
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import kotlinx.metadata.KmPackage
|
||||
import org.jetbrains.kotlin.abicmp.checkers.*
|
||||
import org.jetbrains.kotlin.abicmp.reports.PackageMetadataReport
|
||||
|
||||
class PackageMetadataTask(
|
||||
private val configuration: CheckerConfiguration,
|
||||
private val metadata1: KmPackage,
|
||||
private val metadata2: KmPackage,
|
||||
private val report: PackageMetadataReport,
|
||||
) : Runnable {
|
||||
override fun run() {
|
||||
for (checker in configuration.enabledPackageMetadataCheckers) {
|
||||
checker.check(metadata1, metadata2, report)
|
||||
}
|
||||
|
||||
checkMetadataMembers(metadata1, metadata2, configuration.enabledFunctionMetadataCheckers, report::functionReport, ::loadFunctions)
|
||||
checkMetadataMembers(metadata1, metadata2, configuration.enabledPropertyMetadataCheckers, report::propertyReport, ::loadProperties)
|
||||
checkMetadataMembers(
|
||||
metadata1,
|
||||
metadata2,
|
||||
configuration.enabledTypeAliasMetadataCheckers,
|
||||
report::typeAliasReport,
|
||||
::loadTypeAliases
|
||||
)
|
||||
checkMetadataMembers(
|
||||
metadata1,
|
||||
metadata2,
|
||||
configuration.enabledPropertyMetadataCheckers,
|
||||
report::localDelegatedPropertyReport,
|
||||
::loadLocalDelegatedProperties
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.tasks
|
||||
|
||||
import kotlinx.metadata.jvm.KotlinClassMetadata
|
||||
import org.jetbrains.kotlin.abicmp.reports.SyntheticClassMetadataReport
|
||||
|
||||
class SyntheticMetadataTask(
|
||||
private val configuration: CheckerConfiguration,
|
||||
private val metadata1: KotlinClassMetadata.SyntheticClass,
|
||||
private val metadata2: KotlinClassMetadata.SyntheticClass,
|
||||
private val report: SyntheticClassMetadataReport,
|
||||
) : Runnable {
|
||||
override fun run() {
|
||||
for (checker in configuration.enabledAllSyntheticClassMetadataCheckers) {
|
||||
checker.check(metadata1, metadata2, report)
|
||||
}
|
||||
|
||||
val lambda1 = metadata1.kmLambda
|
||||
val lambda2 = metadata2.kmLambda
|
||||
if (lambda1 != null && lambda2 != null) {
|
||||
GenericMetadataTask(
|
||||
lambda1.function,
|
||||
lambda2.function,
|
||||
report.functionReport(),
|
||||
configuration.enabledFunctionMetadataCheckers
|
||||
).run()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.abicmp
|
||||
|
||||
inline fun <reified T : Any> List<Any?>?.listOfNotNull() = orEmpty().filterIsInstance<T>()
|
||||
|
||||
const val PROPERTY_VAL_STUB = "---"
|
||||
@@ -13,6 +13,10 @@ private object SpecialCharacters {
|
||||
const val TYPE_ALIAS_MARKER = '^'
|
||||
}
|
||||
|
||||
fun printFunction(function: KmFunction, settings: KotlinpSettings): String {
|
||||
return StringBuilder().also { visitFunction(function, settings, it) }.toString()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContextReceivers::class, ExperimentalContracts::class)
|
||||
private fun visitFunction(
|
||||
function: KmFunction,
|
||||
@@ -153,7 +157,7 @@ private fun visitTypeAlias(
|
||||
sb.appendLine()
|
||||
}
|
||||
|
||||
private fun printType(type: KmType): String {
|
||||
fun printType(type: KmType): String {
|
||||
val classifier = when (val cls = type.classifier) {
|
||||
is KmClassifier.Class -> cls.name
|
||||
is KmClassifier.TypeParameter -> "T#${cls.id}"
|
||||
@@ -221,7 +225,7 @@ private fun printType(type: KmType): String {
|
||||
}
|
||||
}
|
||||
|
||||
private fun printTypeParameter(
|
||||
fun printTypeParameter(
|
||||
typeParameter: KmTypeParameter,
|
||||
settings: KotlinpSettings
|
||||
): String = buildString {
|
||||
@@ -241,7 +245,7 @@ private fun printTypeParameter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun printValueParameter(
|
||||
fun printValueParameter(
|
||||
valueParameter: KmValueParameter
|
||||
): String {
|
||||
val type = printType(valueParameter.type)
|
||||
@@ -259,7 +263,7 @@ private fun printValueParameter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderAnnotation(annotation: KmAnnotation): String =
|
||||
fun renderAnnotation(annotation: KmAnnotation): String =
|
||||
annotation.className + if (annotation.arguments.isEmpty()) "" else
|
||||
annotation.arguments.entries.joinToString(prefix = "(", postfix = ")") { (name, argument) ->
|
||||
"$name = ${renderAnnotationArgument(argument)}"
|
||||
@@ -313,7 +317,7 @@ private fun String.sanitize(quote: Char): String =
|
||||
}
|
||||
}
|
||||
|
||||
private fun printVersionRequirement(versionRequirement: KmVersionRequirement): String {
|
||||
fun printVersionRequirement(versionRequirement: KmVersionRequirement): String {
|
||||
val version = with(versionRequirement.version) { "$major.$minor.$patch" }
|
||||
|
||||
return buildString {
|
||||
@@ -363,7 +367,7 @@ private fun <T, R : Comparable<R>> Iterable<T>.sortIfNeededBy(settings: KotlinpS
|
||||
}
|
||||
|
||||
@ExperimentalContracts
|
||||
private fun printContract(kmContract: KmContract): String = buildString {
|
||||
fun printContract(kmContract: KmContract): String = buildString {
|
||||
appendLine("contract {")
|
||||
kmContract.effects.map(::printEffect).forEach { effect ->
|
||||
appendLine(" $effect")
|
||||
@@ -746,7 +750,7 @@ private fun StringBuilder.appendClassModifiers(kmClass: KmClass) {
|
||||
append(CLASS_KIND_MAP[kmClass.kind])
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendConstructorModifiers(kmConstructor: KmConstructor) {
|
||||
fun printConstructorModifiers(kmConstructor: KmConstructor) = buildString {
|
||||
append(VISIBILITY_MAP[kmConstructor.visibility])
|
||||
appendFlags(
|
||||
kmConstructor.isSecondary to "/* secondary */",
|
||||
@@ -754,7 +758,11 @@ private fun StringBuilder.appendConstructorModifiers(kmConstructor: KmConstructo
|
||||
)
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendFunctionModifiers(kmFunction: KmFunction) {
|
||||
private fun StringBuilder.appendConstructorModifiers(kmConstructor: KmConstructor) {
|
||||
append(printConstructorModifiers(kmConstructor))
|
||||
}
|
||||
|
||||
fun printFunctionModifiers(kmFunction: KmFunction) = buildString {
|
||||
append(VISIBILITY_MAP[kmFunction.visibility])
|
||||
append(MODALITY_MAP[kmFunction.modality])
|
||||
append(MEMBER_KIND_MAP[kmFunction.kind])
|
||||
@@ -770,7 +778,11 @@ private fun StringBuilder.appendFunctionModifiers(kmFunction: KmFunction) {
|
||||
)
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendPropertyModifiers(kmProperty: KmProperty) {
|
||||
private fun StringBuilder.appendFunctionModifiers(kmFunction: KmFunction) {
|
||||
append(printFunctionModifiers(kmFunction))
|
||||
}
|
||||
|
||||
fun printPropertyModifiers(kmProperty: KmProperty) = buildString {
|
||||
append(VISIBILITY_MAP[kmProperty.visibility])
|
||||
append(MODALITY_MAP[kmProperty.modality])
|
||||
append(MEMBER_KIND_MAP[kmProperty.kind])
|
||||
@@ -783,7 +795,11 @@ private fun StringBuilder.appendPropertyModifiers(kmProperty: KmProperty) {
|
||||
)
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendPropertyAccessorModifiers(accessorAttributes: KmPropertyAccessorAttributes) {
|
||||
private fun StringBuilder.appendPropertyModifiers(kmProperty: KmProperty) {
|
||||
append(printPropertyModifiers(kmProperty))
|
||||
}
|
||||
|
||||
fun printPropertyAccessorModifiers(accessorAttributes: KmPropertyAccessorAttributes) = buildString {
|
||||
append(VISIBILITY_MAP[accessorAttributes.visibility])
|
||||
append(MODALITY_MAP[accessorAttributes.modality])
|
||||
appendFlags(
|
||||
@@ -793,6 +809,10 @@ private fun StringBuilder.appendPropertyAccessorModifiers(accessorAttributes: Km
|
||||
)
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendPropertyAccessorModifiers(accessorAttributes: KmPropertyAccessorAttributes) {
|
||||
append(printPropertyAccessorModifiers(accessorAttributes))
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendValueParameterModifiers(valueParameter: KmValueParameter) = appendFlags(
|
||||
valueParameter.isCrossinline to "crossinline",
|
||||
valueParameter.isNoinline to "noinline"
|
||||
|
||||
@@ -10,12 +10,14 @@ import org.jetbrains.org.objectweb.asm.*
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
internal fun File.readKotlinClassHeader(): Metadata? {
|
||||
internal fun File.readKotlinClassHeader(): Metadata? = ClassReader(FileInputStream(this)).readKotlinClassHeader()
|
||||
|
||||
fun ClassReader.readKotlinClassHeader(): Metadata? {
|
||||
var header: Metadata? = null
|
||||
|
||||
try {
|
||||
val metadataDesc = Type.getDescriptor(Metadata::class.java)
|
||||
ClassReader(FileInputStream(this)).accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? =
|
||||
if (desc == metadataDesc) readMetadataVisitor { header = it }
|
||||
else null
|
||||
|
||||
Reference in New Issue
Block a user