| // 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.isPresentAndRenamed; |
| 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()), |
| isPresentAndRenamed()); |
| assertThat( |
| inspector.clazz(A.class).method("void", "method1", ImmutableList.of("java.lang.String")), |
| isPresentAndRenamed()); |
| assertThat( |
| inspector |
| .clazz(A.class) |
| .method("void", "method2", ImmutableList.of("java.lang.String", "java.lang.String")), |
| isPresentAndRenamed()); |
| |
| 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()), |
| isPresentAndRenamed()); |
| |
| // 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, isPresentAndRenamed()); |
| } |
| }); |
| |
| 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, isPresentAndRenamed()); |
| } |
| }); |
| |
| assertEquals(runOnJava(mainClass), runOnVM(output, mainClass, backend)); |
| } |
| } |