blob: 6a1e0592a071b0c17d26f1bec0a59ae613c6cb44 [file] [log] [blame]
// 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 com.android.tools.r8.compatproguard.reflection;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
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;
import java.lang.reflect.Method;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
class A {
public void method0() {
System.out.print(0);
}
public void method1(String s) {
System.out.print(s);
}
public void method2(String s1, String s2) {
System.out.print(s1 + s2);
}
public void method3(int i1, int i2) {
System.out.print(i1 + i2);
}
}
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);
// The call with in-exact argument to getMethod is intended and should stay.
// The warning cannot be suppressed according to b/117198454.
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);
m.invoke(a, "2", "3");
m = A.class.getDeclaredMethod("method0");
m.invoke(a);
m = A.class.getDeclaredMethod("method1", String.class);
m.invoke(a, "1");
m = A.class.getDeclaredMethod("method2", String.class, String.class);
m.invoke(a, "2", "3");
m = A.class.getDeclaredMethod("method3", int.class, int.class);
m.invoke(a, 2, 2);
try {
m = A.class.getMethod("method0");
m.invoke(a);
m = A.class.getMethod("method1", String.class);
m.invoke(a, "1");
m = A.class.getMethod("method2", String.class, String.class);
m.invoke(a, "2", "3");
m = A.class.getDeclaredMethod("method0");
m.invoke(a);
m = A.class.getDeclaredMethod("method1", String.class);
m.invoke(a, "1");
m = A.class.getDeclaredMethod("method2", String.class, String.class);
m.invoke(a, "2", "3");
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];
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[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");
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);
} 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);
}
}
}
@RunWith(Parameterized.class)
public class ReflectionTest extends TestBase {
private Backend backend;
@Parameterized.Parameters(name = "Backend: {0}")
public static Backend[] data() {
return ToolHelper.getBackends();
}
public ReflectionTest(Backend backend) {
this.backend = backend;
}
@Test
public void test() throws Exception {
Class<?> mainClass = MainTest.class;
AndroidApp output =
compileWithR8(
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")),
isRenamed());
assertThat(
inspector
.clazz(A.class)
.method("void", "method2", ImmutableList.of("java.lang.String", "java.lang.String")),
isRenamed());
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));
}
}