Fix keep rule debugging support.

When debugging keep rules, we used to keep all classes and methods. This
blows up the dex to a size that no longer runs on devices. With this change
we only keep default constructors, to handle the use of newInstance without
corresponding keep rule.

R=ager@google.com

Bug:
Change-Id: I3bca90c14ede030d03b4b0ae9e4c84dcdbbb41a8
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 3df2fab..c8fbbea 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -12,9 +12,12 @@
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.ConstStringJumbo;
 import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeDirect;
 import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeSuper;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.ir.code.IRCode;
@@ -30,6 +33,7 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
@@ -238,7 +242,7 @@
    * templates might incur a size overhead.
    */
   private DexCode generateCodeFromTemplate(
-      int numberOfRegisters, int outRegisters, Instruction[] instructions) {
+      int numberOfRegisters, int outRegisters, Instruction... instructions) {
     int offset = 0;
     for (Instruction instruction : instructions) {
       assert !(instruction instanceof ConstString);
@@ -276,21 +280,33 @@
         .createMethod(itemFactory.createType("Landroid/util/Log;"), proto,
             itemFactory.createString("e"));
     DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;");
-    DexType[] exceptionArgs = {exceptionType, itemFactory.stringType};
-    DexMethod initMethod = itemFactory
-        .createMethod(exceptionType, itemFactory.createProto(itemFactory.voidType, exceptionArgs),
+    DexMethod exceptionInitMethod = itemFactory
+        .createMethod(exceptionType, itemFactory.createProto(itemFactory.voidType,
+            itemFactory.stringType),
             itemFactory.constructorMethodName);
-    // These methods might not get registered for jumbo string processing, therefore we always
-    // use the jumbo string encoding for the const string instruction.
-    Instruction insn[] = {
-        new ConstStringJumbo(0, tag),
-        new ConstStringJumbo(1, message),
-        new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
-        new NewInstance(0, exceptionType),
-        new InvokeStatic(2, initMethod, 0, 1, 0, 0, 0),
-        new Throw(0)
-    };
-    DexCode code = generateCodeFromTemplate(2, 2, insn);
+    DexCode code;
+    if (accessFlags.isConstructor() && !accessFlags.isStatic()) {
+      // The Java VM Spec requires that a constructor calls an initializer from the super class
+      // or another constructor from the current class. For simplicity we do the latter by just
+      // calling outself. This is ok, as the constructor always throws before the recursive call.
+      code = generateCodeFromTemplate(3, 2, new ConstStringJumbo(0, tag),
+          new ConstStringJumbo(1, message),
+          new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
+          new NewInstance(0, exceptionType),
+          new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
+          new Throw(0),
+          new InvokeDirect(1, method, 2, 0, 0, 0, 0));
+
+    } else {
+      // These methods might not get registered for jumbo string processing, therefore we always
+      // use the jumbo string encoding for the const string instruction.
+      code = generateCodeFromTemplate(2, 2, new ConstStringJumbo(0, tag),
+          new ConstStringJumbo(1, message),
+          new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
+          new NewInstance(0, exceptionType),
+          new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
+          new Throw(0));
+    }
     Builder builder = builder(this);
     builder.setCode(code);
     return builder.build();
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 984215b..5dff4b4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -222,32 +222,22 @@
       DexString toStringMethodName = createString("toString");
 
 
-      appendBoolean =
-          createMethod(receiver, createProto(receiver, new DexType[]{booleanType}), append);
-      appendChar = createMethod(receiver, createProto(receiver, new DexType[]{charType}), append);
-      appendCharArray =
-          createMethod(receiver, createProto(receiver, new DexType[]{charArrayType}), append);
+      appendBoolean = createMethod(receiver, createProto(receiver, booleanType), append);
+      appendChar = createMethod(receiver, createProto(receiver, charType), append);
+      appendCharArray = createMethod(receiver, createProto(receiver, charArrayType), append);
       appendSubCharArray =
-          createMethod(receiver,
-              createProto(receiver, new DexType[]{charArrayType, intType, intType}), append);
-      appendCharSequence =
-          createMethod(receiver, createProto(receiver, new DexType[]{charSequenceType}), append);
+          createMethod(receiver, createProto(receiver, charArrayType, intType, intType), append);
+      appendCharSequence = createMethod(receiver, createProto(receiver, charSequenceType), append);
       appendSubCharSequence =
-          createMethod(receiver,
-              createProto(receiver, new DexType[]{charSequenceType, intType, intType}), append);
-      appendInt = createMethod(receiver, createProto(receiver, new DexType[]{intType}), append);
-      appendDouble =
-          createMethod(receiver, createProto(receiver, new DexType[]{doubleType}), append);
-      appendFloat = createMethod(receiver, createProto(receiver, new DexType[]{floatType}), append);
-      appendLong = createMethod(receiver, createProto(receiver, new DexType[]{longType}), append);
-      appendObject =
-          createMethod(receiver, createProto(receiver, new DexType[]{objectType}), append);
-      appendString =
-          createMethod(receiver, createProto(receiver, new DexType[]{stringType}), append);
-      appendStringBuffer =
-          createMethod(receiver, createProto(receiver, new DexType[]{sbufType}), append);
-      toString =
-          createMethod(receiver, createProto(stringType, DexType.EMPTY_ARRAY), toStringMethodName);
+          createMethod(receiver, createProto(receiver, charSequenceType, intType, intType), append);
+      appendInt = createMethod(receiver, createProto(receiver, intType), append);
+      appendDouble = createMethod(receiver, createProto(receiver, doubleType), append);
+      appendFloat = createMethod(receiver, createProto(receiver, floatType), append);
+      appendLong = createMethod(receiver, createProto(receiver, longType), append);
+      appendObject = createMethod(receiver, createProto(receiver, objectType), append);
+      appendString = createMethod(receiver, createProto(receiver, stringType), append);
+      appendStringBuffer = createMethod(receiver, createProto(receiver, sbufType), append);
+      toString = createMethod(receiver, createProto(stringType), toStringMethodName);
     }
 
     public void forEachAppendMethod(Consumer<DexMethod> consumer) {
@@ -322,7 +312,7 @@
         parameters.length == 0 ? DexTypeList.empty() : new DexTypeList(parameters));
   }
 
-  public DexProto createProto(DexType returnType, DexType[] parameters) {
+  public DexProto createProto(DexType returnType, DexType... parameters) {
     return createProto(createShorty(returnType, parameters), returnType, parameters);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 4d3d8cb..234773f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -19,8 +19,6 @@
 
 public class DexType extends IndexedDexItem implements PresortedComparable<DexType> {
 
-  public static final DexType[] EMPTY_ARRAY = new DexType[]{};
-
   private final static int ROOT_LEVEL = 0;
   private final static int UNKNOWN_LEVEL = -1;
   private final static int INTERFACE_LEVEL = -2;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 2355e5e..9ec776f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.EMPTY_TYPE_ARRAY;
-
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -421,28 +419,28 @@
     DexProto proto;
     switch (primitive) {
       case 'Z':  // byte
-        proto = factory.createProto(factory.booleanType, EMPTY_TYPE_ARRAY);
+        proto = factory.createProto(factory.booleanType);
         return factory.createMethod(boxType, proto, factory.unboxBooleanMethodName);
       case 'B':  // byte
-        proto = factory.createProto(factory.byteType, EMPTY_TYPE_ARRAY);
+        proto = factory.createProto(factory.byteType);
         return factory.createMethod(boxType, proto, factory.unboxByteMethodName);
       case 'S':  // short
-        proto = factory.createProto(factory.shortType, EMPTY_TYPE_ARRAY);
+        proto = factory.createProto(factory.shortType);
         return factory.createMethod(boxType, proto, factory.unboxShortMethodName);
       case 'C':  // char
-        proto = factory.createProto(factory.charType, EMPTY_TYPE_ARRAY);
+        proto = factory.createProto(factory.charType);
         return factory.createMethod(boxType, proto, factory.unboxCharMethodName);
       case 'I':  // int
-        proto = factory.createProto(factory.intType, EMPTY_TYPE_ARRAY);
+        proto = factory.createProto(factory.intType);
         return factory.createMethod(boxType, proto, factory.unboxIntMethodName);
       case 'J':  // long
-        proto = factory.createProto(factory.longType, EMPTY_TYPE_ARRAY);
+        proto = factory.createProto(factory.longType);
         return factory.createMethod(boxType, proto, factory.unboxLongMethodName);
       case 'F':  // float
-        proto = factory.createProto(factory.floatType, EMPTY_TYPE_ARRAY);
+        proto = factory.createProto(factory.floatType);
         return factory.createMethod(boxType, proto, factory.unboxFloatMethodName);
       case 'D':  // double
-        proto = factory.createProto(factory.doubleType, EMPTY_TYPE_ARRAY);
+        proto = factory.createProto(factory.doubleType);
         return factory.createMethod(boxType, proto, factory.unboxDoubleMethodName);
       default:
         throw new Unreachable("Invalid primitive type descriptor: " + primitive);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 10908f0..7d3bc33 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -54,7 +54,6 @@
 
   static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
-  static final DexType[] EMPTY_TYPE_ARRAY = new DexType[0];
   static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
 
   final IRConverter converter;
@@ -103,20 +102,17 @@
     DexType objectArrayType = factory.createType(OBJECT_ARRAY_TYPE_DESCR);
 
     this.metafactoryMethod = factory.createMethod(metafactoryType,
-        factory.createProto(callSiteType, new DexType[] {
-            lookupType, factory.stringType, methodTypeType,
-            methodTypeType, methodHandleType, methodTypeType
-        }),
+        factory.createProto(callSiteType, lookupType, factory.stringType, methodTypeType,
+            methodTypeType, methodHandleType, methodTypeType),
         factory.createString(METAFACTORY_METHOD_NAME));
 
     this.metafactoryAltMethod = factory.createMethod(metafactoryType,
-        factory.createProto(callSiteType, new DexType[] {
-            lookupType, factory.stringType, methodTypeType, objectArrayType
-        }),
+        factory.createProto(callSiteType, lookupType, factory.stringType, methodTypeType,
+            objectArrayType),
         factory.createString(METAFACTORY_ALT_METHOD_NAME));
 
     this.constructorName = factory.createString(Constants.INSTANCE_INITIALIZER_NAME);
-    DexProto initProto = factory.createProto(factory.voidType, EMPTY_TYPE_ARRAY);
+    DexProto initProto = factory.createProto(factory.voidType);
     this.objectInitMethod = factory.createMethod(factory.objectType, initProto, constructorName);
     this.classConstructorName = factory.createString(Constants.CLASS_INITIALIZER_NAME);
     this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
@@ -124,7 +120,7 @@
 
     this.deserializeLambdaMethodName = factory.createString(DESERIALIZE_LAMBDA_METHOD_NAME);
     this.deserializeLambdaMethodProto = factory.createProto(
-        factory.objectType, new DexType[] { factory.createType(SERIALIZED_LAMBDA_TYPE_DESCR) });
+        factory.objectType, factory.createType(SERIALIZED_LAMBDA_TYPE_DESCR));
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 04f6786..3968c9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -55,14 +55,15 @@
   private List<DexProgramClass> getNewProgramClasses(List<DexProgramClass> classes) {
     List<DexProgramClass> newClasses = new ArrayList<>();
     for (DexProgramClass clazz : classes) {
-      if (!appInfo.liveTypes.contains(clazz.type) && !options.debugKeepRules) {
+      if (!appInfo.liveTypes.contains(clazz.type)) {
         // The class is completely unused and we can remove it.
         if (Log.ENABLED) {
           Log.debug(getClass(), "Removing class: " + clazz);
         }
       } else {
         newClasses.add(clazz);
-        if (!appInfo.instantiatedTypes.contains(clazz.type) && !options.debugKeepRules) {
+        if (!appInfo.instantiatedTypes.contains(clazz.type) &&
+            (!options.debugKeepRules || !hasDefaultConstructor(clazz))) {
           // The class is only needed as a type but never instantiated. Make it abstract to reflect
           // this.
           if (clazz.accessFlags.isFinal()) {
@@ -86,6 +87,15 @@
     return newClasses;
   }
 
+  private boolean hasDefaultConstructor(DexProgramClass clazz) {
+    for (DexEncodedMethod method : clazz.directMethods()) {
+      if (isDefaultConstructor(method)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
       T[] items, Set<S> live) {
     for (int i = 0; i < items.length; i++) {
@@ -96,6 +106,11 @@
     return -1;
   }
 
+  private boolean isDefaultConstructor(DexEncodedMethod method) {
+    return method.accessFlags.isConstructor() && !method.accessFlags.isStatic()
+        && method.method.proto.parameters.isEmpty();
+  }
+
   private DexEncodedMethod[] reachableMethods(DexEncodedMethod[] methods, DexClass clazz) {
     int firstUnreachable = firstUnreachableIndex(methods, appInfo.liveMethods);
     // Return the original array if all methods are used.
@@ -109,7 +124,7 @@
     for (int i = firstUnreachable; i < methods.length; i++) {
       if (appInfo.liveMethods.contains(methods[i].getKey())) {
         reachableMethods.add(methods[i]);
-      } else if (options.debugKeepRules) {
+      } else if (options.debugKeepRules && isDefaultConstructor(methods[i])) {
         // Keep the method but rewrite its body, if it has one.
         reachableMethods.add(methods[i].accessFlags.isAbstract()
             ? methods[i]