From 1b1aa5dd75441be2a99b23d841af34c8687cad3e Mon Sep 17 00:00:00 2001 From: Chris Kitching Date: Fri, 5 Dec 2014 11:50:48 -0800 Subject: [PATCH] Bug 1106593: Proguard third-party libraries that ship with Fennec. r=nalexander This applies Proguard to third-party libraries such as the Android support-v4 library and the Google Play Services libraries. Previously, these were not Proguarded, bloating the Fennec APK. Technically, this required a few work-arounds, including: * stripping library debug information with a early Proguard invocation; * altering the optimizations tried; and * reducing the number of Proguard passes. --HG-- rename : mobile/android/config/proguard.cfg => mobile/android/config/proguard/proguard.cfg extra : rebase_source : 6d638695b6c8f759578aba5f1eda668fc9c28e9d extra : amend_source : 96a475c0739c4b44a3df3fdfd2c59321836d9694 --- mobile/android/base/Makefile.in | 82 ++++++++++++++----- .../config/proguard/play-services-keeps.cfg | 19 +++++ .../config/{ => proguard}/proguard.cfg | 11 +++ mobile/android/config/proguard/strip-libs.cfg | 40 +++++++++ 4 files changed, 133 insertions(+), 19 deletions(-) create mode 100644 mobile/android/config/proguard/play-services-keeps.cfg rename mobile/android/config/{ => proguard}/proguard.cfg (93%) create mode 100644 mobile/android/config/proguard/strip-libs.cfg diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 94d355d507c..3cb85700506 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -60,14 +60,20 @@ GARBAGE += \ GARBAGE_DIRS += classes db jars res sync services generated -JAVA_BOOTCLASSPATH = \ +# The bootclasspath is functionally identical to the classpath, but allows the +# classes given to redefine classes in core packages, such as java.lang. +# android.jar is here as it provides Android's definition of the Java Standard +# Library. The compatability lib here tweaks a few of the core classes to paint +# over changes in behaviour between versions. +JAVA_BOOTCLASSPATH := \ $(ANDROID_SDK)/android.jar \ $(ANDROID_COMPAT_LIB) \ $(NULL) JAVA_BOOTCLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_BOOTCLASSPATH))) -# If native devices are enabled, add Google Play Services and some of the v7 compat libraries +# If native devices are enabled, add Google Play Services and some of the v7 +# compat libraries. ifdef MOZ_NATIVE_DEVICES JAVA_CLASSPATH += \ $(GOOGLE_PLAY_SERVICES_LIB) \ @@ -78,6 +84,24 @@ endif JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH))) +# Library jars that we're bundling: these are subject to Proguard before inclusion +# into classes.dex. +java_bundled_libs := \ + $(ANDROID_COMPAT_LIB) \ + $(NULL) + +ifdef MOZ_NATIVE_DEVICES + java_bundled_libs += \ + $(GOOGLE_PLAY_SERVICES_LIB) \ + $(ANDROID_MEDIAROUTER_LIB) \ + $(ANDROID_APPCOMPAT_LIB) \ + $(NULL) +endif + +java_bundled_libs := $(subst $(NULL) ,:,$(strip $(java_bundled_libs))) + +# All the jars we're compiling from source. (not to be confused with +# java_bundled_libs, which holds the jars which we're including as binaries). ALL_JARS = \ constants.jar \ gecko-R.jar \ @@ -102,24 +126,20 @@ ALL_JARS += ../stumbler/stumbler.jar generated/org/mozilla/mozstumbler/R.java: .aapt.deps ; endif +# The list of jars in Java classpath notation (colon-separated). +all_jars_classpath := $(subst $(NULL) ,:,$(strip $(ALL_JARS))) + include $(topsrcdir)/config/config.mk -# Note that we're going to set up a dependency directly between embed_android.dex and the java files -# Instead of on the .class files, since more than one .class file might be produced per .java file -# Sync dependencies are provided in a single jar. Sync classes themselves are delivered as source, -# because Android resource classes must be compiled together in order to avoid overlapping resource -# indices. - -library_jars = \ - $(JAVA_CLASSPATH) \ - $(JAVA_BOOTCLASSPATH) \ +library_jars := \ + $(ANDROID_SDK)/android.jar \ $(NULL) library_jars := $(subst $(NULL) ,:,$(strip $(library_jars))) classes.dex: .proguard.deps $(REPORT_BUILD) - $(DX) --dex --output=classes.dex jars-proguarded $(subst :, ,$(ANDROID_COMPAT_LIB):$(JAVA_CLASSPATH)) + $(DX) --dex --output=classes.dex jars-proguarded ifdef MOZ_DISABLE_PROGUARD PROGUARD_PASSES=0 @@ -135,6 +155,8 @@ else endif endif +proguard_config_dir=$(topsrcdir)/mobile/android/config/proguard + # This stanza ensures that the set of GeckoView classes does not depend on too # much of Fennec, where "too much" is defined as the set of potentially # non-GeckoView classes that GeckoView already depended on at a certain point in @@ -148,19 +170,41 @@ classycle_jar := $(topsrcdir)/mobile/android/build/classycle/classycle-1.4.1.jar $(ALL_JARS) @$(TOUCH) $@ -# We touch the target file before invoking Proguard so that Proguard's -# outputs are fresher than the target, preventing a subsequent -# invocation from thinking Proguard's outputs are stale. This is safe -# because Make removes the target file if any recipe command fails. -.proguard.deps: .geckoview.deps $(ALL_JARS) $(topsrcdir)/mobile/android/config/proguard.cfg +# First, we delete debugging information from libraries. Having line-number +# information for libraries for which we lack the source isn't useful, so this +# saves us a bit of space. Importantly, Proguard has a bug causing it to +# sometimes corrupt this information if present (which it does for some of the +# included libraries). This corruption prevents dex from completing, so we need +# to get rid of it. This prevents us from seeing line numbers in stack traces +# for stack frames inside libraries. +# +# This step can occur much earlier than the main Proguard pass: it needs only +# gecko-R.jar to have been compiled (as that's where the library R.java files +# end up), but it does block the main Proguard pass. +.bundled.proguard.deps: gecko-R.jar $(proguard_config_dir)/strip-libs.cfg $(REPORT_BUILD) @$(TOUCH) $@ java \ -Xmx512m -Xms128m \ -jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \ - @$(topsrcdir)/mobile/android/config/proguard.cfg \ + @$(proguard_config_dir)/strip-libs.cfg \ + -injars $(subst ::,:,$(java_bundled_libs))\ + -outjars bundled-jars-nodebug \ + -libraryjars $(library_jars):gecko-R.jar + +# We touch the target file before invoking Proguard so that Proguard's +# outputs are fresher than the target, preventing a subsequent +# invocation from thinking Proguard's outputs are stale. This is safe +# because Make removes the target file if any recipe command fails. +.proguard.deps: .geckoview.deps .bundled.proguard.deps $(ALL_JARS) $(proguard_config_dir)/proguard.cfg + $(REPORT_BUILD) + @$(TOUCH) $@ + java \ + -Xmx512m -Xms128m \ + -jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \ + @$(proguard_config_dir)/proguard.cfg \ -optimizationpasses $(PROGUARD_PASSES) \ - -injars $(subst ::,:,$(subst $(NULL) ,:,$(strip $(ALL_JARS)))) \ + -injars $(subst ::,:,$(all_jars_classpath)):bundled-jars-nodebug \ -outjars jars-proguarded \ -libraryjars $(library_jars) diff --git a/mobile/android/config/proguard/play-services-keeps.cfg b/mobile/android/config/proguard/play-services-keeps.cfg new file mode 100644 index 00000000000..b3aaf80aa97 --- /dev/null +++ b/mobile/android/config/proguard/play-services-keeps.cfg @@ -0,0 +1,19 @@ +# Rules to prevent Google Play Services from exploding +# (From http://developer.android.com/google/play-services/setup.html#Proguard +# With the reference to "Object" changed so it'll actually *work*...) +-keep class * extends java.util.ListResourceBundle { + protected java.lang.Object[][] getContents(); +} + +-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable { + public static final *** NULL; +} + +-keepnames @com.google.android.gms.common.annotation.KeepName class * +-keepclassmembernames class * { + @com.google.android.gms.common.annotation.KeepName *; +} + +-keepnames class * implements android.os.Parcelable { + public static final ** CREATOR; +} diff --git a/mobile/android/config/proguard.cfg b/mobile/android/config/proguard/proguard.cfg similarity index 93% rename from mobile/android/config/proguard.cfg rename to mobile/android/config/proguard/proguard.cfg index d84996dea45..5da9f1bacae 100644 --- a/mobile/android/config/proguard.cfg +++ b/mobile/android/config/proguard/proguard.cfg @@ -110,6 +110,11 @@ -optimizations !class/merging/horizontal -optimizations !class/merging/vertical +# This optimisation causes corrupt bytecode if we run more than two passes. +# Testing shows that running the extra passes of everything else saves us +# more than this optimisation does, so bye bye! +-optimizations !code/allocation/variable + # Keep miscellaneous targets. # Keep the annotation. @@ -207,3 +212,9 @@ # Suppress warnings about missing descriptor classes. #-dontnote **,!ch.boye.**,!org.mozilla.gecko.sync.** + +-include "play-services-keeps.cfg" + +# Don't print spurious warnings from the support library. +# See: http://stackoverflow.com/questions/22441366/note-android-support-v4-text-icucompatics-cant-find-dynamically-referenced-cl +-dontnote android.support.** diff --git a/mobile/android/config/proguard/strip-libs.cfg b/mobile/android/config/proguard/strip-libs.cfg new file mode 100644 index 00000000000..b9706b61d1b --- /dev/null +++ b/mobile/android/config/proguard/strip-libs.cfg @@ -0,0 +1,40 @@ +# Proguard step for stripping debug information. +# +# This is useful to work around a bug in the way Proguard handles debug information: it +# sometimes corrupts it. Classes with corrupt debug information cannot be dexed, but +# classes with *no* debug information can be. There's no way to configure Proguard to +# delete debug information on a per-class basis, so we need this special extra step for +# stripping debug information only from those classes for which the Proguard bug is +# encountered. +# +# Currently, this pass is applied to all bundled library jars for which we are not +# compiling the source. This is slightly more than is strictly necessary to work around +# the Proguard bug, but such debug information is of negligible value and stripping it +# too slightly simplifies the makefile and saves us a handful of kilobytes of binary size. +# +# Configuring Proguard to do nothing except strip metadata is done by having it run only +# the obfuscation pass, but with a configuration that prevents it from renaming any classes. +# It then attempts to delete class metadata, so we further configure it not to do so for +# anything except the problematic debug information. + +# Run only the obfuscator. +-dontoptimize +-dontshrink +-dontpreverify +-verbose + +# Don't rename anything. +-keeppackagenames + +# Seriously, don't rename anything. +-keep class * +-keepclassmembers class * { + *; +} + +# Don't delete other useful metadata. +-keepattributes Exceptions,InnerClasses,Signature,Deprecated,*Annotation*,EnclosingMethod + +# Don't print spurious warnings from the support library. +# See: http://stackoverflow.com/questions/22441366/note-android-support-v4-text-icucompatics-cant-find-dynamically-referenced-cl +-dontnote android.support.**