Test behavior of annotations with injected methods
Change-Id: I4ecb23066740e572e098eef29c86eda6dff1a3fe
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 51d79bb..6c3ed35 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1481,6 +1481,10 @@
return clazz.getTypeName();
}
+ public static AndroidApiLevel apiLevelWithDefaultInterfaceMethodsSupport() {
+ return AndroidApiLevel.N;
+ }
+
public static AndroidApiLevel apiLevelWithInvokeCustomSupport() {
return AndroidApiLevel.O;
}
diff --git a/src/test/java/com/android/tools/r8/annotations/AnnotationWithInjectedMethodsTest.java b/src/test/java/com/android/tools/r8/annotations/AnnotationWithInjectedMethodsTest.java
new file mode 100644
index 0000000..0142bce
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/AnnotationWithInjectedMethodsTest.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2020, 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.annotations;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AnnotationWithInjectedMethodsTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimes()
+ .withApiLevelsStartingAtIncluding(apiLevelWithDefaultInterfaceMethodsSupport())
+ .build();
+ }
+
+ public AnnotationWithInjectedMethodsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getProgramClasses())
+ .addProgramClassFileData(getProgramClassFileData())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getProgramClasses())
+ .addProgramClassFileData(getProgramClassFileData())
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(AnnotationWithInjectedMethod.class)
+ .addKeepRuntimeVisibleAnnotations()
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ private String getExpectedOutput() {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ builder.add(
+ "Foo",
+ "Bar",
+ "Caught IncompleteAnnotationException: "
+ + typeName(AnnotationWithInjectedMethod.class)
+ + " missing element getInstanceData");
+ if (parameters.isCfRuntime()) {
+ builder.add("Caught AssertionError: Too many parameters for an annotation method");
+ } else {
+ builder.add(
+ "Caught IllegalArgumentException: Invalid method for annotation type: "
+ + "public "
+ + (parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0
+ ? ""
+ : "default ")
+ + typeName(Data.class)
+ + " "
+ + typeName(AnnotationWithInjectedMethod.class)
+ + ".getInstanceData("
+ + typeName(Data.class)
+ + ")");
+ }
+ return StringUtils.lines(builder.build());
+ }
+
+ private Collection<Class<?>> getProgramClasses() {
+ return ImmutableList.of(Data.class);
+ }
+
+ private Collection<byte[]> getProgramClassFileData() throws IOException {
+ return ImmutableList.of(
+ transformer(Main.class)
+ .replaceAnnotationDescriptor(
+ descriptor(ToBeAnnotationWithInjectedMethod.class),
+ descriptor(AnnotationWithInjectedMethod.class))
+ .transform(),
+ transformer(AnnotationWithInjectedMethod.class)
+ .setAnnotation()
+ .removeInnerClasses()
+ .replaceAnnotationDescriptor(
+ descriptor(ToBeRetention.class), descriptor(Retention.class))
+ .transform());
+ }
+
+ static class Data {
+
+ String value;
+
+ Data(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ToBeRetention {
+ RetentionPolicy value();
+ }
+
+ @ToBeRetention(RetentionPolicy.RUNTIME)
+ /*@*/ interface AnnotationWithInjectedMethod extends Annotation {
+
+ default Data getInstanceData() {
+ return new Data("Baz");
+ }
+
+ default Data getInstanceData(Data in) {
+ return new Data(in.value);
+ }
+
+ static Data getStaticData() {
+ return new Data("Foo");
+ }
+
+ static Data getStaticData(Data in) {
+ return new Data(in.value);
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ToBeAnnotationWithInjectedMethod {}
+
+ @ToBeAnnotationWithInjectedMethod
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(AnnotationWithInjectedMethod.getStaticData());
+ System.out.println(AnnotationWithInjectedMethod.getStaticData(new Data("Bar")));
+
+ try {
+ System.out.println(getAnnotationWithInjectedMethod().getInstanceData());
+ } catch (Throwable e) {
+ System.out.println("Caught " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+
+ try {
+ System.out.println(getAnnotationWithInjectedMethod().getInstanceData(new Data("Qux")));
+ } catch (Throwable e) {
+ System.out.println("Caught " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ }
+
+ static AnnotationWithInjectedMethod getAnnotationWithInjectedMethod() {
+ return Main.class.getAnnotation(AnnotationWithInjectedMethod.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 7b0e410..b2e0060 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -28,6 +28,7 @@
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
@@ -336,6 +337,15 @@
});
}
+ public ClassFileTransformer setAnnotation() {
+ return setAccessFlags(
+ accessFlags -> {
+ assert accessFlags.isAbstract();
+ assert accessFlags.isInterface();
+ accessFlags.setAnnotation();
+ });
+ }
+
public ClassFileTransformer setBridge(Method method) {
return setAccessFlags(method, MethodAccessFlags::setBridge);
}
@@ -401,6 +411,16 @@
boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
}
+ public ClassFileTransformer removeInnerClasses() {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // Intentionally empty.
+ }
+ });
+ }
+
public ClassFileTransformer removeMethods(MethodPredicate predicate) {
return addClassTransformer(
new ClassTransformer() {
@@ -459,6 +479,18 @@
void apply(Label start, Label end, Label handler, String type);
}
+ public ClassFileTransformer replaceAnnotationDescriptor(
+ String oldDescriptor, String newDescriptor) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ return super.visitAnnotation(
+ descriptor.equals(oldDescriptor) ? newDescriptor : descriptor, visible);
+ }
+ });
+ }
+
public ClassFileTransformer replaceClassDescriptorInMethodInstructions(
String oldDescriptor, String newDescriptor) {
return addMethodTransformer(