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));
}
}