Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support diffing Jar bytecode versions #168

Open
wants to merge 9 commits into
base: trunk
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.jakewharton.diffuse.io.Input

class Aar private constructor(
override val filename: String?,
val bytecodeVersion: Short?,
val files: ArchiveFiles,
val manifest: AndroidManifest,
val classes: Jar,
Expand All @@ -30,7 +31,7 @@ class Aar private constructor(
val libs = zip.entries
.filter { it.path.matches(libsJarRegex) }
.map { it.asInput().toJar() }
return Aar(name, files, manifest, classes, libs)
return Aar(name, classes.bytecodeVersion, files, manifest, classes, libs)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.jakewharton.diffuse.format

interface BinaryFormat {
sealed interface BinaryFormat {
val filename: String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes

class Class private constructor(
val bytecodeVersion: Short,
val descriptor: TypeDescriptor,
val declaredMembers: List<Member>,
val referencedMembers: List<Member>,
Expand All @@ -26,13 +27,14 @@ class Class private constructor(
@JvmName("parse")
fun Input.toClass(): Class {
val reader = ClassReader(toByteArray())
val bytecodeVersion = reader.readShort(0 + 6)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to implement this for Dex files.

val type = TypeDescriptor("L${reader.className};")

val referencedVisitor = ReferencedMembersVisitor()
val declaredVisitor = DeclaredMembersVisitor(type, referencedVisitor)
reader.accept(declaredVisitor, 0)

return Class(type, declaredVisitor.members.sorted(), referencedVisitor.members.sorted())
return Class(bytecodeVersion, type, declaredVisitor.members.sorted(), referencedVisitor.members.sorted())
}
}
}
Expand Down
15 changes: 13 additions & 2 deletions formats/src/main/kotlin/com/jakewharton/diffuse/format/Jar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.jakewharton.diffuse.io.Input

class Jar private constructor(
override val filename: String?,
val bytecodeVersion: Short?,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I think we're going to drown in nuance here. Jars don't have bytecode versions, classes do. And multiple classes within a jar can have different versions. You can conditionally classload classes based on capabilities and those classes could be compiled for two different versions of Java. There's also the multi-version jar standard which puts classes of different versions into META-INF/versions/N/. The ordering of the classes in the jar (read: zip) could be effectively random so taking the first class may not be indicative of what the majority of classes were compiled with.

I'm happy to discuss strategies for surfacing this information, but as-is I think this is too simple of an implementation. Note that it probably works for 99.99% of jars, but when it fails it fails in a spectacularly wrong way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I count all the byte code versions in a Jar and filter the most one.

val files: ArchiveFiles,
val classes: List<Class>,
override val declaredMembers: List<Member>,
Expand All @@ -20,17 +21,27 @@ class Jar private constructor(
fun Input.toJar(): Jar {
toZip().use { zip ->
val files = zip.toArchiveFiles { it.toJarFileType() }
val bcVersions = mutableMapOf<Short, Long>()

val classes = zip.entries
.asSequence()
.filter { it.path.endsWith(".class") }
.map { it.asInput().toClass() }
.map { entry ->
entry.asInput().toClass().also { cls ->
// Count all the byte code versions in a Jar.
bcVersions.merge(cls.bytecodeVersion, 1L, Long::plus)
}
}
.toList()

val mostBytecodeVersion = bcVersions.maxByOrNull { it.value }?.key

val declaredMembers = classes.flatMap { it.declaredMembers }
val referencedMembers = classes.flatMapTo(LinkedHashSet()) { it.referencedMembers }
// Declared methods are likely to reference other declared members. Ensure all are removed.
referencedMembers -= declaredMembers

return Jar(name, files, classes, declaredMembers.sorted(), referencedMembers.sorted())
return Jar(name, mostBytecodeVersion, files, classes, declaredMembers.sorted(), referencedMembers.sorted())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ internal class AarDiffTextReport(private val aarDiff: AarDiff) : Report {
override fun write(appendable: Appendable) {
appendable.apply {
append("OLD: ")
appendLine(aarDiff.oldAar.filename)
append(aarDiff.oldAar.filename)
append(" (bytecodeVersion: ")
append(aarDiff.oldAar.bytecodeVersion.toString())
appendLine(')')

append("NEW: ")
appendLine(aarDiff.newAar.filename)
append(aarDiff.newAar.filename)
append(" (bytecodeVersion: ")
append(aarDiff.newAar.bytecodeVersion.toString())
appendLine(')')

appendLine()
appendLine(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ internal class JarDiffTextReport(private val jarDiff: JarDiff) : Report {
override fun write(appendable: Appendable) {
appendable.apply {
append("OLD: ")
appendLine(jarDiff.oldJar.filename)
append(jarDiff.oldJar.filename)
append(" (bytecodeVersion: ")
append(jarDiff.oldJar.bytecodeVersion.toString())
appendLine(')')

append("NEW: ")
appendLine(jarDiff.newJar.filename)

append(jarDiff.newJar.filename)
append(" (bytecodeVersion: ")
append(jarDiff.newJar.bytecodeVersion.toString())
appendLine(')')
appendLine()
appendLine(jarDiff.archive.toSummaryTable("JAR", Type.JAR_TYPES))
appendLine()
Expand Down
Loading