Version 1.0.29

Merge: Keep fewer default constructors in Proguard compat mode
CL: https://r8-review.googlesource.com/c/r8/+/18121

Merge: Only add keep rule when there is a default ctor
CL: https://r8-review.googlesource.com/c/r8/+/21621

Merge: Fix use of -whyareyoukeeping with main dex rules
CL: https://r8-review.googlesource.com/c/r8/+/21240

Bug: 78678415
Change-Id: If975d4be4ec6bd8c054794f636f7fc60a25fddd2
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index a696aa4..84a7929 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -59,7 +59,7 @@
     // 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);
+      TreePruner pruner = new TreePruner(application, mainDexAppInfo.withLiveness(), options);
       application = pruner.run();
       ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
       reasonPrinter.run(application);
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6a39500..6b1ed51 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.28";
+  public static final String LABEL = "v1.0.29";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index d3576c1..9416a7b 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    registry.registerTypeReference(getType());
+    registry.registerCheckCast(getType());
   }
 
   public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index f140022..ef8b257 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    registry.registerTypeReference(getType());
+    registry.registerConstClass(getType());
   }
 
   public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 78b2f56..48ef91d 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -27,6 +27,14 @@
 
   public abstract boolean registerTypeReference(DexType type);
 
+  public boolean registerConstClass(DexType type) {
+    return registerTypeReference(type);
+  }
+
+  public boolean registerCheckCast(DexType type) {
+    return registerTypeReference(type);
+  }
+
   public void registerMethodHandle(DexMethodHandle methodHandle) {
     switch (methodHandle.type) {
       case INSTANCE_GET:
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index f9a7f49..37c9aea 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -35,6 +35,8 @@
     DexType type = application.getTypeFromName(name);
     if (opcode == org.objectweb.asm.Opcodes.NEW) {
       registry.registerNewInstance(type);
+    } else if (opcode == Opcodes.CHECKCAST) {
+        registry.registerCheckCast(type);
     } else {
       registry.registerTypeReference(type);
     }
@@ -51,7 +53,7 @@
       // Nothing to register for method type, it represents only a prototype not associated with a
       // method name.
       if (((Type) cst).getSort() != Type.METHOD) {
-        registry.registerTypeReference(application.getType((Type) cst));
+        registry.registerConstClass(application.getType((Type) cst));
       }
     } else if (cst instanceof Handle) {
       registerMethodHandleType((Handle) cst);
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 71ffe54..5200d06 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -205,11 +205,30 @@
   }
 
   private void enqueueRootItems(Map<DexItem, ProguardKeepRule> items) {
-    workList.addAll(
-        items.entrySet().stream().map(Action::forRootItem).collect(Collectors.toList()));
+    items.entrySet().forEach(this::enqueueRootItem);
     pinnedItems.addAll(items.keySet());
   }
 
+  private void enqueueRootItem(Map.Entry<DexItem, ProguardKeepRule> root) {
+    DexItem item = root.getKey();
+    KeepReason reason = KeepReason.dueToKeepRule(root.getValue());
+    if (item instanceof DexClass) {
+      DexClass clazz = (DexClass) item;
+      workList.add(Action.markInstantiated(clazz, reason));
+      if (options.forceProguardCompatibility && clazz.hasDefaultInitializer()) {
+        ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
+        proguardCompatibilityWorkList.add(Action.markMethodLive(
+            clazz.getDefaultInitializer(), KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+      }
+    } else if (item instanceof DexEncodedField) {
+      workList.add(Action.markFieldKept((DexEncodedField) item, reason));
+    } else if (item instanceof DexEncodedMethod) {
+      workList.add(Action.markMethodKept((DexEncodedMethod) item, reason));
+    } else {
+      throw new IllegalArgumentException(item.toString());
+    }
+  }
+
   //
   // Things to do with registering events. This is essentially the interface for byte-code
   // traversals.
@@ -374,6 +393,16 @@
     }
 
     @Override
+    public boolean registerConstClass(DexType type) {
+      return registerConstClassOrCheckCast(type);
+    }
+
+    @Override
+    public boolean registerCheckCast(DexType type) {
+      return registerConstClassOrCheckCast(type);
+    }
+
+    @Override
     public boolean registerTypeReference(DexType type) {
       DexType baseType = type.toBaseType(appInfo.dexItemFactory);
       if (baseType.isClassType()) {
@@ -382,6 +411,26 @@
       }
       return false;
     }
+
+    private boolean registerConstClassOrCheckCast(DexType type) {
+      if (options.forceProguardCompatibility) {
+        DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+        if (baseType.isClassType()) {
+          DexClass baseClass = appInfo.definitionFor(baseType);
+          if (baseClass != null && baseClass.isProgramClass()
+              && baseClass.hasDefaultInitializer()) {
+            markClassAsInstantiatedWithCompatRule(baseClass);
+          } else {
+            // This also handles reporting of missing classes.
+            markTypeAsLive(baseType);
+          }
+          return true;
+        }
+        return false;
+      } else {
+        return registerTypeReference(type);
+      }
+    }
   }
 
   private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
@@ -441,14 +490,12 @@
         annotations.forEach(this::handleAnnotationOfLiveType);
       }
 
-      // Add all dependent static members to the workqueue.
-      enqueueRootItems(rootSet.getDependentStaticMembers(type));
-
-      // For Proguard compatibility keep the default initializer for live types.
-      if (forceProguardCompatibility) {
-        if (holder.isProgramClass() && holder.hasDefaultInitializer()) {
-          markClassAsInstantiatedWithCompatRule(holder);
-        }
+      if (options.forceProguardCompatibility) {
+        // Add all dependent members to the workqueue.
+        enqueueRootItems(rootSet.getDependentItems(type));
+      } else {
+        // Add all dependent static members to the workqueue.
+        enqueueRootItems(rootSet.getDependentStaticMembers(type));
       }
     }
   }
@@ -568,6 +615,7 @@
     if (!instantiatedTypes.add(clazz.type, reason)) {
       return;
     }
+
     collectProguardCompatibilityRule(reason);
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
@@ -1287,20 +1335,6 @@
       return new Action(Kind.MARK_FIELD_KEPT, method, null, reason);
     }
 
-    public static Action forRootItem(Map.Entry<DexItem, ProguardKeepRule> root) {
-      DexItem item = root.getKey();
-      KeepReason reason = KeepReason.dueToKeepRule(root.getValue());
-      if (item instanceof DexClass) {
-        return markInstantiated((DexClass) item, reason);
-      } else if (item instanceof DexEncodedField) {
-        return markFieldKept((DexEncodedField) item, reason);
-      } else if (item instanceof DexEncodedMethod) {
-        return markMethodKept((DexEncodedMethod) item, reason);
-      } else {
-        throw new IllegalArgumentException(item.toString());
-      }
-    }
-
     private enum Kind {
       MARK_REACHABLE_VIRTUAL,
       MARK_REACHABLE_INTERFACE,
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
deleted file mode 100644
index 7904400..0000000
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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 com.android.tools.r8.shaking.keepclassmembers;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.google.common.collect.ImmutableList;
-import org.junit.Test;
-
-public class KeepClassMembersTest extends TestBase {
-
-  public void runTest(Class mainClass, Class<?> staticClass,
-      boolean forceProguardCompatibility) throws Exception {
-    boolean staticClassHasDefaultConstructor = true;
-    try {
-      staticClass.getDeclaredConstructor();
-    } catch (NoSuchMethodException e) {
-      staticClassHasDefaultConstructor = false;
-    }
-    String proguardConfig = String.join("\n", ImmutableList.of(
-        "-keepclassmembers class **.PureStatic* {",
-        "  public static int b;",
-        "  public static int getA();",
-        "  public int getI();",
-        "}",
-        "-keep class **.MainUsing* {",
-        "  public static void main(java.lang.String[]);",
-        "}",
-        "-dontoptimize", "-dontobfuscate"
-    ));
-    DexInspector inspector = new DexInspector(
-        compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
-            options -> options.forceProguardCompatibility = forceProguardCompatibility));
-    assertTrue(inspector.clazz(mainClass).isPresent());
-    ClassSubject staticClassSubject = inspector.clazz(staticClass);
-    assertTrue(staticClassSubject.isPresent());
-    assertTrue(staticClassSubject.method("int", "getA", ImmutableList.of()).isPresent());
-    assertFalse(staticClassSubject.method("int", "getB", ImmutableList.of()).isPresent());
-    assertTrue(staticClassSubject.field("int", "a").isPresent());
-    assertTrue(staticClassSubject.field("int", "b").isPresent());
-    assertFalse(staticClassSubject.field("int", "c").isPresent());
-    // Force Proguard compatibility keeps the default constructor if present and then assumes
-    // instantiated, hence keeps the instance method as well.
-    assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
-        staticClassSubject.init(ImmutableList.of()).isPresent());
-    assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
-        staticClassSubject.method("int", "getI", ImmutableList.of()).isPresent());
-    assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
-        staticClassSubject.field("int", "i").isPresent());
-    assertFalse(staticClassSubject.method("int", "getJ", ImmutableList.of()).isPresent());
-    assertFalse(staticClassSubject.field("int", "j").isPresent());
-  }
-
-  @Test
-  public void regress69028743() throws Exception {
-    runTest(MainUsingWithDefaultConstructor.class,
-        PureStaticClassWithDefaultConstructor.class, false);
-    runTest(MainUsingWithDefaultConstructor.class,
-        PureStaticClassWithDefaultConstructor.class, true);
-    runTest(MainUsingWithoutDefaultConstructor.class,
-        PureStaticClassWithoutDefaultConstructor.class, false);
-    runTest(MainUsingWithoutDefaultConstructor.class,
-        PureStaticClassWithoutDefaultConstructor.class, true);
-  }
-}