Improve the partial evaluation of arguments to reflective calls

The previous implementation had a number of issues, which have
been addressed:

* Always match the prototype argument types
* Handle primitive type class values
* Handle chains of casts of the array. The main use case for this
  is casting null like this: (Class<?>[]) null
* Track side effects on all these check-cast aliases
* Reset all values when noticing side efects
* Handle phi values (just bail out for now)

Bug: 116078230
Change-Id: I0e82e4f6a87dd11a79a819305388ce2754af8004
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 403790c..b02222f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -192,6 +192,7 @@
   public final DexType stringArrayType = createType(stringArrayDescriptor);
   public final DexType objectType = createType(objectDescriptor);
   public final DexType objectArrayType = createType(objectArrayDescriptor);
+  public final DexType classArrayType = createType(classArrayDescriptor);
   public final DexType enumType = createType(enumDescriptor);
   public final DexType annotationType = createType(annotationDescriptor);
   public final DexType throwableType = createType(throwableDescriptor);
@@ -214,6 +215,8 @@
   public final LongMethods longMethods = new LongMethods();
   public final ThrowableMethods throwableMethods = new ThrowableMethods();
   public final ClassMethods classMethods = new ClassMethods();
+  public final PrimitiveTypesBoxedTypeFields primitiveTypesBoxedTypeFields =
+      new PrimitiveTypesBoxedTypeFields();
   public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
       new AtomicFieldUpdaterMethods();
   public final Kotlin kotlin;
@@ -403,8 +406,54 @@
   }
 
   /**
+   * All boxed types (Boolean, Byte, ...) have a field named TYPE which contains the Class object
+   * for the primitive type.
+   *
+   * E.g. for Boolean https://docs.oracle.com/javase/8/docs/api/java/lang/Boolean.html#TYPE.
+   */
+  public class PrimitiveTypesBoxedTypeFields {
+    public DexField booleanTYPE;
+    public DexField byteTYPE;
+    public DexField charTYPE;
+    public DexField shortTYPE;
+    public DexField intTYPE;
+    public DexField longTYPE;
+    public DexField floatTYPE;
+    public DexField doubleTYPE;
+
+    private final Map<DexField, DexType> boxedFieldTypeToPrimitiveType;
+
+    private PrimitiveTypesBoxedTypeFields() {
+      booleanTYPE = createField(boxedBooleanType, classType, "TYPE");
+      byteTYPE = createField(boxedByteType, classType, "TYPE");
+      charTYPE = createField(boxedCharType, classType, "TYPE");
+      shortTYPE = createField(boxedShortType, classType, "TYPE");
+      intTYPE = createField(boxedIntType, classType, "TYPE");
+      longTYPE = createField(boxedLongType, classType, "TYPE");
+      floatTYPE = createField(boxedFloatType, classType, "TYPE");
+      doubleTYPE = createField(boxedDoubleType, classType, "TYPE");
+
+      boxedFieldTypeToPrimitiveType =
+          ImmutableMap.<DexField, DexType>builder()
+              .put(booleanTYPE, booleanType)
+              .put(byteTYPE, byteType)
+              .put(charTYPE, charType)
+              .put(shortTYPE, shortType)
+              .put(intTYPE, intType)
+              .put(longTYPE, longType)
+              .put(floatTYPE, floatType)
+              .put(doubleTYPE, doubleType)
+              .build();
+    }
+
+    public DexType boxedFieldTypeToPrimitiveType(DexField field) {
+      return boxedFieldTypeToPrimitiveType.get(field);
+    }
+  }
+
+  /**
    * A class that encompasses methods that create different types of atomic field updaters:
-   *   Atomic(Integer|Long|Reference)FieldUpdater#newUpdater.
+   * Atomic(Integer|Long|Reference)FieldUpdater#newUpdater.
    */
   public class AtomicFieldUpdaterMethods {
     public DexMethod intUpdater;
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 5128b96..3d1539e 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -18,13 +18,15 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstInstruction;
+import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.Value;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -187,7 +189,11 @@
       itemBasedString = inferFieldInHolder(appInfo, holder, dexString.toString(), null);
     } else {
       assert numOfParams == 3;
-      DexTypeList arguments = retrieveDexTypeListFromClassList(invoke, ins.get(2));
+      DexTypeList arguments =
+          retrieveDexTypeListFromClassList(invoke, ins.get(2), appInfo.dexItemFactory);
+      if (arguments == null) {
+        return null;
+      }
       itemBasedString = inferMethodInHolder(appInfo, holder, dexString.toString(), arguments);
     }
     return itemBasedString;
@@ -246,7 +252,7 @@
     DexItemBasedString itemBasedString =
         inferFieldInHolder(appInfo, holder, memberIdentifier, null);
     if (itemBasedString == null) {
-      itemBasedString = inferMethodInHolder(appInfo, holder, memberIdentifier, null);
+      itemBasedString = inferMethodNameInHolder(appInfo, holder, memberIdentifier);
     }
     return itemBasedString;
   }
@@ -273,20 +279,18 @@
     return itemBasedString;
   }
 
-  private static DexItemBasedString inferMethodInHolder(
-      AppInfo appInfo, DexClass holder, String name, DexTypeList arguments) {
+  private static DexItemBasedString inferMethodNameInHolder(
+      AppInfo appInfo, DexClass holder, String name) {
     DexItemBasedString itemBasedString = null;
     for (DexEncodedMethod encodedMethod : holder.directMethods()) {
-      if (encodedMethod.method.name.toString().equals(name)
-          && (arguments == null || encodedMethod.method.proto.parameters.equals(arguments))) {
+      if (encodedMethod.method.name.toString().equals(name)) {
         itemBasedString = appInfo.dexItemFactory.createItemBasedString(encodedMethod.method);
         break;
       }
     }
     if (itemBasedString == null) {
       for (DexEncodedMethod encodedMethod : holder.virtualMethods()) {
-        if (encodedMethod.method.name.toString().equals(name)
-            && (arguments == null || encodedMethod.method.proto.parameters.equals(arguments))) {
+        if (encodedMethod.method.name.toString().equals(name)) {
           itemBasedString = appInfo.dexItemFactory.createItemBasedString(encodedMethod.method);
           break;
         }
@@ -295,13 +299,59 @@
     return itemBasedString;
   }
 
-  // Perform a conservative evaluation of the constant content of an array from its construction
+  private static DexItemBasedString inferMethodInHolder(
+      AppInfo appInfo, DexClass holder, String name, DexTypeList arguments) {
+    assert arguments != null;
+    DexItemBasedString itemBasedString = null;
+    for (DexEncodedMethod encodedMethod : holder.directMethods()) {
+      if (encodedMethod.method.name.toString().equals(name)
+          && encodedMethod.method.proto.parameters.equals(arguments)) {
+        itemBasedString = appInfo.dexItemFactory.createItemBasedString(encodedMethod.method);
+        break;
+      }
+    }
+    if (itemBasedString == null) {
+      for (DexEncodedMethod encodedMethod : holder.virtualMethods()) {
+        if (encodedMethod.method.name.toString().equals(name)
+            && encodedMethod.method.proto.parameters.equals(arguments)) {
+          itemBasedString = appInfo.dexItemFactory.createItemBasedString(encodedMethod.method);
+          break;
+        }
+      }
+    }
+    return itemBasedString;
+  }
+
+  private static DexType getTypeFromConstClassOrBoxedPrimitive(
+      Value value, DexItemFactory factory) {
+    if (value.isPhi()) {
+      return null;
+    }
+    if (value.isConstant() && value.getConstInstruction().isConstClass()) {
+      return value.getConstInstruction().asConstClass().getValue();
+    }
+    if (value.definition.isStaticGet()) {
+      return factory.primitiveTypesBoxedTypeFields.boxedFieldTypeToPrimitiveType(
+          value.definition.asStaticGet().getField());
+    }
+    return null;
+  }
+
+  // Perform a conservative evaluation of an array content of dex type values from its construction
   // until its use at a given instruction.
-  private static ConstInstruction[] evaluateConstArrayContentFromConstructionToUse(
-      NewArrayEmpty newArray, int size, Instruction user) {
-    ConstInstruction[] values = new ConstInstruction[size];
+  private static DexType[] evaluateTypeArrayContentFromConstructionToUse(
+      NewArrayEmpty newArray,
+      List<CheckCast> aliases,
+      int size,
+      Instruction user,
+      DexItemFactory factory) {
+    DexType[] values = new DexType[size];
     int remaining = size;
-    Set<Instruction> users = newArray.outValue().uniqueUsers();
+    Set<Instruction> users = Sets.newIdentityHashSet();
+    users.addAll(newArray.outValue().uniqueUsers());
+    for (CheckCast alias : aliases) {
+      users.addAll(alias.outValue().uniqueUsers());
+    }
     // Follow the path from the array construction to the requested use collecting the constants
     // put into the array. Conservatively bail out if the content of the array cannot be statically
     // computed.
@@ -320,25 +370,33 @@
           // the content was requested.
           return remaining == 0 ? values : null;
         }
-        // Any other kinds of use besides array-put mean that the array escapes and could be
-        // altered.
+        // Any other kinds of use besides array-put mean that the array escapes and its content
+        // could be altered.
         if (!instruction.isArrayPut()) {
-          return null;
+          if (instruction.isCheckCast() && aliases.contains(instruction.asCheckCast())) {
+            continue;
+          }
+          values = new DexType[size];
+          remaining = size;
+          continue;
         }
         ArrayPut arrayPut = instruction.asArrayPut();
-        if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
+        if (!arrayPut.index().isConstNumber()) {
           return null;
         }
         int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
         if (index < 0 || index >= values.length) {
           return null;
         }
+        DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayPut.value(), factory);
+        if (type == null) {
+          return null;
+        }
         // Allow several writes to the same array element.
         if (values[index] == null) {
           remaining--;
         }
-        ConstInstruction value = arrayPut.value().getConstInstruction();
-        values[index] = value;
+        values[index] = type;
       }
       if (!block.exit().isGoto()) {
         return null;
@@ -364,7 +422,7 @@
    * @return a list of {@link DexType} that corresponds to const class in {@param classListValue}
    */
   private static DexTypeList retrieveDexTypeListFromClassList(
-      InvokeMethod invoke, Value classListValue) {
+      InvokeMethod invoke, Value classListValue, DexItemFactory factory) {
 
     // The code
     //   A.class.getMethod("m", String.class, String.class)
@@ -385,8 +443,31 @@
     // INVOKEVIRTUAL java/lang/Class.getMethod \
     //     (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
 
+    // Besides the code pattern above this supports a series of check-cast instructions. e.g.:
+    //
+    // A.class.getMethod("name", (Class<?>[]) new Class<?>[]{String.class})
+
+    List<CheckCast> aliases = new ArrayList<>();
+    if (!classListValue.isPhi()
+        && classListValue.definition.isCheckCast()
+        && classListValue.definition.asCheckCast().getType() == factory.classArrayType) {
+      while (!classListValue.isPhi() && classListValue.definition.isCheckCast()) {
+        aliases.add(classListValue.definition.asCheckCast());
+        classListValue = classListValue.definition.asCheckCast().object();
+      }
+    }
+    if (classListValue.isPhi()) {
+      return null;
+    }
+
+    // A null argument list is an empty argument list
+    if (classListValue.isZero()) {
+      return DexTypeList.empty();
+    }
+
     // Make sure this Value refers to a new array.
-    if (!classListValue.definition.isNewArrayEmpty()) {
+    if (!classListValue.definition.isNewArrayEmpty()
+        || !classListValue.definition.asNewArrayEmpty().size().isConstant()) {
       return null;
     }
 
@@ -402,20 +483,13 @@
       return DexTypeList.empty();
     }
 
-    ConstInstruction[] arrayContent =
-        evaluateConstArrayContentFromConstructionToUse(
-            classListValue.definition.asNewArrayEmpty(), size, invoke);
+    DexType[] arrayContent =
+        evaluateTypeArrayContentFromConstructionToUse(
+            classListValue.definition.asNewArrayEmpty(), aliases, size, invoke, factory);
 
     if (arrayContent == null) {
       return null;
     }
-    DexType[] types = new DexType[size];
-    for (int i = 0; i < size; i++) {
-      if (!arrayContent[i].isConstClass()) {
-        return null;
-      }
-      types[i] = arrayContent[i].asConstClass().getValue();
-    }
-    return new DexTypeList(types);
+    return new DexTypeList(arrayContent);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
index c90e0fb..c86711f 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
@@ -8,7 +8,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -38,13 +42,22 @@
   }
 }
 
-class Main {
+class MainTest {
+  @SuppressWarnings("warning")
   public static void main(String[] args) throws Exception {
     A a = new A();
 
     Method m;
     m = A.class.getMethod("method0");
     m.invoke(a);
+    m = A.class.getMethod("method0", null);
+    m.invoke(a);
+    m = A.class.getMethod("method0", (Class<?>[]) null);
+    m.invoke(a);
+    m = A.class.getMethod("method0", (Class<?>[]) (Class<?>[]) null);
+    m.invoke(a);
+    m = A.class.getMethod("method0", (Class<?>[]) (Object[]) (Class<?>[]) null);
+    m.invoke(a);
     m = A.class.getMethod("method1", String.class);
     m.invoke(a, "1");
     m = A.class.getMethod("method2", String.class, String.class);
@@ -74,34 +87,49 @@
       m = A.class.getDeclaredMethod("method3", int.class, int.class);
       m.invoke(a, 2, 2);
     } catch (Exception e) {
+      System.out.println("Unexpected: " + e);
     }
 
-    Class[] argumentTypes;
-    argumentTypes = new Class[2];
+    Class<?>[] argumentTypes;
+    argumentTypes = new Class<?>[2];
     argumentTypes[1] = int.class;
     argumentTypes[0] = int.class;
     argumentTypes[0] = String.class;
     argumentTypes[1] = String.class;
     m = A.class.getDeclaredMethod("method2", argumentTypes);
     m.invoke(a, "2", "3");
+    argumentTypes[0] = String.class;
+    argumentTypes[1] = String.class;
     m = A.class.getDeclaredMethod("method2", argumentTypes);
     m.invoke(a, "4", "5");
+    argumentTypes[0] = String.class;
+    argumentTypes[1] = String.class;
+    m = A.class.getDeclaredMethod("method2", (Class<?>[]) argumentTypes);
+    m.invoke(a, "5", "4");
+    argumentTypes[0] = String.class;
+    argumentTypes[1] = String.class;
+    m = A.class.getDeclaredMethod("method2", (Class<?>[]) (Object[]) argumentTypes);
+    m.invoke(a, "5", "4");
 
     argumentTypes[1] = int.class;
     argumentTypes[0] = int.class;
     m = A.class.getDeclaredMethod("method3", argumentTypes);
     m.invoke(a, 3, 3);
+    argumentTypes[1] = int.class;
+    argumentTypes[0] = int.class;
     m = A.class.getDeclaredMethod("method3", argumentTypes);
     m.invoke(a, 3, 4);
 
     try {
-      argumentTypes = new Class[2];
+      argumentTypes = new Class<?>[2];
       argumentTypes[1] = int.class;
       argumentTypes[0] = int.class;
       argumentTypes[0] = String.class;
       argumentTypes[1] = String.class;
       m = A.class.getDeclaredMethod("method2", argumentTypes);
       m.invoke(a, "2", "3");
+      argumentTypes[0] = String.class;
+      argumentTypes[1] = String.class;
       m = A.class.getDeclaredMethod("method2", argumentTypes);
       m.invoke(a, "4", "7");
 
@@ -109,9 +137,311 @@
       argumentTypes[0] = int.class;
       m = A.class.getDeclaredMethod("method3", argumentTypes);
       m.invoke(a, 3, 3);
+      argumentTypes[1] = int.class;
+      argumentTypes[0] = int.class;
       m = A.class.getDeclaredMethod("method3", argumentTypes);
       m.invoke(a, 3, 4);
     } catch (Exception e) {
+      System.out.println("Unexpected: " + e);
+    }
+  }
+}
+
+class MainNonConstArraySize {
+  public static void main(String[] args) throws Exception {
+    Method m;
+    m = A.class.getMethod("method0");
+    m.invoke(new A());
+
+    nonConstArraySize(0);
+  }
+
+  @NeverInline
+  static void nonConstArraySize(int argumentTypesSize) {
+    try {
+      A a = new A();
+
+      Method m;
+      Class<?>[] argumentTypes;
+      argumentTypes = new Class<?>[argumentTypesSize];
+      m = A.class.getDeclaredMethod("method0", argumentTypes);
+      m.invoke(a);
+    } catch (Exception e) {
+      // Prepend the 0 output to the exception name to make it easier to compare the result with
+      // the reference run.
+      System.out.print("0" + e.getClass().getCanonicalName());
+    }
+  }
+}
+
+class MainPhiValue {
+  public static void main(String[] args) throws Exception {
+    Method m;
+    m = A.class.getMethod("method0");
+    m.invoke(new A());
+    m = A.class.getMethod("method1", String.class);
+    m.invoke(new A(), "1");
+
+    try {
+      arrayIsPhi(true);
+      throw new Exception("Unexpected");
+    } catch (NoSuchMethodException e) {
+      // Expected.
+    }
+
+    try {
+      elementIsPhi(true);
+      throw new Exception("Unexpected");
+    } catch (NoSuchMethodException e) {
+      // Expected.
+    }
+
+    try {
+      elementIsPhiButDeterministic(true, "");
+      throw new Exception("Unexpected");
+    } catch (NoSuchMethodException e) {
+      // Expected.
+    }
+  }
+
+  @NeverInline
+  static void arrayIsPhi(boolean b) throws Exception {
+    A a = new A();
+
+    Method m;
+    Class<?>[] argumentTypes;
+    if (b) {
+      argumentTypes = new Class<?>[1];
+      argumentTypes[0] = String.class;
+    } else {
+      argumentTypes = new Class<?>[1];
+      argumentTypes[0] = int.class;
+    }
+    m = A.class.getDeclaredMethod("method1", argumentTypes);
+    m.invoke(a, "0");
+  }
+
+  @NeverInline
+  static void elementIsPhi(boolean b) throws Exception {
+    A a = new A();
+
+    Class<?> x;
+    x = b ? String.class : int.class;
+    Method m;
+    Class<?>[] argumentTypes = new Class<?>[1];
+    argumentTypes[0] = x;
+    m = A.class.getDeclaredMethod("method1", argumentTypes);
+    m.invoke(a, "0");
+  }
+
+  @NeverInline
+  static void elementIsPhiButDeterministic(boolean b, String arg) throws Exception {
+    A a = new A();
+
+    Class<?> x;
+    x = b ? String.class : arg.getClass();
+    Method m;
+    Class<?>[] argumentTypes = new Class<?>[1];
+    argumentTypes[0] = x;
+    m = A.class.getDeclaredMethod("method1", argumentTypes);
+    m.invoke(a, "0");
+  }
+}
+
+class AllPrimitiveTypes {
+  public void method(boolean b) {
+    System.out.print(b);
+  }
+
+  public void method(byte b) {
+    System.out.print(b);
+  }
+
+  public void method(char c) {
+    System.out.print(c);
+  }
+
+  public void method(short s) {
+    System.out.print(s);
+  }
+
+  public void method(int i) {
+    System.out.print(i);
+  }
+
+  public void method(long l) {
+    System.out.print(l);
+  }
+
+  public void method(float f) {
+    System.out.print(f);
+  }
+
+  public void method(double d) {
+    System.out.print(d);
+  }
+}
+
+class MainAllPrimitiveTypes {
+
+  public static void main(String[] args) throws Exception {
+    AllPrimitiveTypes a = new AllPrimitiveTypes();
+
+    Method m;
+    Class<?>[] argumentTypes;
+    argumentTypes = new Class<?>[1];
+    argumentTypes[0] = boolean.class;
+    m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, true);
+    argumentTypes[0] = byte.class;
+    m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, (byte) 0);
+    argumentTypes[0] = char.class;
+    m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 'a');
+    argumentTypes[0] = short.class;
+    m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, (short) 1);
+    argumentTypes[0] = int.class;
+    m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 2);
+    argumentTypes[0] = long.class;
+    m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 3L);
+    argumentTypes[0] = float.class;
+    m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 4.4f);
+    argumentTypes[0] = double.class;
+    m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 5.5d);
+
+    try {
+      argumentTypes = new Class<?>[1];
+      argumentTypes[0] = boolean.class;
+      m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, true);
+      argumentTypes[0] = byte.class;
+      m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, (byte) 0);
+      argumentTypes[0] = char.class;
+      m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 'a');
+      argumentTypes[0] = short.class;
+      m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, (short) 1);
+      argumentTypes[0] = int.class;
+      m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 2);
+      argumentTypes[0] = long.class;
+      m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 3L);
+      argumentTypes[0] = float.class;
+      m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 4.4f);
+      argumentTypes[0] = double.class;
+      m = AllPrimitiveTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 5.5d);
+    } catch (Exception e) {
+      System.out.println("Unexpected: " + e);
+    }
+  }
+}
+
+class AllBoxedTypes {
+  public void method(Boolean b) {
+    System.out.print(b);
+  }
+
+  public void method(Byte b) {
+    System.out.print(b);
+  }
+
+  public void method(Character c) {
+    System.out.print(c);
+  }
+
+  public void method(Short s) {
+    System.out.print(s);
+  }
+
+  public void method(Integer i) {
+    System.out.print(i);
+  }
+
+  public void method(Long l) {
+    System.out.print(l);
+  }
+
+  public void method(Float f) {
+    System.out.print(f);
+  }
+
+  public void method(Double d) {
+    System.out.print(d);
+  }
+}
+
+class MainAllBoxedTypes {
+
+  public static void main(String[] args) throws Exception {
+    AllBoxedTypes a = new AllBoxedTypes();
+
+    Method m;
+    Class<?>[] argumentTypes;
+    argumentTypes = new Class<?>[1];
+    argumentTypes[0] = Boolean.class;
+    m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, true);
+    argumentTypes[0] = Byte.class;
+    m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, (byte) 0);
+    argumentTypes[0] = Character.class;
+    m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 'a');
+    argumentTypes[0] = Short.class;
+    m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, (short) 1);
+    argumentTypes[0] = Integer.class;
+    m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 2);
+    argumentTypes[0] = Long.class;
+    m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 3L);
+    argumentTypes[0] = Float.class;
+    m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 4.4f);
+    argumentTypes[0] = Double.class;
+    m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+    m.invoke(a, 5.5d);
+
+    try {
+      argumentTypes = new Class<?>[1];
+      argumentTypes[0] = Boolean.class;
+      m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, true);
+      argumentTypes[0] = Byte.class;
+      m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, (byte) 0);
+      argumentTypes[0] = Character.class;
+      m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 'a');
+      argumentTypes[0] = Short.class;
+      m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, (short) 1);
+      argumentTypes[0] = Integer.class;
+      m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 2);
+      argumentTypes[0] = Long.class;
+      m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 3L);
+      argumentTypes[0] = Float.class;
+      m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 4.4f);
+      argumentTypes[0] = Double.class;
+      m = AllBoxedTypes.class.getDeclaredMethod("method", argumentTypes);
+      m.invoke(a, 5.5d);
+    } catch (Exception e) {
+      System.out.println("Unexpected: " + e);
     }
   }
 }
@@ -132,10 +462,12 @@
 
   @Test
   public void test() throws Exception {
+    Class<?> mainClass = MainTest.class;
     AndroidApp output =
         compileWithR8(
-            readClasses(A.class, Main.class), keepMainProguardConfiguration(Main.class), backend);
+            readClasses(A.class, mainClass), keepMainProguardConfiguration(mainClass), backend);
     CodeInspector inspector = new CodeInspector(output);
+
     assertThat(inspector.clazz(A.class).method("void", "method0", ImmutableList.of()), isRenamed());
     assertThat(
         inspector.clazz(A.class).method("void", "method1", ImmutableList.of("java.lang.String")),
@@ -146,6 +478,88 @@
             .method("void", "method2", ImmutableList.of("java.lang.String", "java.lang.String")),
         isRenamed());
 
-    assertEquals(runOnJava(Main.class), runOnVM(output, Main.class, backend));
+    assertEquals(runOnJava(mainClass), runOnVM(output, mainClass, backend));
+  }
+
+  @Test
+  public void testNonConstArraySize() throws Exception {
+    Class<?> mainClass = MainNonConstArraySize.class;
+    R8Command.Builder builder =
+        ToolHelper.prepareR8CommandBuilder(
+                readClasses(A.class, mainClass, NeverInline.class), emptyConsumer(backend))
+            .addLibraryFiles(runtimeJar(backend));
+    builder.addProguardConfiguration(
+        ImmutableList.of(keepMainProguardConfigurationWithInliningAnnotation(mainClass)),
+        Origin.unknown());
+    ToolHelper.allowTestProguardOptions(builder);
+    AndroidApp output = ToolHelper.runR8(builder.build());
+    CodeInspector inspector = new CodeInspector(output);
+
+    assertThat(inspector.clazz(A.class).method("void", "method0", ImmutableList.of()), isRenamed());
+
+    // The reference run on the Java VM will succeed, whereas the run on the R8 output will fail
+    // as in this test we fail to recognize the reflective call. To compare the output of the
+    // successful reference run append "java.lang.NoSuchMethodException" to it.
+    assertEquals(
+        runOnJava(mainClass) + "java.lang.NoSuchMethodException",
+        runOnVM(output, mainClass, backend));
+  }
+
+  @Test
+  public void testPhiValue() throws Exception {
+    Class<?> mainClass = MainPhiValue.class;
+    R8Command.Builder builder =
+        ToolHelper.prepareR8CommandBuilder(
+                readClasses(A.class, mainClass, NeverInline.class), emptyConsumer(backend))
+            .addLibraryFiles(runtimeJar(backend));
+    builder.addProguardConfiguration(
+        ImmutableList.of(keepMainProguardConfigurationWithInliningAnnotation(mainClass)),
+        Origin.unknown());
+    ToolHelper.allowTestProguardOptions(builder);
+    AndroidApp output = ToolHelper.runR8(builder.build(), o -> o.enableInlining = false);
+
+    runOnVM(output, mainClass, backend);
+  }
+
+  @Test
+  public void testAllPrimitiveTypes() throws Exception {
+    Class<?> mainClass = MainAllPrimitiveTypes.class;
+    AndroidApp output =
+        compileWithR8(
+            readClasses(AllPrimitiveTypes.class, mainClass),
+            keepMainProguardConfiguration(mainClass),
+            backend);
+
+    new CodeInspector(output)
+        .clazz(AllPrimitiveTypes.class)
+        .forAllMethods(
+            m -> {
+              if (!m.isInstanceInitializer()) {
+                assertThat(m, isRenamed());
+              }
+            });
+
+    assertEquals(runOnJava(mainClass), runOnVM(output, mainClass, backend));
+  }
+
+  @Test
+  public void testAllBoxedTypes() throws Exception {
+    Class<?> mainClass = MainAllBoxedTypes.class;
+    AndroidApp output =
+        compileWithR8(
+            readClasses(AllBoxedTypes.class, mainClass),
+            keepMainProguardConfiguration(mainClass),
+            backend);
+
+    new CodeInspector(output)
+        .clazz(AllBoxedTypes.class)
+        .forAllMethods(
+            m -> {
+              if (!m.isInstanceInitializer()) {
+                assertThat(m, isRenamed());
+              }
+            });
+
+    assertEquals(runOnJava(mainClass), runOnVM(output, mainClass, backend));
   }
 }