| // 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.ir.desugar.annotations; |
| |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.AsmTestBase; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import java.util.Collections; |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| public class CovariantReturnTypeAnnotationTransformerTest extends AsmTestBase { |
| public static final String PACKAGE_NAME = "com/android/tools/r8/ir/desugar/annotations"; |
| public static final String CRT_BINARY_NAME = "dalvik/annotation/codegen/CovariantReturnType"; |
| public static final String CRTS_INNER_NAME = "CovariantReturnTypes"; |
| public static final String CRTS_BINARY_NAME = CRT_BINARY_NAME + "$" + CRTS_INNER_NAME; |
| |
| public static final String CRT_TYPE_NAME = CRT_BINARY_NAME.replace('/', '.'); |
| public static final String CRTS_TYPE_NAME = CRT_BINARY_NAME.replace('/', '.'); |
| |
| @Test |
| public void testVersion1WithClient1And2() throws Exception { |
| AndroidApp input = |
| buildAndroidApp( |
| ToolHelper.getClassAsBytes(Client.class), |
| ToolHelper.getClassAsBytes(A.class), |
| ToolHelper.getClassAsBytes(B.class), |
| ToolHelper.getClassAsBytes(C.class)); |
| |
| // Version 1 does not contain annotations. |
| checkPresenceOfCovariantAnnotations(input, false); |
| |
| // Version 1 of the library should always work. |
| succeedsIndependentOfFlag(input, false); |
| } |
| |
| @Test |
| public void testVersion1WithClient3() throws Exception { |
| AndroidApp input = |
| buildAndroidApp( |
| com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(), |
| ToolHelper.getClassAsBytes(A.class), |
| ToolHelper.getClassAsBytes(B.class), |
| ToolHelper.getClassAsBytes(C.class)); |
| |
| // Version 1 does not contain annotations. |
| checkPresenceOfCovariantAnnotations(input, false); |
| |
| // There will be no methods with the signature "L.../B;->method()L.../B;" and |
| // "L.../C;->method()L.../C;". |
| failsIndependentOfFlag(input); |
| } |
| |
| @Test |
| public void testVersion2WithClient1And2() throws Exception { |
| AndroidApp input = |
| buildAndroidApp( |
| ToolHelper.getClassAsBytes(Client.class), |
| ToolHelper.getClassAsBytes(A.class), |
| com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(), |
| com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump()); |
| |
| // Version 2 contains annotations. |
| checkPresenceOfCovariantAnnotations(input, true); |
| |
| // Version 2 of the library should always work. |
| succeedsIndependentOfFlag(input, true); |
| } |
| |
| @Test |
| public void testVersion2WithClient3() throws Exception { |
| AndroidApp input = |
| buildAndroidApp( |
| com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(), |
| ToolHelper.getClassAsBytes(A.class), |
| com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(), |
| com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump()); |
| |
| // Version 2 contains annotations. |
| checkPresenceOfCovariantAnnotations(input, true); |
| |
| // If CovariantReturnType annotations are processed, then synthetic methods with the signatures |
| // "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;" will be added by D8. |
| succeedsWithOption(input, true, true); |
| |
| // If CovariantReturnType annotations are ignored, then there will be no methods with the |
| // signatures "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;". |
| failsWithOption(input, false); |
| } |
| |
| @Test |
| public void testVersion3WithClient3() throws Exception { |
| AndroidApp input = |
| buildAndroidApp( |
| com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(), |
| ToolHelper.getClassAsBytes(A.class), |
| com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(), |
| com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump()); |
| |
| // Version 3 does not contain annotations. |
| checkPresenceOfCovariantAnnotations(input, false); |
| |
| // Version 3 of the library should always work. |
| succeedsIndependentOfFlag(input, false); |
| } |
| |
| @Test |
| public void testVersion3WithClient1And2() throws Exception { |
| AndroidApp input = |
| buildAndroidApp( |
| ToolHelper.getClassAsBytes(Client.class), |
| ToolHelper.getClassAsBytes(A.class), |
| com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(), |
| com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump()); |
| |
| // Version 3 does not contain annotations. |
| checkPresenceOfCovariantAnnotations(input, false); |
| |
| // Version 3 of the library should always work with client 1. |
| succeedsIndependentOfFlag(input, false); |
| } |
| |
| @Test |
| public void testRepeatedCompilation() throws Exception { |
| AndroidApp input = |
| buildAndroidApp( |
| ToolHelper.getClassAsBytes(Client.class), |
| ToolHelper.getClassAsBytes(A.class), |
| com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(), |
| com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump()); |
| |
| // Version 2 contains annotations. |
| checkPresenceOfCovariantAnnotations(input, true); |
| |
| AndroidApp output = |
| compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = true); |
| |
| // Compilation output does not contain annotations. |
| checkPresenceOfCovariantAnnotations(output, false); |
| |
| // Compilation will fail with a compilation error the second time if the implementation does |
| // not remove the CovariantReturnType annotations properly during the first compilation. |
| compileWithD8(output, options -> options.processCovariantReturnTypeAnnotations = true); |
| } |
| |
| private void succeedsWithOption( |
| AndroidApp input, boolean option, boolean checkPresenceOfSyntheticMethods) throws Exception { |
| AndroidApp output = |
| compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option); |
| String stdout = runOnArt(output, Client.class.getCanonicalName()); |
| assertEquals(getExpectedOutput(), stdout); |
| checkPresenceOfCovariantAnnotations(output, false); |
| if (option && checkPresenceOfSyntheticMethods) { |
| checkPresenceOfSyntheticMethods(output); |
| } |
| } |
| |
| private void failsWithOption(AndroidApp input, boolean option) throws Exception { |
| AndroidApp output = |
| compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option); |
| checkPresenceOfCovariantAnnotations(output, false); |
| ToolHelper.ProcessResult result = runOnArtRaw(output, Client.class.getCanonicalName()); |
| assertThat(result.stderr, containsString("java.lang.NoSuchMethodError")); |
| } |
| |
| private void succeedsIndependentOfFlag(AndroidApp input, boolean checkPresenceOfSyntheticMethods) |
| throws Exception { |
| succeedsWithOption(input, true, checkPresenceOfSyntheticMethods); |
| succeedsWithOption(input, false, checkPresenceOfSyntheticMethods); |
| } |
| |
| private void failsIndependentOfFlag(AndroidApp input) throws Exception { |
| failsWithOption(input, true); |
| failsWithOption(input, false); |
| } |
| |
| private void checkPresenceOfCovariantAnnotations(AndroidApp app, boolean expected) |
| throws Exception { |
| CodeInspector inspector = new CodeInspector(app); |
| assertEquals( |
| expected, |
| inspector.allClasses().stream() |
| .anyMatch( |
| clazz -> |
| clazz.allMethods().stream() |
| .anyMatch(method -> method.annotation(CRTS_TYPE_NAME).isPresent()))); |
| } |
| |
| private void checkPresenceOfSyntheticMethods(AndroidApp output) throws Exception { |
| CodeInspector inspector = new CodeInspector(output); |
| |
| // Get classes A, B, and C. |
| ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName()); |
| assertThat(clazzA, isPresent()); |
| |
| ClassSubject clazzB = inspector.clazz(B.class.getCanonicalName()); |
| assertThat(clazzB, isPresent()); |
| |
| ClassSubject clazzC = inspector.clazz(C.class.getCanonicalName()); |
| assertThat(clazzC, isPresent()); |
| |
| // Check that the original methods are there, and that they are not synthetic. |
| MethodSubject methodA = |
| clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList()); |
| assertThat(methodA, isPresent()); |
| Assert.assertTrue(!methodA.getMethod().isSyntheticMethod()); |
| |
| MethodSubject methodB = |
| clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList()); |
| assertThat(methodB, isPresent()); |
| Assert.assertTrue(!methodB.getMethod().isSyntheticMethod()); |
| |
| MethodSubject methodC = |
| clazzC.method(A.class.getCanonicalName(), "method", Collections.emptyList()); |
| assertThat(methodC, isPresent()); |
| Assert.assertTrue(!methodC.getMethod().isSyntheticMethod()); |
| |
| // Check that a synthetic method has been added to class B. |
| MethodSubject methodB2 = |
| clazzB.method(B.class.getCanonicalName(), "method", Collections.emptyList()); |
| assertThat(methodB2, isPresent()); |
| Assert.assertTrue(methodB2.getMethod().isSyntheticMethod()); |
| |
| // Check that two synthetic methods have been added to class C. |
| MethodSubject methodC2 = |
| clazzC.method(B.class.getCanonicalName(), "method", Collections.emptyList()); |
| assertThat(methodC2, isPresent()); |
| Assert.assertTrue(methodC2.getMethod().isSyntheticMethod()); |
| |
| MethodSubject methodC3 = |
| clazzC.method(C.class.getCanonicalName(), "method", Collections.emptyList()); |
| assertThat(methodC3, isPresent()); |
| Assert.assertTrue(methodC3.getMethod().isSyntheticMethod()); |
| } |
| |
| private String getExpectedOutput() { |
| return "a=A\nb=B\nc=C\n"; |
| } |
| } |