jakewharton / diffuse Goto Github PK
View Code? Open in Web Editor NEWDiffuse is a tool for diffing APKs, AABs, AARs, and JARs
License: Apache License 2.0
Diffuse is a tool for diffing APKs, AABs, AARs, and JARs
License: Apache License 2.0
Exception in thread "main" java.lang.NullPointerException
at com.jakewharton.diffuse.MemberKt.withoutSyntheticSuffix(Member.kt:20)
at com.jakewharton.diffuse.MembersCommand.run(diffuse.kt:185)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:5266)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:5274)
at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:281)
at com.jakewharton.diffuse.Diffuse.main(diffuse.kt:23324)
Can't share APK, but that doesn't matter since I assume I'll be the one fixing it.
Right now, diffuse
fails with:
Unable to parse line 11 as member mapping: # {"id":"com.android.tools.r8.synthesized"}
New mapping format reference https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md
The most simple fix:
--- a/formats/src/main/kotlin/com/jakewharton/diffuse/format/ApiMapping.kt
+++ b/formats/src/main/kotlin/com/jakewharton/diffuse/format/ApiMapping.kt
@@ -82,7 +82,7 @@ class ApiMapping private constructor(private val typeMappings: Map<TypeDescripto
var fields: MutableMap<String, String>? = null
var methods: MutableMap<MethodSignature, String>? = null
toUtf8().split('\n').forEachIndexed { index, line ->
- if (line.startsWith('#') || line.isBlank()) {
+ if (line.trimStart().startsWith('#') || line.isBlank()) {
return@forEachIndexed
}
if (line.startsWith(' ')) {
arsc │ 682 B │ 674 B │ -8 B │ 576 B │ 564 B │ -12 B
manifest │ 535 B │ 535 B │ 0 B │ 1.1 KiB │ 1.1 KiB │ 0 B
We should append two spaces to B
so that the numbers line up vertically.
I have this dependency:
testImplementation("com.jakewharton.dex:dex-member-list:4.1.1")
and I'm getting an exception when calling from Kotlin (at 1.4.32)
I'm using this code
fun dexMethod(className: String, methodName: String): Matcher<DexMethod> =
object : TypeSafeMatcher<DexMethod>() {
override fun describeTo(description: Description): Unit = with(description) {
appendText("method ").appendValue(methodName)
appendText(" ")
appendText("in class ").appendValue(className)
}
override fun matchesSafely(item: DexMethod) =
className == item.declaringType.sourceName && methodName == item.name
}
and getting this exception
java.lang.NoSuchMethodError: com.jakewharton.dex.DexMethod.getDeclaringType-YNYzXYw()Ljava/lang/String;
at net.twisterrob.gradle.android.GradleTestHelpersKt$dexMethod$1.matchesSafely(GradleTestHelpers.kt:184)
I tried using "-Xuse-14-inline-classes-mangling-scheme"
, but no luck.
So far I've spent 3 hours of deep hacking around on this, and couldn't find a way to do it. I managed to write a reflective hack in Java after I understood the issue, but it would be easier to upgrade to a newer version of the library, is diffuse available from maven?
@Override
protected boolean matchesSafely(DexMethod item) {
return className.equals(sourceName(item.getDeclaringType())) && methodName.equals(item.getName());
}
String sourceName(String declaringType) {
try {
Method sourceName = TypeDescriptor.class.getDeclaredMethod("getSourceName-impl", String.class);
sourceName.setAccessible(true);
return (String)sourceName.invoke(null, declaringType);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
When building with gradle plugin 2.2.0-alpha2
and android gradle plugin 2.2.0-alpha2
the following exception is thrown
java.lang.NoSuchMethodError: com.android.dx.command.dexer.Main.clearInternTables()V
.
Shading or repackaging the dx dependency should fix this.
Hey, do you mind if I send a simple PR updating README.md to have any usage steps at all? I would just list what worked for me, that should be easier than everyone figuring it out on their own.
The use of appendline
means we aggregate a bunch of trailing newlines where we don't want them. These should be minimized to spacing is hoisted to the responsibility of the person composing things together.
On CI, we diff the method lists of the ancestor commit and HEAD to surface any hidden methods added in the change.
One recent PR gave the following diff:
@@ -32495,2 +32495,2 @@
-com.squareup.cash.ui.MainActivity lambda$checkForUpdate$17$MainActivity(Throwable)
-com.squareup.cash.ui.MainActivity lambda$handleScenarioPlanError$21$MainActivity(CashAlertDialog, View)
+com.squareup.cash.ui.MainActivity lambda$checkForUpdate$19$MainActivity(Throwable)
+com.squareup.cash.ui.MainActivity lambda$handleScenarioPlanError$23$MainActivity(CashAlertDialog, View)
@@ -32500,4 +32500,4 @@
-com.squareup.cash.ui.MainActivity lambda$refreshSessionToken$14$MainActivity(StringPreference, RefreshSessionResponse)
-com.squareup.cash.ui.MainActivity lambda$refreshSessionToken$15$MainActivity(Throwable)
-com.squareup.cash.ui.MainActivity lambda$showUpgradeDialog$18$MainActivity(CashAlertDialog, View)
-com.squareup.cash.ui.MainActivity lambda$startOnboardingFlow$10$MainActivity(Throwable)
+com.squareup.cash.ui.MainActivity lambda$refreshSessionToken$16$MainActivity(StringPreference, RefreshSessionResponse)
+com.squareup.cash.ui.MainActivity lambda$refreshSessionToken$17$MainActivity(Throwable)
+com.squareup.cash.ui.MainActivity lambda$showUpgradeDialog$20$MainActivity(CashAlertDialog, View)
+com.squareup.cash.ui.MainActivity lambda$startOnboardingFlow$12$MainActivity(Throwable)
@@ -32570,2 +32570,7 @@
-com.squareup.cash.ui.MainActivity$$Lambda$27 <init>(MainActivity, Window)
-com.squareup.cash.ui.MainActivity$$Lambda$27 run()
+com.squareup.cash.ui.MainActivity$$Lambda$27 <clinit>()
+com.squareup.cash.ui.MainActivity$$Lambda$27 <init>()
+com.squareup.cash.ui.MainActivity$$Lambda$27 test(Object)
+com.squareup.cash.ui.MainActivity$$Lambda$28 <init>(MainActivity)
+com.squareup.cash.ui.MainActivity$$Lambda$28 apply(Object)
+com.squareup.cash.ui.MainActivity$$Lambda$29 <init>(MainActivity, Window)
+com.squareup.cash.ui.MainActivity$$Lambda$29 run()
@@ -32581,3 +32586,2 @@
-com.squareup.cash.ui.MainActivity$$Lambda$7 <clinit>()
-com.squareup.cash.ui.MainActivity$$Lambda$7 <init>()
-com.squareup.cash.ui.MainActivity$$Lambda$7 test(Object)
+com.squareup.cash.ui.MainActivity$$Lambda$7 <init>(MainActivity)
+com.squareup.cash.ui.MainActivity$$Lambda$7 apply(Object)
@@ -60033,0 +60038,14 @@
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle <init>(MaybeSource, Function)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle subscribeActual(SingleObserver)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapMaybeObserver <init>(SingleObserver, Function)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapMaybeObserver dispose()
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapMaybeObserver get()
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapMaybeObserver isDisposed()
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapMaybeObserver onComplete()
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapMaybeObserver onError(Throwable)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapMaybeObserver onSubscribe(Disposable)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapMaybeObserver onSuccess(Object)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapSingleObserver <init>(AtomicReference, SingleObserver)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapSingleObserver onError(Throwable)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapSingleObserver onSubscribe(Disposable)
+io.reactivex.internal.operators.maybe.MaybeFlatMapSingle$FlatMapSingleObserver onSuccess(Object)
Note that in the first two segments, the lambda accessor methods are shifted down by two in the enclosing class -- a no-op in our case.
A feature request proposal would strip the numbering when the --hide-synthetic-numbers
flag is enabled, allowing the diff to be more indicative of the change to the method list.
Exception in thread "main" java.util.zip.ZipException: invalid entry compressed size (expected 1276 but got 1272 bytes)
at java.util.zip.ZipInputStream.readEnd(ZipInputStream.java:389)
at java.util.zip.ZipInputStream.read(ZipInputStream.java:196)
at java.util.zip.ZipInputStream.closeEntry(ZipInputStream.java:140)
at java.util.zip.ZipInputStream.getNextEntry(ZipInputStream.java:118)
at com.jakewharton.diffuse.ExtensionsKt$entries$1$iterator$1.hasNext(extensions.kt:73)
at kotlin.sequences.TransformingSequence$iterator$1.hasNext(Sequences.kt:176)
at kotlin.sequences.SequencesKt___SequencesKt.toCollection(_Sequences.kt:722)
at kotlin.sequences.SequencesKt___SequencesKt.toMutableList(_Sequences.kt:752)
at kotlin.sequences.SequencesKt___SequencesKt.toList(_Sequences.kt:743)
at com.jakewharton.diffuse.io.ZipKt.mapEntries(Zip.kt:70)
at com.jakewharton.diffuse.io.ZipKt.toZip(Zip.kt:96)
at com.jakewharton.diffuse.io.PathInput.toZip(Input.kt:43)
at com.jakewharton.diffuse.Apk$Companion.parse(Apk.kt:28)
at com.jakewharton.diffuse.DiffCommand$inputOptions$2.parse(diffuse.kt:76)
at com.jakewharton.diffuse.DiffCommand.run(diffuse.kt:135)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:139)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:14)
at com.github.ajalt.clikt.core.CliktCommand.parse(CliktCommand.kt:215)
at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:212)
at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:230)
at com.jakewharton.diffuse.Diffuse.main(diffuse.kt:41)
Detect dex files using the header magic, otherwise assume apk. Read all dex files from the apk.
OOM occurred when diff two apks by using the following command:
$ diffuse diff a.apk b.apk
The stack trace shows as following
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at com.android.dex.Mutf8.decode(Mutf8.java:39)
at com.android.dex.Dex$Section.readString(Dex.java:408)
at com.android.dex.Dex$StringTable.get(Dex.java:718)
at com.android.dex.Dex$TypeIndexToDescriptorTable.get(Dex.java:742)
at com.android.dex.Dex$TypeIndexToDescriptorTable.get(Dex.java:738)
at com.jakewharton.diffuse.DexKt.getField(Dex.kt:62)
at com.jakewharton.diffuse.DexKt.access$getField(Dex.kt:1)
at com.jakewharton.diffuse.Dex$Companion$toDex$4.invoke(Dex.kt:43)
at com.jakewharton.diffuse.Dex$Companion$toDex$4.invoke(Dex.kt:28)
at com.jakewharton.diffuse.ExtensionsKt.mapEach(extensions.kt:39)
at com.jakewharton.diffuse.Dex$Companion.parse(Dex.kt:43)
at com.jakewharton.diffuse.Apk$Companion.parse(Apk.kt:34)
at com.jakewharton.diffuse.DiffCommand$inputOptions$2.parse(diffuse.kt:76)
at com.jakewharton.diffuse.DiffCommand.run(diffuse.kt:135)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:139)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:14)
at com.github.ajalt.clikt.core.CliktCommand.parse(CliktCommand.kt:215)
at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:212)
at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:230)
at com.jakewharton.diffuse.Diffuse.main(diffuse.kt:41)
So it can be curl/wget downloaded and used without compilation.
In AGP 7, if you execute (e.g.)
./gradlew lib:assembleDebugAndroidTest
on an Android library project that doesn't have any resources (no androidTest/res
directory), and then attempt to compare that APK with one built with AGP 4, you'll get an error like this:
$ diffuse diff --apk test4.apk test7.apk
Exception in thread "main" java.lang.IllegalStateException: ResourceTableChunk package count was < 1.
at com.android.apksig.ApkSigner.checkState(ApkSigner.java:1)
at com.google.devrel.gmscore.tools.apk.arsc.ResourceTableChunk.<init>(ResourceTableChunk.java:7)
at com.google.devrel.gmscore.tools.apk.arsc.Chunk.newInstance(Chunk.java:57)
at com.jakewharton.diffuse.Arsc$Companion.create(Arsc.kt:42)
at com.jakewharton.diffuse.Apk$Companion.parse(Apk.kt:84)
at com.jakewharton.diffuse.DiffCommand.run(diffuse.kt:117)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:5266)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:5274)
at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:281)
at com.jakewharton.diffuse.Diffuse.main(diffuse.kt:23324)
Note that this issue arises regardless of whether the library project uses
android.buildFeatures.androidResources = false
That value appears to be ignored, at least in AGP 7.0.3. The mere presence of a resource in androidrTest/res
can workaround this issue.
apktool
has a similar issue when executing apktool d path/to/apk -o foo
, but there's a CLI option, -r
, that solves the problem: apktool -r d path/to/apk -o foo
.
Otherwise things like covariant return types and generics look like duplicates when they're really not.
so far diffuse only has diff command which requires two binaries, in some usecase, we might want to just see whats the number in a binary, save it to database, and analyze the differences through some graph/trend.
is it possible to just seeing an APK's stats instead of diffing?
I have a need where another tool (written in a JVM language) could use rendering of methods in a dex or apk to validate its behavior. A simple API could be teased out.
com.github.ajalt.clikt:clikt:3.0.0-rc
Have you considered having a lightweight version of input for this tool?
I'm thinking of something like all the important stats exported to a text file. Maintaining a bunch of apk snapshots (for comparisons) seems not to make any sense, when I could be maintaining a number of text files for a fraction of the cost.
If the lightweight input was available I could be keeping the text file for every single commit on master
(or at least X last of them) and using it as the base for comparisons on my Pull Requests.
This creates a lot of noise when doing diffs of the output and a new field or lambda changes every subsequent method.
Merge into a single sorted list.
In this scenario:
./gradlew assemble
../gradlew assemble
I don't see the change from step 2. being reflected after step 3.
Am I missing something or should the sources be set as input in fatTranslationsJarProvider
task in build.gradle
?
task.inputs.files(sourceSets.main.output.classesDirs)
But all tasteful like, so as to not distract.
Been using https://github.com/ajalt/mordant in another project and it's good.
I'd like to use dex-method-list
to find all the test methods in my apk. Our tests, unfortunately, don't follow a convention that I can use to parse them out, instead we rely on @Test
annotations.
It would be very nice if the annotations of the method could be available, perhaps behind an option.
I tried to implement this myself, but I got stuck with the DEX file format and did not get the correct annotations out. Here is my WIP
If you can give me a pointer in the right direction, I would very much appreciate it.
Tried to run the diffuse but it returns the above error.
the command used
java -jar diffuse.jar diff "apk1" "apk2"
the apk
in question can be found here
the error trace here:
at com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl.checkNamespaceWF(CoreDocumentImpl.java:2535) at com.sun.org.apache.xerces.internal.dom.AttrNSImpl.setName(AttrNSImpl.java:93) at com.sun.org.apache.xerces.internal.dom.AttrNSImpl.<init>(AttrNSImpl.java:78) at com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl.createAttributeNS(CoreDocumentImpl.java:2164) at com.sun.org.apache.xerces.internal.dom.ElementImpl.setAttributeNS(ElementImpl.java:659) at com.jakewharton.diffuse.Manifest$Companion.toManifest$diffuse(Manifest.kt:974) at com.jakewharton.diffuse.Apk$Companion.parse(Apk.kt:124) at com.jakewharton.diffuse.DiffCommand.run(diffuse.kt:117) at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:5266) at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:5274) at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:281) at com.jakewharton.diffuse.Diffuse.main(diffuse.kt:23324)
although I am not sure it is about the xml
naming issue? How can I solve this?
This is output for diffuse when comparing APK before and after adding a big library with native libs and whatnot (unique numbers are way off because of R8):
│ compressed │ uncompressed
├───────────┬───────────┬────────────┼───────────┬───────────┬───────────
APK │ old │ new │ diff │ old │ new │ diff
──────────┼───────────┼───────────┼────────────┼───────────┼───────────┼───────────
dex │ 2.6 MiB │ 196 B │ -2.6 MiB │ 6.6 MiB │ -2 B │ -6.6 MiB
arsc │ 440.6 KiB │ 439.6 KiB │ -970 B │ 440.5 KiB │ 439.5 KiB │ -960 B
manifest │ 3 KiB │ 113 B │ -2.9 KiB │ 12.2 KiB │ -1 B │ -12.3 KiB
res │ 1.5 MiB │ 1.1 MiB │ -496.3 KiB │ 2.1 MiB │ 906.7 KiB │ -1.3 MiB
native │ 43.8 KiB │ 4.6 MiB │ +4.6 MiB │ 43 KiB │ 4.6 MiB │ +4.5 MiB
asset │ 49 KiB │ 421 B │ -48.6 KiB │ 124.9 KiB │ -3 B │ -125 KiB
other │ 170.8 KiB │ 112.4 KiB │ -58.4 KiB │ 361.6 KiB │ 275.6 KiB │ -86.1 KiB
──────────┼───────────┼───────────┼────────────┼───────────┼───────────┼───────────
total │ 4.8 MiB │ 6.2 MiB │ +1.4 MiB │ 9.7 MiB │ 6.1 MiB │ -3.6 MiB
│ raw │ unique
├───────┬───────┬───────┼───────┬───────┬───────────────────────
DEX │ old │ new │ diff │ old │ new │ diff
─────────┼───────┼───────┼───────┼───────┼───────┼───────────────────────
count │ 1 │ 2 │ +1 │ │ │
strings │ 35815 │ 38915 │ +3100 │ 35815 │ 37708 │ +1893 (+5632 -3739)
types │ 11415 │ 12329 │ +914 │ 11415 │ 12031 │ +616 (+4007 -3391)
classes │ 10163 │ 10693 │ +530 │ 10163 │ 10693 │ +530 (+3701 -3171)
methods │ 42939 │ 46614 │ +3675 │ 42939 │ 45943 │ +3004 (+30436 -27432)
fields │ 57046 │ 57083 │ +37 │ 57046 │ 57018 │ -28 (+48758 -48786)
Note DEX#count
row, which shows increased DEX count, yet APK#dex
row shows dex size has significantly decreased. Also total compressed file size is much lower than in reality. Here's what AS shows when inspecting new apk file:
For comparison, old apk seems to be aligned with diffuse output:
We are migrating build-systems and plan to use Diffuse for comparison between two APKs built by different build systems (Buck and Bazel). As part of this, some formatting in the output to account for significant content helps with parsing since the list of files that are different would be enormous.
Is this a use-case you'd be interested in us upstreaming?
Needs
plugins.withId('com.vanniktech.maven.publish') {
signing {
def signingKey = findProperty('signingKey')
def signingPassword = ''
useInMemoryPgpKeys(signingKey, signingPassword)
}
}
And a GPG key without a password added to secrets.
The CLI support is there, but it just TODO()
s in code.
There is strong use case of Diffuse being run on CI Systems.
CI Systems can then send JSON reports over to NOSQL data stores for data analysis.
After migrating the project to AGP 7.1.1 diffuse starts failing with
Usage: diffuse diff [OPTIONS] OLD NEW
Error: Invalid value for "--new-mapping": Unable to parse line [76] as member mapping: # {"id":"com.android.tools.r8.synthesized"}
I call it via:
java -jar diffuse.jar diff --apk diffuse-source-file-release app/build/outputs/apk/graphql/release/app-graphql-release-unsigned.apk --old-mapping diffuse-mapping-file-release --new-mapping app/build/outputs/mapping/graphqlRelease/mapping.txt
here's how line 76 looks like:
The workaround seems to be not to pass the mapping related arguments as they are ignored either way (#96)
Hey,
this is not a feature request for this repo,
but I was curious if the info in the dex code would unveil the original classes
in which the method calls were issued.
I would like to extend the tool to track if some classes reference certain methods
(to check if third-party libraries in apks reference system api calls).
Since you have more experiance with the dex bytecode format,
i wanted to ask, if this would be possible.
I am finding it hard to set up and use the diffuse tool. After cloning this repo what next? What if I want to call the tool with GitHub actions?
In the readme it says the binary is in diffuse/build/diffuse
but building on trunk
puts the binary in diffuse/build/libs/
Was trying to find a diff of one of the privately published library in my team. I'm getting the following stacktrace.
> ./diffuse diff --aar catlib-2.2.1.aar catlib-2.2.4.aar
Exception in thread "main" java.nio.file.ProviderNotFoundException: Provider not found
at java.nio.file.FileSystems.newFileSystem(FileSystems.java:407)
at com.jakewharton.diffuse.ExtensionsKt.asZipFileSystem(extensions.kt:28)
at com.jakewharton.diffuse.ExtensionsKt.asZipFileSystem$default(extensions.kt:28)
at com.jakewharton.diffuse.io.ZipKt.toZip(Zip.kt:93)
at com.jakewharton.diffuse.io.PathInput.toZip(Input.kt:43)
at com.jakewharton.diffuse.Jar$Companion.parse(Jar.kt:21)
at com.jakewharton.diffuse.Aar$Companion.parse(Aar.kt:29)
at com.jakewharton.diffuse.DiffCommand$inputOptions$2.parse(diffuse.kt:77)
at com.jakewharton.diffuse.DiffCommand.run(diffuse.kt:133)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:139)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:14)
at com.github.ajalt.clikt.core.CliktCommand.parse(CliktCommand.kt:215)
at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:212)
at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:230)
at com.jakewharton.diffuse.Diffuse.main(diffuse.kt:40)
Once it's released...
There already exists and has existed for some time a diff tool called diffuse.
http://diffuse.sourceforge.net/
It is a graphical tool for doing multiple-file visual diffs of text.
https://www.google.com/search?q=diffuse+software
People looking for that tool can accidentaly end up finding this tool especially since both are described as diffing tools even though they work with completely different types of files.
Diff with two AABs does not work when AABs have dynamic modules
Additional info:
If I perform diffuse diff someapk1.apk someapk2.apk I get a full output of comparisons between the various parts of the apks.
If I use the same for bundles I don't get a full comparative output, instead it only shows something like this:
MODULES │ old │ new
─────────┼─────┼─────
base │ ✓ │ ✓
some other module │ ✓ │ ✓
I'm using diffuse diff --aab somebundle1.aab somebundle2.aab
Add a legacy --dx
flag.
When running:
diffuse diff old_obfuscated.apk new_obfuscated.apk
the diff shows output with invalid +-
stats
DEX │ old │ new │ diff
─────────┼───────┼───────┼────────────────────
files │ 1 │ 1 │ 0
strings │ 15365 │ 15368 │ +3 (+2449 -2446)
types │ 3315 │ 3317 │ +2 (+2250 -2248)
classes │ 2564 │ 2566 │ +2 (+2182 -2180)
methods │ 16360 │ 16364 │ +4 (+9971 -9967)
fields │ 15108 │ 15108 │ 0 (+13635 -13635)
and prints all strings, types, classes, methods and fields within the app twice (once visible as added and once as removed), resulting in large, unreadable text outputs.
collapsed | expanded |
---|---|
![]() |
![]() |
running diffuse diff --help
reveals additonal parameters --old-mapping
and --new-mapping
, but they don't change the output.
Looking at the source code, the DexDiff doesn't take mapping parameters into account. And actually none of existing Diff types leverage the ApiMapping
class.
Expected behavior:
diffuse diff old_obfuscated.apk new_obfuscated.apk --old-mapping old_mapping.txt --new-mapping new_mapping.txt
shows actuall diff
Getting this exception when working with an APK.
Exception in thread "main" java.lang.IllegalStateException: Check failed.
at com.jakewharton.diffuse.Manifest$Companion.toDocument(Manifest.kt:81)
at com.jakewharton.diffuse.Manifest$Companion.toManifest$diffuse(Manifest.kt:48)
at com.jakewharton.diffuse.Apk$Companion.parse(Apk.kt:31)
at com.jakewharton.diffuse.DiffCommand$inputOptions$2.parse(diffuse.kt:76)
at com.jakewharton.diffuse.DiffCommand.run(diffuse.kt:135)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:139)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:14)
at com.github.ajalt.clikt.core.CliktCommand.parse(CliktCommand.kt:215)
at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:212)
at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:230)
at com.jakewharton.diffuse.Diffuse.main(diffuse.kt:41)
If i disable the check in com.jakewharton.diffuse.Manifest
then it proceeds and completes execution.
check(namespacesInScope.put(chunk.uri, "${chunk.prefix}:") == null)
Looking at the APK extracted with apktool, it seems like we have several resource XMLs with the URIs in question. So, perhaps those are causing this issue...
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.