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();
}