Add support for tracing main dex classes in R8
In addition to the given main dex list, allow to handle tracing rules
for calculating the list of classes required to be kept in the main dex
regarding legacy MultiDex constraints. Main dex list and constraints
tracing are not excliusive, when both are provided their class lists are
just merged. Runtime annotations with enum values are taken into account
precisely (if classpath is complete) and Annotations without enum are
not forced into the main dex so as classes annotated by them.
In this first step support is only added to R8.
In R8, When tree shaking and legacy main dex tracing are enabled together,
the assumption is made that tree shaking is keeping every classes with
code involved in secondary dex files installation. This allows to let tree
shaking reuse the tracing made for the main dex list instead of restarting
from scratch.
This change also introduces CompilationResult as an internal result allowing
easier inspection by tests. CompilationResult is not returned by public
R8/D8 methods.
Sources of new multidex tests are modified versions of some Jack tests.
Bug: 37772111
Change-Id: I7b741ace5a990a66cd4b991197de409c795e7d95
diff --git a/src/test/examplesAndroidO/multidex004/fakelibrary/MultiDex.java b/src/test/examplesAndroidO/multidex004/fakelibrary/MultiDex.java
new file mode 100644
index 0000000..3b15c11
--- /dev/null
+++ b/src/test/examplesAndroidO/multidex004/fakelibrary/MultiDex.java
@@ -0,0 +1,265 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package multidex004.fakelibrary;
+
+
+import multidex004.fakeframeworks.Application;
+import multidex004.fakeframeworks.Context;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Monkey patches {@link Context#getClassLoader() the application context class
+ * loader} in order to load classes from more than one dex file. The primary
+ * {@code classes.dex} must contain the classes necessary for calling this
+ * class methods. Secondary dex files named classes2.dex, classes3.dex... found
+ * in the application apk will be added to the classloader after first call to
+ * {@link #install(Context)}.
+ *
+ * <p/>
+ * This library provides compatibility for platforms with API level 4 through 20. This library does
+ * nothing on newer versions of the platform which provide built-in support for secondary dex files.
+ */
+public final class MultiDex {
+
+ static final String TAG = "MultiDex";
+
+ private static final String SECONDARY_FOLDER_NAME = "secondary-dexes";
+
+ private static final int MAX_SUPPORTED_SDK_VERSION = 20;
+
+ private static final int MIN_SDK_VERSION = 4;
+
+ private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;
+
+ private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;
+
+ private static final Set<String> installedApk = null;
+
+ private static final boolean IS_VM_MULTIDEX_CAPABLE =
+ isVMMultidexCapable(System.getProperty("java.vm.version"));
+
+ private MultiDex() {}
+
+ /**
+ * Patches the application context class loader by appending extra dex files
+ * loaded from the application apk. This method should be called in the
+ * attachBaseContext of your {@link Application}, see
+ * {@link MultiDexApplication} for more explanation and an example.
+ *
+ * @param context application context.
+ * @throws RuntimeException if an error occurred preventing the classloader
+ * extension.
+ */
+ public static void install(Context context) {
+ if (IS_VM_MULTIDEX_CAPABLE) {
+ try {
+ clearOldDexDir(context);
+ } catch (Throwable t) {
+ }
+ return;
+ }
+ }
+
+ /**
+ * Identifies if the current VM has a native support for multidex, meaning there is no need for
+ * additional installation by this library.
+ * @return true if the VM handles multidex
+ */
+ /* package visible for test */
+ static boolean isVMMultidexCapable(String versionString) {
+ boolean isMultidexCapable = false;
+ if (versionString != null) {
+ }
+ return isMultidexCapable;
+ }
+
+ private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)
+ throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
+ InvocationTargetException, NoSuchMethodException, IOException {
+ int version = new Random().nextInt(23);
+ if (!files.isEmpty()) {
+ if (version >= 19) {
+ V19.install(loader, files, dexDir);
+ } else if (version >= 14) {
+ V14.install(loader, files, dexDir);
+ } else {
+ V4.install(loader, files);
+ }
+ }
+ }
+
+ /**
+ * Returns whether all files in the list are valid zip files. If {@code files} is empty, then
+ * returns true.
+ */
+ private static boolean checkValidZipFiles(List<File> files) {
+ for (File file : files) {
+ }
+ return true;
+ }
+
+ /**
+ * Locates a given field anywhere in the class inheritance hierarchy.
+ *
+ * @param instance an object to search the field into.
+ * @param name field name
+ * @return a field object
+ * @throws NoSuchFieldException if the field cannot be located
+ */
+ private static Field findField(Object instance, String name) throws NoSuchFieldException {
+ for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
+ try {
+ Field field = clazz.getDeclaredField(name);
+
+
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+
+ return field;
+ } catch (NoSuchFieldException e) {
+ // ignore and search next
+ }
+ }
+
+ throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
+ }
+
+ /**
+ * Locates a given method anywhere in the class inheritance hierarchy.
+ *
+ * @param instance an object to search the method into.
+ * @param name method name
+ * @param parameterTypes method parameter types
+ * @return a method object
+ * @throws NoSuchMethodException if the method cannot be located
+ */
+ private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
+ throws NoSuchMethodException {
+ for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
+ try {
+ Method method = clazz.getDeclaredMethod(name, parameterTypes);
+
+
+ if (!method.isAccessible()) {
+ method.setAccessible(true);
+ }
+
+ return method;
+ } catch (NoSuchMethodException e) {
+ // ignore and search next
+ }
+ }
+
+ throw new NoSuchMethodException("Method " + name + " with parameters " +
+ parameterTypes + " not found in " + instance.getClass());
+ }
+
+ /**
+ * Replace the value of a field containing a non null array, by a new array containing the
+ * elements of the original array plus the elements of extraElements.
+ * @param instance the instance whose field is to be modified.
+ * @param fieldName the field to modify.
+ * @param extraElements elements to append at the end of the array.
+ */
+ private static void expandFieldArray(Object instance, String fieldName,
+ Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
+ IllegalAccessException {
+ Field jlrField = findField(instance, fieldName);
+ Object[] original = (Object[]) jlrField.get(instance);
+ Object[] combined = (Object[]) null;
+ System.arraycopy(original, 0, combined, 0, original.length);
+ System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
+ jlrField.set(instance, combined);
+ }
+
+ private static void clearOldDexDir(Context context) throws Exception {
+
+ }
+
+ /**
+ * Installer for platform versions 19.
+ */
+ private static final class V19 {
+
+ private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
+ File optimizedDirectory)
+ throws IllegalArgumentException, IllegalAccessException,
+ NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
+ /* The patched class loader is expected to be a descendant of
+ * dalvik.system.BaseDexClassLoader. We modify its
+ * dalvik.system.DexPathList pathList field to append additional DEX
+ * file entries.
+ */
+ Field pathListField = findField(loader, "pathList");
+ Object dexPathList = pathListField.get(loader);
+ }
+
+ }
+ /**
+ * Installer for platform versions 14, 15, 16, 17 and 18.
+ */
+ private static final class V14 {
+
+ private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
+ File optimizedDirectory)
+ throws IllegalArgumentException, IllegalAccessException,
+ NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
+ /* The patched class loader is expected to be a descendant of
+ * dalvik.system.BaseDexClassLoader. We modify its
+ * dalvik.system.DexPathList pathList field to append additional DEX
+ * file entries.
+ */
+ Field pathListField = findField(loader, "pathList");
+ Object dexPathList = pathListField.get(loader);
+ }
+
+ }
+
+ /**
+ * Installer for platform versions 4 to 13.
+ */
+ private static final class V4 {
+ private static void install(ClassLoader loader, List<File> additionalClassPathEntries)
+ throws IllegalArgumentException, IllegalAccessException,
+ NoSuchFieldException, IOException {
+ /* The patched class loader is expected to be a descendant of
+ * dalvik.system.DexClassLoader. We modify its
+ * fields mPaths, mFiles, mZips and mDexs to append additional DEX
+ * file entries.
+ */
+ int extraSize = additionalClassPathEntries.size();
+
+ Field pathField = findField(loader, "path");
+
+ StringBuilder path = new StringBuilder((String) pathField.get(loader));
+ String[] extraPaths = new String[extraSize];
+ File[] extraFiles = new File[extraSize];
+ for (ListIterator<File> iterator = additionalClassPathEntries.listIterator();
+ iterator.hasNext();) {
+ File additionalEntry = iterator.next();
+ String entryPath = additionalEntry.getAbsolutePath();
+ path.append(':').append(entryPath);
+ int index = iterator.previousIndex();
+ extraPaths[index] = entryPath;
+ extraFiles[index] = additionalEntry;
+ }
+
+ pathField.set(loader, path.toString());
+ expandFieldArray(loader, "mPaths", extraPaths);
+ expandFieldArray(loader, "mFiles", extraFiles);
+ }
+ }
+
+}