Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
**Added**
- Add `--summary-only` flag.
- Support diffing bytecode versions for classes.
- Support diffing Kotlin metadata versions for classes.

**Changed**
- Replace `com.jakewharton.diffuse.io.Size` with `me.saket.bytesize.ByteSize` in the APIs.
Expand Down
1 change: 1 addition & 0 deletions formats/api/formats.api
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ public final class com/jakewharton/diffuse/format/Class {
public final fun getBytecodeVersion ()I
public final fun getDeclaredMembers ()Ljava/util/List;
public final fun getDescriptor-BeHrSHk ()Ljava/lang/String;
public final fun getKotlinMetadataVersion ()[I
public final fun getReferencedMembers ()Ljava/util/List;
public fun hashCode ()I
public static final fun parse (Lcom/jakewharton/diffuse/io/Input;)Lcom/jakewharton/diffuse/format/Class;
Expand Down
35 changes: 34 additions & 1 deletion formats/src/main/kotlin/com/jakewharton/diffuse/format/Class.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jakewharton.diffuse.format

import com.jakewharton.diffuse.io.Input
import java.util.Objects
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.FieldVisitor
Expand All @@ -13,18 +14,26 @@ class Class
private constructor(
val descriptor: TypeDescriptor,
val bytecodeVersion: Int,
val kotlinMetadataVersion: IntArray,
val declaredMembers: List<Member>,
val referencedMembers: List<Member>,
) {
override fun toString() = descriptor.toString()

override fun hashCode() =
Objects.hash(descriptor, bytecodeVersion, declaredMembers, referencedMembers)
Objects.hash(
descriptor,
bytecodeVersion,
kotlinMetadataVersion.contentHashCode(),
declaredMembers,
referencedMembers,
)

override fun equals(other: Any?) =
other is Class &&
descriptor == other.descriptor &&
bytecodeVersion == other.bytecodeVersion &&
kotlinMetadataVersion.contentEquals(other.kotlinMetadataVersion) &&
declaredMembers == other.declaredMembers &&
referencedMembers == other.referencedMembers

Expand All @@ -42,6 +51,7 @@ private constructor(
return Class(
type,
declaredVisitor.version,
declaredVisitor.kotlinMetadataVersion,
declaredVisitor.members.sorted(),
referencedVisitor.members.sorted(),
)
Expand All @@ -52,6 +62,7 @@ private constructor(
private class DeclaredMembersVisitor(val type: TypeDescriptor, val methodVisitor: MethodVisitor) :
ClassVisitor(Opcodes.ASM9) {
var version: Int = 0
var kotlinMetadataVersion = intArrayOf()
val members = mutableListOf<Member>()

override fun visit(
Expand Down Expand Up @@ -87,6 +98,28 @@ private class DeclaredMembersVisitor(val type: TypeDescriptor, val methodVisitor
members += Field(type, name, TypeDescriptor(descriptor))
return null
}

override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
if (descriptor == "Lkotlin/Metadata;") {
return KotlinMetadataAnnotationVisitor { kotlinMetadataVersion = it }
}
return super.visitAnnotation(descriptor, visible)
}
}

/**
* Reads [Metadata.bytecodeVersion] directly via ASM's [AnnotationVisitor] instead of
* `kotlin.metadata.jvm.KotlinClassMetadata` to avoid overheads for parsing [Metadata.data1] and
* [Metadata.data2].
*/
private class KotlinMetadataAnnotationVisitor(
private val onMetadataVersionFound: (IntArray) -> Unit
) : AnnotationVisitor(Opcodes.ASM9) {
override fun visit(name: String?, value: Any?) {
if (name == "mv" && value is IntArray) {
onMetadataVersionFound(value)
}
}
}

private class ReferencedMembersVisitor : MethodVisitor(Opcodes.ASM9) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.jakewharton.diffuse.format

import assertk.all
import assertk.assertThat
import assertk.assertions.containsOnly
import assertk.assertions.hasSize
import assertk.assertions.index
import assertk.assertions.isEqualTo
import com.jakewharton.diffuse.format.Class.Companion.toClass
import java.util.function.Function
Expand All @@ -17,6 +20,11 @@ class ClassTest {

assertThat(clazz.descriptor).isEqualTo(type)
assertThat(clazz.bytecodeVersion).isEqualTo(55) // Reflects the JVM target 11.
assertThat(clazz.kotlinMetadataVersion).all {
hasSize(3) // Like [2,4,0].
index(0).isEqualTo(2)
index(2).isEqualTo(0)
}

val initMethod = Method(type, "<init>", emptyList(), TypeDescriptor("V"))
val stringArrayDescriptor = TypeDescriptor("[Ljava/lang/String;")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ internal class JarsDiff(
componentDiff(oldJars, newJars) { jar ->
jar.classes.map { "${it.descriptor}: ${it.bytecodeVersion}" }
}
val kotlinMetadataVersions =
componentDiff(oldJars, newJars) { jar ->
jar.classes.map { "${it.descriptor}: ${it.kotlinMetadataVersion.joinToString(".")}" }
}
val methods = componentDiff(oldJars, newJars) { it.members.filterIsInstance<Method>() }
val declaredMethods =
componentDiff(oldJars, newJars) { it.declaredMembers.filterIsInstance<Method>() }
Expand All @@ -33,7 +37,8 @@ internal class JarsDiff(
val referencedFields =
componentDiff(oldJars, newJars) { it.referencedMembers.filterIsInstance<Field>() }

val changed = bytecodeVersions.changed || methods.changed || fields.changed
val changed =
bytecodeVersions.changed || kotlinMetadataVersions.changed || methods.changed || fields.changed
}

internal fun JarsDiff.toSummaryTable(name: String) = diffuseTable {
Expand Down Expand Up @@ -76,6 +81,7 @@ internal fun JarsDiff.toDetailReport() = buildString {
// TODO appendComponentDiff("STRINGS", strings)?
appendComponentDiff("CLASSES", classes)
appendComponentDiff("BYTECODE VERSIONS", bytecodeVersions)
appendComponentDiff("KOTLIN METADATA VERSIONS", kotlinMetadataVersions)
appendComponentDiff("METHODS", methods)
appendComponentDiff("FIELDS", fields)
}