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

doesn't build reproducibly (due to bug in coreLibraryDesugaring) #6486

Closed
4 tasks done
obfusk opened this issue Jun 14, 2021 · 32 comments · Fixed by #9709
Closed
4 tasks done

doesn't build reproducibly (due to bug in coreLibraryDesugaring) #6486

obfusk opened this issue Jun 14, 2021 · 32 comments · Fixed by #9709
Labels
bug Issue is related to a bug meta Related to the project but not strictly to code

Comments

@obfusk
Copy link

obfusk commented Jun 14, 2021

I compared the APKs from the last 5 releases on GitHub to the APKs built and published by F-Droid.
Some of them are identical (apart from the signature), but not all of them are.

I've done multiple clean builds of 0.21.4; they were not always identical.
I've seen 3 variants, one identical to the GitHub APK, another identical to the F-Droid APK.
The differences I've seen were always in j$/util/Collection$-EL.class.

diffoscope output

--- NewPipe_v0.21.4.apk
+++ org.schabi.newpipe_970.apk
├── zipinfo {}
│ @@ -1430,11 +1430,11 @@
│  -rw----     0.0 fat      186 b- stor 81-Jan-01 01:01 res/drawable-hdpi-v4/abc_textfield_activated_mtrl_alpha.9.png
│  -rw----     0.0 fat     1824 b- defN 81-Jan-01 01:01 res/layout/notification_template_big_media_narrow.xml
│  -rw----     0.0 fat      106 b- stor 81-Jan-01 01:01 res/drawable-xxxhdpi-v4/exo_ic_fullscreen_exit.png
│  -rw----     0.0 fat      360 b- defN 81-Jan-01 01:01 res/xml/standalone_badge_offset.xml
│  -rw----     0.0 fat      516 b- defN 81-Jan-01 01:01 res/drawable/abc_seekbar_tick_mark_material.xml
│  -rw----     0.0 fat     4384 b- defN 81-Jan-01 01:01 res/xml/video_audio_settings.xml
│  -rw----     0.0 fat      212 b- stor 81-Jan-01 01:01 res/drawable-hdpi-v4/notification_bg_normal.9.png
│ --rw----     2.0 fat   166112 b- defN 81-Jan-01 01:01 META-INF/SCHABI_K.SF
│ --rw----     2.0 fat     1187 b- defN 81-Jan-01 01:01 META-INF/SCHABI_K.RSA
│ +-rw----     2.0 fat   166112 b- defN 81-Jan-01 01:01 META-INF/EE8807D2.SF
│ +-rw----     2.0 fat     1334 b- defN 81-Jan-01 01:01 META-INF/EE8807D2.RSA
│  -rw----     2.0 fat   165985 b- defN 81-Jan-01 01:01 META-INF/MANIFEST.MF
│ -1438 files, 14878729 bytes uncompressed, 8633241 bytes compressed:  42.0%
│ +1438 files, 14878876 bytes uncompressed, 8633255 bytes compressed:  42.0%
├── classes2.dex
│ ├── classes2.jar
│ │ ├── zipinfo -v {}
│ │ │ @@ -3024,15 +3024,15 @@
│ │ │    version of encoding software:                   2.0
│ │ │    minimum file system compatibility required:     MS-DOS, OS/2 or NT FAT
│ │ │    minimum software version required to extract:   2.0
│ │ │    compression method:                             none (stored)
│ │ │    file security status:                           not encrypted
│ │ │    extended local header:                          no
│ │ │    file last modified on (DOS date/time):          1980 Jan 1 00:00:00
│ │ │ -  32-bit CRC value (hex):                         6608aac0
│ │ │ +  32-bit CRC value (hex):                         2ce3d6ec
│ │ │    compressed size:                                1704 bytes
│ │ │    uncompressed size:                              1704 bytes
│ │ │    length of filename:                             28 characters
│ │ │    length of extra field:                          0 bytes
│ │ │    length of file comment:                         0 characters
│ │ │    disk number on which file begins:               disk 1
│ │ │    apparent file type:                             binary
│ │ ├── j$/util/Collection$-EL.class
│ │ │ ├── procyon -ec {}
│ │ │ │ @@ -1,14 +1,14 @@
│ │ │ │  
│ │ │ │  package j$.util;
│ │ │ │  
│ │ │ │  import j$.util.stream.Stream;
│ │ │ │  import j$.util.function.Predicate;
│ │ │ │ -import java.util.List;
│ │ │ │  import java.util.Set;
│ │ │ │ +import java.util.List;
│ │ │ │  import java.util.SortedSet;
│ │ │ │  import java.util.LinkedHashSet;
│ │ │ │  import java.util.Iterator;
│ │ │ │  import java.util.Objects;
│ │ │ │  import j$.util.function.Consumer;
│ │ │ │  import java.util.Collection;
│ │ │ │  
│ │ │ │ @@ -33,16 +33,16 @@
│ │ │ │          if (collection instanceof LinkedHashSet) {
│ │ │ │              return w.m((Collection)(LinkedHashSet)collection, 17);
│ │ │ │          }
│ │ │ │          if (collection instanceof SortedSet) {
│ │ │ │              final SortedSet set = (SortedSet)collection;
│ │ │ │              return (Spliterator)new v(set, (Collection)set, 21);
│ │ │ │          }
│ │ │ │ -        if (collection instanceof Set) {
│ │ │ │ -            return Set$-CC.$default$spliterator((Set)(SortedSet)collection);
│ │ │ │ -        }
│ │ │ │          if (collection instanceof List) {
│ │ │ │              return List$-CC.$default$spliterator((List)collection);
│ │ │ │          }
│ │ │ │ +        if (collection instanceof Set) {
│ │ │ │ +            return Set$-CC.$default$spliterator((Set)(SortedSet)collection);
│ │ │ │ +        }
│ │ │ │          return Collection$-CC.$default$spliterator(collection);
│ │ │ │      }
│ │ │ │  }
├── original/META-INF/MANIFEST.MF
│ @@ -217,15 +217,15 @@
│  Name: assets/mpl2.html
│  SHA-256-Digest: iPab3imJW9Oh7oo7DAHIVYhGvM1I/iIteLPlxsLK7IA=
│  
│  Name: classes.dex
│  SHA-256-Digest: 5WQQ5NRR4pSehErw5UNwC37Z6yaIwm6dUtmSzJ0ttZM=
│  
│  Name: classes2.dex
│ -SHA-256-Digest: vSB9FF9aTkw7iyZ/4V0EqWMiG0ffS4Od2x6OeXHgzcE=
│ +SHA-256-Digest: RndUCOiBahWYKoC8EdGgkUFPUR+sIfGyPBni0e6NSYI=
│  
│  Name: icepick/Bundler.java
│  SHA-256-Digest: /CpGZxU8bJLUhTWFDvrD6Ubdl7FQAcjXQ4HwVQRH45k=
│  
│  Name: icepick/Icepick.java
│  SHA-256-Digest: YxL+Da05WHp50ET+GaCfRsvo24Vb82O8yBWbbbTWtN8=
├── smali_classes2/j$/util/Collection$-EL.smali
│┄ Ordering differences only
│ @@ -91,34 +91,34 @@
│  
│      invoke-direct {v0, p0, p0, v1}, Lj$/util/v;-><init>(Ljava/util/SortedSet;Ljava/util/Collection;I)V
│  
│      return-object v0
│  
│      .line 4
│      :cond_2
│ -    instance-of v0, p0, Ljava/util/Set;
│ +    instance-of v0, p0, Ljava/util/List;
│  
│      if-eqz v0, :cond_3
│  
│ -    check-cast p0, Ljava/util/Set;
│ +    check-cast p0, Ljava/util/List;
│  
│ -    invoke-static {p0}, Lj$/util/Set$-CC;->$default$spliterator(Ljava/util/Set;)Lj$/util/Spliterator;
│ +    invoke-static {p0}, Lj$/util/List$-CC;->$default$spliterator(Ljava/util/List;)Lj$/util/Spliterator;
│  
│      move-result-object p0
│  
│      return-object p0
│  
│      :cond_3
│ -    instance-of v0, p0, Ljava/util/List;
│ +    instance-of v0, p0, Ljava/util/Set;
│  
│      if-eqz v0, :cond_4
│  
│ -    check-cast p0, Ljava/util/List;
│ +    check-cast p0, Ljava/util/Set;
│  
│ -    invoke-static {p0}, Lj$/util/List$-CC;->$default$spliterator(Ljava/util/List;)Lj$/util/Spliterator;
│ +    invoke-static {p0}, Lj$/util/Set$-CC;->$default$spliterator(Ljava/util/Set;)Lj$/util/Spliterator;
│  
│      move-result-object p0
│  
│      return-object p0
│  
│      :cond_4
│      invoke-static {p0}, Lj$/util/Collection$-CC;->$default$spliterator(Ljava/util/Collection;)Lj$/util/Spliterator;
│   --- original/META-INF/SCHABI_K.SF
├── +++ original/META-INF/EE8807D2.SF
│┄ Files 0% similar despite different names
│ @@ -1,10 +1,10 @@
│  Signature-Version: 1.0
│  Created-By: 1.0 (Android)
│ -SHA-256-Digest-Manifest: v84EC0vUrqI92WWNlzvCDyzKBpRViJBa+1ANFUj3N1A=
│ +SHA-256-Digest-Manifest: jbpSSD1njen1Cd7aLsGllyyJnQm5fUXo/AXzUAVgKvo=
│  X-Android-APK-Signed: 2, 3
│  
│  Name: AndroidManifest.xml
│  SHA-256-Digest: YtCQDh3t22jNAHVEF2NdaHZaQQXjmFXz3vrlsVwz5zY=
│  
│  Name: META-INF/CHANGES
│  SHA-256-Digest: LZASpfoSLdLbuVdC6w+hzvq04iMCTYYZavDj5G/cQw0=
│ @@ -220,15 +220,15 @@
│  Name: assets/mpl2.html
│  SHA-256-Digest: XL4KAvbh+E+7aLFiSCQfL8ePvML7GrrJdeHTneISqQ0=
│  
│  Name: classes.dex
│  SHA-256-Digest: 9D36aPSLr9vuEqWg/izmbo+vuX3tpkzOX06IbrxuiX0=
│  
│  Name: classes2.dex
│ -SHA-256-Digest: SR2GclUxR2ti2Fm1HWB+hl17NxodXhyO9uraAab4nUE=
│ +SHA-256-Digest: NHRGCjGDHWl55ZrMs6hopXK4YNYq7lyPaEeVjylO8AA=
│  
│  Name: icepick/Bundler.java
│  SHA-256-Digest: 9DRM1Vta36bnfE+4a7Ux385cxv/nW8Lw1KrGoi9bf4I=
│  
│  Name: icepick/Icepick.java
│  SHA-256-Digest: gskitOskS7dphCVTFTv+FRC2+3Dknet5ruDcAsxKQBk=

cc @eighthave


  • I am using the latest version - 0.21.4
  • I checked, but didn't find any duplicates (open OR closed) of this issue in the repo.
  • I have read the contribution guidelines.
  • This issue contains only one bug. I will open one issue for every bug report I want to file.
@obfusk obfusk added the bug Issue is related to a bug label Jun 14, 2021
@mhmdanas mhmdanas added the meta Related to the project but not strictly to code label Jun 15, 2021
@obfusk
Copy link
Author

obfusk commented Jun 15, 2021

shasums for the 3 variants:

40a4ef4e9ac2346bd7b9c705efee3df7935ce7a726cd89bf292f9e9dce7f1b9a7f46935936dc5f9bf394fd383617beaf193b3417afe6593bff22c948fa3660f9  v0.21.4-unsigned-java8-1-matches-upstream.apk
8ef1b0d2ed3b8c5b9780e6d2463d940a3ea0eed64c9824eacd22f21778adcedf42cc3bed33590273fcd1190fec4db7574f4825f78cdc5fede8099cbd7dfd91b8  v0.21.4-unsigned-java8-2.apk
3c7efd9fab7660f2b8e0e434515493b65b55adc41440f64e8818f4d7f4dc09f7d8a7be33d7bfe3c5101db29602bf748e6d95164c8620f0212d4c95a95ecc4075  v0.21.4-unsigned-java8-3-matches-fdroid.apk

@obfusk
Copy link
Author

obfusk commented Jun 15, 2021

So far it looks like disabling coreLibraryDesugaring and building w/ openjdk 11 fixes this.

@TobiGr
Copy link
Member

TobiGr commented Jun 16, 2021

With coreLibraryDesugaring false the app cannot be started on devices running KitKat, because some classes are not found. I don't know if there is a way to get NewPipe running on KitKat without using core library desugaring

@eighthave
Copy link
Contributor

eighthave commented Jun 16, 2021

Desugaring isn't optional if you're using Java8+ and supporting old Android releases. Sounds like this is a bug worth reporting to Android/Google. Someone else recently reported a bug in an androidx lib, and it seems to have gotten some attention from Google.

@eighthave
Copy link
Contributor

eighthave commented Jun 23, 2021 via email

@obfusk obfusk changed the title doesn't build reproducibly doesn't build reproducibly (due to bug in coreLibraryDesugaring) Jun 25, 2021
@Bubu
Copy link

Bubu commented Aug 10, 2021

I couldn't find an issue with google about desugaring related reproducible builds issues so we opened: https://issuetracker.google.com/issues/195968520

@ale5000-git
Copy link

Doesn't downgrade the bugged libraries work as workaround?

@TobiGr
Copy link
Member

TobiGr commented Aug 14, 2021

the core library desugaring dependency broke reproducible builds right when they were introduced in NewPipe. I tried almost every version.

@opusforlife2
Copy link
Collaborator

https://issuetracker.google.com/issues/195968520

Is there a way to view this without using a Google account? Forcing a person to sign-in to just view an issue is absolutely moronic.

@obfusk
Copy link
Author

obfusk commented Aug 14, 2021

Is there a way to view this without using a Google account? Forcing a person to sign-in to just view an issue is absolutely moronic.

Agreed. fwiw: so far, all I can see is that it's been "assigned to an...@google.com".

@obfusk
Copy link
Author

obfusk commented Aug 18, 2021

There has been some activity:

The example shown above matches a non deterministic issue we had in L8 for classes ending with $-EL. This issue should be fixed on tip of tree. If you can reproduce on ToT let us know, I'll work on another fix. If the changes include something else than ordering inside the $-EL methods, let us know too, that would need to be fixed.

@obfusk
Copy link
Author

obfusk commented Aug 18, 2021

There has been some activity:

The example shown above matches a non deterministic issue we had in L8 for classes ending with $-EL. This issue should be fixed on tip of tree. If you can reproduce on ToT let us know, I'll work on another fix. If the changes include something else than ordering inside the $-EL methods, let us know too, that would need to be fixed.

Does anyone know how to use this "tip of tree"? I'd be happy to test.

@Bubu does that cover the issues you saw or did you see other differences as well?

@Bubu
Copy link

Bubu commented Aug 18, 2021

I don't really know. It's really hard to tell. The diffoscope diff output is 25mb. :-/

Unfortunately I don't currently know how to test latest git version of the desugarer.

Meanwhile we recursively forked all our required libs, replaced the Java time api accesses with threetenbp and got rid of this stupid desugarer. The project is reproducible again now.

@obfusk
Copy link
Author

obfusk commented Aug 18, 2021

I don't really know. It's really hard to tell. The diffoscope diff output is 25mb. :-/

The uncompressed text diff is 204Mb :p

That seems to be mostly these kind of differences though, which is different -- but could be related, not sure -- from the bug I reported here:

├── smali_classes4/de/rki/coronawarnapp/util/encoding/Base45Decoder.smali
│┄ Ordering differences only
│ @@ -50,17 +50,17 @@
│          "(Ljava/math/BigInteger;I)J",
│          "",
│          "encode",
│          "([B)Ljava/lang/String;",
│          "decode",
│          "(Ljava/lang/String;)[B",
│          "kotlin.jvm.PlatformType",
│ -        "int45",
│ -        "Ljava/math/BigInteger;",
│          "int256",
│ +        "Ljava/math/BigInteger;",
│ +        "int45",
│          "alphabet",
│          "Ljava/lang/String;",
│          "<init>",
│          "()V",
│          "Corona-Warn-App_deviceRelease"
│      }
│      k = 0x1

@obfusk
Copy link
Author

obfusk commented Aug 19, 2021

So... the problem seems to be in (older versions of) R8.

I've built the latest version 9 times with this patch and all builds were identical.
I can't prove that this fixes the issue, but it seems likely.

diff --git a/build.gradle b/build.gradle
index 315625a0f..9548d1080 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,6 +7,7 @@ buildscript {
         google()
     }
     dependencies {
+        classpath 'com.android.tools:r8:2.2.64' // must be before com.android.tools.build:gradle
         classpath 'com.android.tools.build:gradle:4.1.3'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

@Bubu
Copy link

Bubu commented Aug 19, 2021

I now also tried this, it doesn't change anything for CCTG. (i.e. there's still megabytes of diff output)

@obfusk
Copy link
Author

obfusk commented Aug 19, 2021

I now also tried this, it doesn't change anything for CCTG. (i.e. there's still megabytes of diff output)

That seems to be a different bug.

@obfusk
Copy link
Author

obfusk commented Aug 19, 2021

@Bubu what happens when you set minSdkVersion to 24?

@Bubu
Copy link

Bubu commented Aug 19, 2021

I'll test that later (or tomorrow, currently in the middle of 3 other projects) but this isn't really a fix for us as we do want to keep our minsdk at 21.

@obfusk
Copy link
Author

obfusk commented Aug 19, 2021

I'll test that later (or tomorrow, currently in the middle of 3 other projects) but this isn't really a fix for us as we do want to keep our minsdk at 21.

I get that. But at least it's some kind of progress (if it works).

@GaniduAsh
Copy link

tried with this
classpath 'com.android.tools:r8:3.0.69'

and maven { url 'https://storage.googleapis.com/r8-releases/raw' }

as it mentioned here

not working still

@linsui
Copy link

linsui commented Dec 9, 2022

I build the latest version 4 times and all of them are identical. But they are differnt with your release. The only different file is assets/dexopt/baseline.profm. This looks like a known issue https://issuetracker.google.com/issues/231837768 but I always get the same apk. I'd test more build.

@linsui
Copy link

linsui commented Dec 9, 2022

The 5th build produced an apk identical with your release. We can enable reproducible build for F-Droid and wait until it builds a reproducible apk. Or you can disable the baseline file.

@obfusk
Copy link
Author

obfusk commented Dec 9, 2022

We can enable reproducible build for F-Droid and wait until it builds a reproducible apk.

~5 build cycles before it gets published seems a bit too long to wait for updates, especially for NewPipe; unless e.g. we were to retry building a few times during the same cycle if RB fails, but that would require modifications to fdroidserver.

@ale5000-git
Copy link

I don't really know. It's really hard to tell. The diffoscope diff output is 25mb. :-/

Unfortunately I don't currently know how to test latest git version of the desugarer.

Meanwhile we recursively forked all our required libs, replaced the Java time api accesses with threetenbp and got rid of this stupid desugarer. The project is reproducible again now.

I had a similar reproducibility problem (time difference) with zipsigner and I have fixed it but running with:
java -Duser.timezone=UTC -jar "zipsigner.jar" ...
Maybe a similar fix can be applied elsewhere.

@obfusk
Copy link
Author

obfusk commented Dec 11, 2022

@linsui
Copy link

linsui commented Jan 16, 2023

@obfusk's https://github.com/obfusk/reproducible-apk-tools#sort-baselinepy can fix the profm problem. @TobiGr Do you want to have a try?

@obfusk
Copy link
Author

obfusk commented Jan 20, 2023

You can also sort the baseline.profm file by adding something like this to build.gradle:

import com.android.tools.profgen.ArtProfileKt
import com.android.tools.profgen.ArtProfileSerializer
import com.android.tools.profgen.DexFile

project.afterEvaluate {
    tasks.compileReleaseArtProfile.doLast {
        outputs.files.each { file ->
            if (file.toString().endsWith(".profm")) {
                println("Sorting ${file} ...")
                def version = ArtProfileSerializer.valueOf("METADATA_0_0_2")
                def profile = ArtProfileKt.ArtProfile(file)
                def keys = new ArrayList(profile.profileData.keySet())
                def sortedData = new LinkedHashMap()
                Collections.sort keys, new DexFile.Companion()
                keys.each { key -> sortedData[key] = profile.profileData[key] }
                new FileOutputStream(file).with {
                    write(version.magicBytes$profgen)
                    write(version.versionBytes$profgen)
                    version.write$profgen(it, sortedData, "")
                }
            }
        }
    }
}

@opusforlife2
Copy link
Collaborator

Out of curiosity, what, exactly, is a "baseline.profm"?

@obfusk
Copy link
Author

obfusk commented Jan 20, 2023

Out of curiosity, what, exactly, is a "baseline.profm"?

An Android ART profile metadata file, part of a compiled baseline profile; you can dump the contents with dump-baseline.py.

Not sure what it's actually used for. Google does not seem to provide any documentation regarding these files or their formats.

@Stypox
Copy link
Member

Stypox commented Jan 20, 2023

Here is an apk signed with my keys (NOT NewPipe's) based on v0.24.1 (git checkout v0.24.1), after adding the code snippet you suggested above to app/build.gradle. I checked that "Sorting ..." was printed. Can you confirm that this is reproducible? app-release.zip

@obfusk
Copy link
Author

obfusk commented Jan 20, 2023

Can you confirm that this is reproducible?

$ apksigcopier compare app-release-upstream.apk app-release-built-by-obfusk.apk && echo reproducible
reproducible

So yes :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue is related to a bug meta Related to the project but not strictly to code
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants