Version 1.0.21

Merge: Extend main dex list tracing to handle Class.forName
CL: https://r8-review.googlesource.com/c/r8/+/19062
Change-Id: I3fa479c65dfa5e4f84b1a491c97cc39d1b343257
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 4a3fc2e..a696aa4 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -8,14 +8,18 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexListBuilder;
+import com.android.tools.r8.shaking.ReasonPrinter;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.TreePruner;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -37,9 +41,11 @@
     AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
     RootSet mainDexRootSet =
         new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options).run(executor);
-    Set<DexType> mainDexBaseClasses =
-        new Enqueuer(appInfo, options).traceMainDex(mainDexRootSet, timing);
-    Set<DexType> mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+    Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
+    AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, timing);
+    // LiveTypes is the result.
+    Set<DexType> mainDexClasses =
+        new MainDexListBuilder(new HashSet<>(mainDexAppInfo.liveTypes), application).run();
 
     List<String> result = mainDexClasses.stream()
         .map(c -> c.toSourceString().replace('.', '/') + ".class")
@@ -50,6 +56,15 @@
       options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
     }
 
+    // Print -whyareyoukeeping results if any.
+    if (mainDexRootSet.reasonAsked.size() > 0) {
+      // Print reasons on the application after pruning, so that we reflect the actual result.
+      TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+      application = pruner.run();
+      ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
+      reasonPrinter.run(application);
+    }
+
     return result;
   }
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9609280..9b7ccc1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.DiscardedChecker;
 import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexListBuilder;
 import com.android.tools.r8.shaking.ProguardClassFilter;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -272,7 +273,8 @@
                 .run(executorService);
         ProtoLiteExtension protoLiteExtension =
             options.forceProguardCompatibility ? null : new ProtoLiteExtension(appInfo);
-        appInfo = new Enqueuer(appInfo, options, compatibility, protoLiteExtension)
+        appInfo = new Enqueuer(appInfo, options, options.forceProguardCompatibility,
+            compatibility, protoLiteExtension)
             .traceApplication(rootSet, timing);
         if (options.proguardConfiguration.isPrintSeeds()) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@@ -378,12 +380,15 @@
 
       if (!options.mainDexKeepRules.isEmpty()) {
         appInfo = new AppInfoWithSubtyping(application);
-        Enqueuer enqueuer = new Enqueuer(appInfo, options);
+        Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
         // Lets find classes which may have code executed before secondary dex files installation.
         RootSet mainDexRootSet =
             new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options)
                 .run(executorService);
-        Set<DexType> mainDexBaseClasses = enqueuer.traceMainDex(mainDexRootSet, timing);
+        AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, timing);
+
+        // LiveTypes is the result.
+        Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
 
         // Calculate the automatic main dex list according to legacy multidex constraints.
         // Add those classes to an eventual manual list of classes.
@@ -397,7 +402,7 @@
       if (options.useTreeShaking || !options.skipMinification) {
         timing.begin("Post optimization code stripping");
         try {
-          Enqueuer enqueuer = new Enqueuer(appInfo, options);
+          Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
           appInfo = enqueuer.traceApplication(rootSet, timing);
           if (options.useTreeShaking) {
             TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index a528e39..398d25d 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "v1.0.20";
+  public static final String LABEL = "v1.0.21";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d674ccb..db9b78c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -399,6 +399,10 @@
     return createType(createString(descriptor));
   }
 
+  synchronized public DexType lookupType(DexString descriptor) {
+    return types.get(descriptor);
+  }
+
   public DexType createArrayType(int nesting, DexType baseType) {
     assert nesting > 0;
     return createType(Strings.repeat("[", nesting) + baseType.toDescriptorString());
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 9aff920..90bd458 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -78,6 +78,8 @@
  * field descriptions for details.
  */
 public class Enqueuer {
+
+  private final boolean forceProguardCompatibility;
   private boolean tracingMainDex = false;
 
   private final AppInfoWithSubtyping appInfo;
@@ -187,16 +189,19 @@
    */
   private final ProguardConfiguration.Builder compatibility;
 
-  public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options) {
-    this(appInfo, options, null, null);
+  public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options,
+      boolean forceProguardCompatibility) {
+    this(appInfo, options, forceProguardCompatibility, null, null);
   }
 
   public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options,
+      boolean forceProguardCompatibility,
       ProguardConfiguration.Builder compatibility, ProtoLiteExtension protoLiteExtension) {
     this.appInfo = appInfo;
     this.compatibility = compatibility;
     this.options = options;
     this.protoLiteExtension = protoLiteExtension;
+    this.forceProguardCompatibility = forceProguardCompatibility;
   }
 
   private void enqueueRootItems(Map<DexItem, ProguardKeepRule> items) {
@@ -271,7 +276,7 @@
 
     @Override
     public boolean registerInvokeStatic(DexMethod method) {
-      if (options.forceProguardCompatibility
+      if (forceProguardCompatibility
           && method == appInfo.dexItemFactory.classMethods.forName) {
         pendingProguardReflectiveCompatibility.add(currentMethod);
       }
@@ -440,7 +445,7 @@
       enqueueRootItems(rootSet.getDependentStaticMembers(type));
 
       // For Proguard compatibility keep the default initializer for live types.
-      if (options.forceProguardCompatibility) {
+      if (forceProguardCompatibility) {
         if (holder.isProgramClass() && holder.hasDefaultInitializer()) {
           markClassAsInstantiatedWithCompatRule(holder);
         }
@@ -512,7 +517,7 @@
       Diagnostic message = new StringDiagnostic("Library class " + context.toSourceString()
           + (holder.isInterface() ? " implements " : " extends ")
           + "program class " + type.toSourceString());
-      if (options.forceProguardCompatibility) {
+      if (forceProguardCompatibility) {
         options.reporter.warning(message);
       } else {
         options.reporter.error(message);
@@ -544,7 +549,7 @@
       Log.verbose(getClass(), "Method `%s` is targeted.", encodedMethod.method);
     }
     targetedMethods.add(encodedMethod, reason);
-    if (options.forceProguardCompatibility) {
+    if (forceProguardCompatibility) {
       // Keep targeted default methods in compatibility mode. The tree pruner will otherwise make
       // these methods abstract, whereas Proguard does not (seem to) touch their code.
       DexClass clazz = appInfo.definitionFor(encodedMethod.method.holder);
@@ -950,15 +955,14 @@
         reachability, instantiatedTypes.getReasons());
   }
 
-  public Set<DexType> traceMainDex(RootSet rootSet, Timing timing) {
+  public AppInfoWithLiveness traceMainDex(RootSet rootSet, Timing timing) {
     this.tracingMainDex = true;
     this.rootSet = rootSet;
     // Translate the result of root-set computation into enqueuer actions.
     enqueueRootItems(rootSet.noShrinking);
     AppInfoWithLiveness appInfo = trace(timing);
     options.reporter.failIfPendingErrors();
-    // LiveTypes is the result, just make a copy because further work will modify its content.
-    return new HashSet<>(appInfo.liveTypes);
+    return appInfo;
   }
 
   public AppInfoWithLiveness traceApplication(RootSet rootSet, Timing timing) {
diff --git a/src/test/examples/multidex005/ReflectionReference.java b/src/test/examples/multidex005/ReflectionReference.java
new file mode 100644
index 0000000..791e7d2
--- /dev/null
+++ b/src/test/examples/multidex005/ReflectionReference.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, 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 multidex005;
+
+public class ReflectionReference {
+  Class directlyReferencedClass;
+
+  public ReflectionReference() throws ClassNotFoundException {
+    directlyReferencedClass = Class.forName("multidex005.ClassReference");
+  }
+
+  public Object noReference() throws ClassNotFoundException {
+    return Class.forName("multidex005.IndirectlyReferenced");
+  }
+
+}
diff --git a/src/test/examples/multidex005/main-dex-rules-7.txt b/src/test/examples/multidex005/main-dex-rules-7.txt
new file mode 100644
index 0000000..84120e0
--- /dev/null
+++ b/src/test/examples/multidex005/main-dex-rules-7.txt
@@ -0,0 +1,7 @@
+# Copyright (c) 2018, 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.
+
+-keep public class *.ReflectionReference {
+  <init>();
+}
diff --git a/src/test/examples/multidex005/ref-list-7.txt b/src/test/examples/multidex005/ref-list-7.txt
new file mode 100644
index 0000000..5f01577
--- /dev/null
+++ b/src/test/examples/multidex005/ref-list-7.txt
@@ -0,0 +1,9 @@
+Lmultidex005/ClassReference;
+Lmultidex005/DirectlyReferenced;
+Lmultidex005/Interface1;
+Lmultidex005/Interface2;
+Lmultidex005/Interface3;
+Lmultidex005/ReflectionReference;
+Lmultidex005/SuperClass;
+Lmultidex005/SuperInterface;
+Lmultidex005/SuperSuperClass;
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 8c57058..263aff7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -125,6 +125,11 @@
   }
 
   @Test
+  public void traceMainDexList005_7() throws Throwable {
+    doTest5(7);
+  }
+
+  @Test
   public void traceMainDexList006() throws Throwable {
     doTest(
         "traceMainDexList006",
@@ -137,7 +142,7 @@
 
   private void doTest5(int variant) throws Throwable {
     doTest(
-        "traceMainDexList003",
+        "traceMainDexList005",
         "multidex005",
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex005", "main-dex-rules-" + variant + ".txt"),
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index b55aea0..1b1f823 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -76,7 +76,7 @@
 
     RootSet rootSet = new RootSetBuilder(program, appInfo, configuration.getRules(), options)
         .run(ThreadUtils.getExecutorService(options));
-    Enqueuer enqueuer = new Enqueuer(appInfo, options);
+    Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
     appInfo = enqueuer.traceApplication(rootSet, timing);
     return new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
   }