Version 1.3.42

Merge: Keep annotations for targeted methods
CL: https://r8-review.googlesource.com/c/r8/+/31402

Bug: 120168590
Change-Id: Iac98b6a27f92a0895ca5b68cd8b4081ad74806b0
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7bed8ae..e218f3d 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -313,7 +313,10 @@
                   .prunedCopyFrom(application, pruner.getRemovedClasses()));
           new AbstractMethodRemover(appView.getAppInfo()).run();
         }
-        new AnnotationRemover(appView.getAppInfo().withLiveness(), compatibility, options).run();
+
+        new AnnotationRemover(appView.getAppInfo().withLiveness(), options)
+            .ensureValid(compatibility)
+            .run();
 
         // TODO(69445518): This is still work in progress, and this file writing is currently used
         // for testing.
@@ -462,6 +465,8 @@
             // Print reasons on the application after pruning, so that we reflect the actual result.
             ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
             reasonPrinter.run(application);
+            // Remove annotations that refer to types that no longer exist.
+            new AnnotationRemover(appView.getAppInfo().withLiveness(), options).run();
           }
         } finally {
           timing.end();
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 3ea008a..f99ce40 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.41";
+  public static final String LABEL = "1.3.42";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 459fe7a..00e09a4 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -19,13 +19,10 @@
   private final AppInfoWithLiveness appInfo;
   private final ProguardKeepAttributes keep;
   private final InternalOptions options;
-  private final ProguardConfiguration.Builder compatibility;
 
-  public AnnotationRemover(AppInfoWithLiveness appInfo,
-      ProguardConfiguration.Builder compatibility, InternalOptions options) {
+  public AnnotationRemover(AppInfoWithLiveness appInfo, InternalOptions options) {
     this.appInfo = appInfo;
     this.keep = options.proguardConfiguration.getKeepAttributes();
-    this.compatibility = compatibility;
     this.options = options;
   }
 
@@ -118,8 +115,12 @@
     return isAnnotationTypeLive(annotation);
   }
 
-  public void run() {
+  public AnnotationRemover ensureValid(ProguardConfiguration.Builder compatibility) {
     keep.ensureValid(options.forceProguardCompatibility, compatibility);
+    return this;
+  }
+
+  public void run() {
     for (DexProgramClass clazz : appInfo.classes()) {
       stripAttributes(clazz);
       clazz.annotations = clazz.annotations.keepIf(this::filterAnnotations);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f43dab5..d32402a 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -814,20 +814,24 @@
     }
   }
 
-  private void markMethodAsTargeted(DexEncodedMethod encodedMethod, KeepReason reason) {
-    markTypeAsLive(encodedMethod.method.holder);
-    markParameterAndReturnTypesAsLive(encodedMethod);
-    if (Log.ENABLED) {
-      Log.verbose(getClass(), "Method `%s` is targeted.", encodedMethod.method);
+  private void markMethodAsTargeted(DexEncodedMethod method, KeepReason reason) {
+    if (!targetedMethods.add(method, reason)) {
+      return;
     }
-    targetedMethods.add(encodedMethod, reason);
+    markTypeAsLive(method.method.holder);
+    markParameterAndReturnTypesAsLive(method);
+    processAnnotations(method.annotations.annotations);
+    method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
+    if (Log.ENABLED) {
+      Log.verbose(getClass(), "Method `%s` is targeted.", method.method);
+    }
     if (forceProguardCompatibility) {
       // Keep targeted default methods in compatibility mode. The tree pruner will otherwise make
       // these methods abstract, whereas Proguard does not (seem to) touch their code.
-      DexClass clazz = appInfo.definitionFor(encodedMethod.method.holder);
-      if (!encodedMethod.accessFlags.isAbstract()
+      DexClass clazz = appInfo.definitionFor(method.method.holder);
+      if (!method.accessFlags.isAbstract()
           && clazz.isInterface() && !clazz.isLibraryClass()) {
-        markMethodAsKeptWithCompatRule(encodedMethod);
+        markMethodAsKeptWithCompatRule(method);
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
new file mode 100644
index 0000000..a7bdf0b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -0,0 +1,117 @@
+// 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.shaking.annotations;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+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 AnnotationsOnTargetedMethodTest extends TestBase {
+
+  private static final String expectedOutput =
+      StringUtils.lines(
+          "In InterfaceImpl.targetedMethod()",
+          "In OtherInterfaceImpl.targetedMethod()",
+          MyAnnotation.class.getName());
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public AnnotationsOnTargetedMethodTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void jvmTest() throws Exception {
+    assumeTrue(
+        "JVM test independent of Art version - only run when testing on latest",
+        ToolHelper.getDexVm().getVersion().isLatest());
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+  }
+
+  @Test
+  public void r8Test() throws Exception {
+    testForR8(backend)
+        .addInnerClasses(AnnotationsOnTargetedMethodTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keepattributes *Annotation*", "-dontobfuscate")
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      test(new InterfaceImpl());
+      test(new OtherInterfaceImpl());
+
+      Method method = Interface.class.getDeclaredMethods()[0];
+      for (Annotation annotation : method.getAnnotations()) {
+        visitAnnotation((MyAnnotation) annotation);
+      }
+    }
+
+    @NeverInline
+    private static void test(Interface obj) {
+      obj.targetedMethod();
+    }
+
+    @NeverInline
+    private static void visitAnnotation(MyAnnotation annotation) {
+      System.out.println(annotation.annotationType().getName());
+    }
+  }
+
+  @NeverMerge
+  interface Interface {
+
+    @NeverInline
+    @MyAnnotation
+    void targetedMethod();
+  }
+
+  static class InterfaceImpl implements Interface {
+
+    @NeverInline
+    @Override
+    public void targetedMethod() {
+      System.out.println("In InterfaceImpl.targetedMethod()");
+    }
+  }
+
+  static class OtherInterfaceImpl implements Interface {
+
+    @NeverInline
+    @Override
+    public void targetedMethod() {
+      System.out.println("In OtherInterfaceImpl.targetedMethod()");
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD})
+  @interface MyAnnotation {}
+}