Kotlin metadata for annotations

Bug: b/332081811
Change-Id: I778166cea8985e86eaebce743d6b58606a94376e
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index e670bb5..1eaa94c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -124,6 +124,16 @@
           originalAssignmentTracker.add(method.getReference());
         }
       }
+      if (propertyProcessor.syntheticMethodForAnnotationsSignature() != null) {
+        DexEncodedMethod method =
+            methodSignatureMap.get(
+                propertyProcessor.syntheticMethodForAnnotationsSignature().asString());
+        if (method != null) {
+          hasBacking = true;
+          method.setKotlinMemberInfo(kotlinPropertyInfo);
+          originalAssignmentTracker.add(method.getReference());
+        }
+      }
       if (!hasBacking) {
         notBackedProperties.add(kotlinPropertyInfo);
       }
@@ -209,8 +219,13 @@
       KotlinPropertyGroup kotlinPropertyGroup =
           properties.computeIfAbsent(kotlinPropertyInfo, ignored -> new KotlinPropertyGroup());
       if (method.getReference().proto.returnType == appView.dexItemFactory().voidType) {
-        // This is a setter.
-        kotlinPropertyGroup.setSetter(method);
+        if (method.getReference().getArity() == 0) {
+          // This is an annotation method.
+          kotlinPropertyGroup.setSyntheticMethodForAnnotations(method);
+        } else {
+          // This is a setter.
+          kotlinPropertyGroup.setSetter(method);
+        }
       } else {
         kotlinPropertyGroup.setGetter(method);
       }
@@ -223,6 +238,7 @@
               kotlinPropertyGroup.backingField,
               kotlinPropertyGroup.getter,
               kotlinPropertyGroup.setter,
+              kotlinPropertyGroup.syntheticMethodForAnnotations,
               appView);
     }
     // Add all not backed functions and properties.
@@ -253,6 +269,7 @@
     private DexEncodedField backingField = null;
     private DexEncodedMethod setter = null;
     private DexEncodedMethod getter = null;
+    private DexEncodedMethod syntheticMethodForAnnotations = null;
 
     void setBackingField(DexEncodedField backingField) {
       assert this.backingField == null;
@@ -268,5 +285,10 @@
       assert this.setter == null;
       this.setter = setter;
     }
+
+    public void setSyntheticMethodForAnnotations(DexEncodedMethod syntheticMethodForAnnotations) {
+      assert this.syntheticMethodForAnnotations == null;
+      this.syntheticMethodForAnnotations = syntheticMethodForAnnotations;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index b5de2c0..e7ffaec 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -114,6 +114,8 @@
     private JvmMethodSignature getterSignature = null;
     // Custom getter via @set:JvmName("..."). Otherwise, null.
     private JvmMethodSignature setterSignature = null;
+    private JvmMethodSignature syntheticMethodForAnnotationsSignature = null;
+
     KmPropertyProcessor(KmProperty kmProperty) {
       kmProperty.accept(
           new KmPropertyVisitor() {
@@ -137,6 +139,12 @@
                   assert setterSignature == null : setterSignature.asString();
                   setterSignature = setterDesc;
                 }
+
+                @Override
+                public void visitSyntheticMethodForAnnotations(JvmMethodSignature signature) {
+                  assert syntheticMethodForAnnotationsSignature == null : signature.asString();
+                  syntheticMethodForAnnotationsSignature = signature;
+                }
               };
             }
           });
@@ -153,6 +161,10 @@
     JvmMethodSignature setterSignature() {
       return setterSignature;
     }
+
+    public JvmMethodSignature syntheticMethodForAnnotationsSignature() {
+      return syntheticMethodForAnnotationsSignature;
+    }
   }
 
   static boolean isValidMethodDescriptor(String methodDescriptor) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
index fc8e889..0581a5d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -146,7 +146,7 @@
   }
 
   boolean rewriteNoBacking(Consumer<KmProperty> consumer, AppView<?> appView) {
-    return rewrite(consumer, null, null, null, appView);
+    return rewrite(consumer, null, null, null, null, appView);
   }
 
   boolean rewrite(
@@ -154,6 +154,7 @@
       DexEncodedField field,
       DexEncodedMethod getter,
       DexEncodedMethod setter,
+      DexEncodedMethod syntheticMethodForAnnotationsMethod,
       AppView<?> appView) {
     // TODO(b/154348683): Flags again.
     KmProperty kmProperty =
@@ -207,16 +208,18 @@
               setter,
               appView);
     }
+    if (syntheticMethodForAnnotations != null) {
+      rewritten |=
+          syntheticMethodForAnnotations.rewrite(
+              newSignature ->
+                  JvmExtensionsKt.setSyntheticMethodForAnnotations(kmProperty, newSignature),
+              syntheticMethodForAnnotationsMethod,
+              appView);
+    }
     JvmExtensionsKt.setJvmFlags(kmProperty, jvmFlags);
     rewritten |=
         rewriteIfNotNull(
             appView,
-            syntheticMethodForAnnotations,
-            newMethod -> JvmExtensionsKt.setSyntheticMethodForAnnotations(kmProperty, newMethod),
-            KotlinJvmMethodSignatureInfo::rewriteNoBacking);
-    rewritten |=
-        rewriteIfNotNull(
-            appView,
             syntheticMethodForDelegate,
             newMethod -> JvmExtensionsKt.setSyntheticMethodForDelegate(kmProperty, newMethod),
             KotlinJvmMethodSignatureInfo::rewriteNoBacking);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/KotlinMetadataTest.java
new file mode 100644
index 0000000..b11a4db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/KotlinMetadataTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2024, 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.kotlin.metadata.syntheticmethodforannotations;
+
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import kotlinx.metadata.internal.extensions.KmPropertyExtension;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.internal.JvmPropertyExtension;
+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 KotlinMetadataTest extends KotlinTestBase {
+
+  private static final String PACKAGE =
+      "com.android.tools.r8.kotlin.metadata.syntheticmethodforannotations.kt";
+  private static final String MAIN = PACKAGE + ".MetadataKt";
+  private static final List<String> EXPECTED_OUTPUT =
+      ImmutableList.of("start", "All has @Test: true", "All2 has @Test: true", "end");
+  private static final List<String> EXPECTED_FALSE_OUTPUT =
+      ImmutableList.of("start", "All has @Test: false", "All2 has @Test: false", "end");
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getKotlinTestParameters().withAllCompilersLambdaGenerationsAndTargetVersions().build());
+  }
+
+  public KotlinMetadataTest(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
+    this.parameters = parameters;
+  }
+
+  private static final KotlinCompileMemoizer compilationResults =
+      getCompileMemoizer(getKotlinSources());
+
+  private static Collection<Path> getKotlinSources() {
+    try {
+      return getFilesInTestFolderRelativeToClass(KotlinMetadataTest.class, "kt", ".kt");
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Test
+  public void testJvm() throws ExecutionException, CompilationFailedException, IOException {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters))
+        .addRunClasspathFiles(
+            buildOnDexRuntime(
+                parameters, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar()))
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testD8() throws ExecutionException, CompilationFailedException, IOException {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters))
+        .addProgramFiles(kotlinc.getKotlinStdlibJar())
+        .addProgramFiles(kotlinc.getKotlinReflectJar())
+        .addProgramFiles(kotlinc.getKotlinAnnotationJar())
+        .setMinApi(parameters)
+        .addOptionsModification(
+            options -> {
+              options.testing.enableD8ResourcesPassThrough = true;
+              options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+            })
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters))
+        .addProgramFiles(kotlinc.getKotlinStdlibJar())
+        .addProgramFiles(kotlinc.getKotlinReflectJar())
+        .addProgramFiles(kotlinc.getKotlinAnnotationJar())
+        .addKeepRules(
+            "-keep class "
+                + PACKAGE
+                + ".**\n"
+                + "-keep,allowobfuscation class "
+                + PACKAGE
+                + ".** { *; }")
+        .addKeepEnumsRule()
+        .addKeepMainRule(MAIN)
+        .allowUnusedDontWarnPatterns()
+        .allowDiagnosticMessages()
+        .setMinApi(parameters)
+        .addOptionsModification(
+            options -> {
+              options.testing.enableD8ResourcesPassThrough = true;
+              options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+            })
+        .compile()
+        .inspect(this::verifyRewrittenExtension)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  static JvmMethodSignature toJvmMethodSignature(DexMethod method) {
+    StringBuilder descBuilder = new StringBuilder();
+    descBuilder.append("(");
+    for (DexType argType : method.proto.parameters.values) {
+      descBuilder.append(argType.toDescriptorString());
+    }
+    descBuilder.append(")");
+    descBuilder.append(method.proto.returnType.toDescriptorString());
+    return new JvmMethodSignature(method.name.toString(), descBuilder.toString());
+  }
+
+  private void verifyRewrittenExtension(CodeInspector i) {
+    ClassSubject clazz = i.clazz(PACKAGE + ".Data$Companion");
+    List<KmPropertySubject> properties = clazz.getKmClass().getProperties();
+    for (int i1 = 0; i1 < 2; i1++) {
+      KmPropertySubject kmPropertySubject = properties.get(i1);
+      List<KmPropertyExtension> extensions =
+          kmPropertySubject.getKmProperty().getExtensions$kotlinx_metadata();
+      assertEquals(1, extensions.size());
+      JvmMethodSignature syntheticMethodForAnnotations =
+          ((JvmPropertyExtension) extensions.get(0)).getSyntheticMethodForAnnotations();
+      assertTrue(
+          clazz.allMethods().stream()
+              .anyMatch(
+                  m ->
+                      toJvmMethodSignature(m.getMethod().getReference())
+                          .equals(syntheticMethodForAnnotations)));
+    }
+  }
+
+  @Test
+  public void testR8NoKR() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(compilationResults.getForConfiguration(kotlinParameters))
+        .addProgramFiles(kotlinc.getKotlinStdlibJar())
+        .addProgramFiles(kotlinc.getKotlinReflectJar())
+        .addProgramFiles(kotlinc.getKotlinAnnotationJar())
+        .addKeepRules(
+            "-keep class "
+                + PACKAGE
+                + ".**\n"
+                + "-keep,allowobfuscation class "
+                + PACKAGE
+                + ".** {  getAll(); getAll2(); }")
+        .addKeepEnumsRule()
+        .addKeepMainRule(MAIN)
+        .allowUnusedDontWarnPatterns()
+        .allowDiagnosticMessages()
+        .setMinApi(parameters)
+        .addOptionsModification(
+            options -> {
+              options.testing.enableD8ResourcesPassThrough = true;
+              options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+            })
+        .compile()
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_FALSE_OUTPUT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/kt/Metadata.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/kt/Metadata.kt
new file mode 100644
index 0000000..74154f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/syntheticmethodforannotations/kt/Metadata.kt
@@ -0,0 +1,27 @@
+// Copyright (c) 2023, 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.kotlin.metadata.syntheticmethodforannotations.kt
+
+import kotlin.reflect.full.declaredMemberProperties
+
+annotation class Test
+
+class Data {
+  companion object {
+    @Test
+    var All: Set<String> = emptySet()
+
+    @property:Test
+    var All2: Set<String> = emptySet()
+  }
+}
+
+fun main() {
+  println("start")
+  Data.Companion::class.declaredMemberProperties.forEach {
+    println("${it.name} has @Test: ${it.annotations.filterIsInstance<Test>().any()}")
+  }
+  println("end")
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
index accf1b0..e2099f2 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
@@ -70,6 +70,11 @@
   }
 
   @Override
+  public KmProperty getKmProperty() {
+    return kmProperty;
+  }
+
+  @Override
   public KmTypeSubject returnType() {
     return new KmTypeSubject(codeInspector, kmProperty.getReturnType());
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
index dc3f62e..77a6b40 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
@@ -23,5 +23,9 @@
 
   public abstract JvmMethodSignature setterSignature();
 
+  public KmProperty getKmProperty() {
+    return null;
+  }
+
   public abstract KmTypeSubject returnType();
 }