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(