Add test for modeling kotlin metadata annotations and arguments
Bug: 158124557
Change-Id: Id98e33df1171eedcfbd12c31bc4236bde6926189
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index c1eb1d2..1a51644 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.StringUtils;
import java.io.PrintStream;
+import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -20,6 +21,7 @@
import kotlinx.metadata.InconsistentKotlinMetadataException;
import kotlinx.metadata.KmAnnotation;
import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmContract;
@@ -177,7 +179,7 @@
String indent,
String typeDescription,
StringBuilder sb,
- List<T> items,
+ Collection<T> items,
BiConsumer<String, T> appendItem) {
if (items.isEmpty()) {
sb.append(typeDescription).append("[]");
@@ -768,13 +770,47 @@
sb,
nextIndent -> {
Map<String, KmAnnotationArgument<?>> arguments = kmAnnotation.getArguments();
- for (String key : arguments.keySet()) {
- appendKeyValue(nextIndent, key, sb, arguments.get(key).toString());
- }
+ appendKmList(
+ nextIndent,
+ "{ key: String, value: KmAnnotationArgument<?> }",
+ sb,
+ arguments.keySet(),
+ (nextNextIndent, key) -> {
+ appendKmSection(
+ nextNextIndent,
+ "",
+ sb,
+ nextNextNextIndent -> {
+ appendKeyValue(
+ nextNextNextIndent,
+ key,
+ sb,
+ nextNextNextNextIndent -> {
+ appendKmArgument(nextNextNextIndent, sb, arguments.get(key));
+ });
+ });
+ });
});
});
}
+ private static void appendKmArgument(
+ String indent, StringBuilder sb, KmAnnotationArgument<?> annotationArgument) {
+ if (annotationArgument instanceof KmAnnotationArgument.ArrayValue) {
+ List<KmAnnotationArgument<?>> value = ((ArrayValue) annotationArgument).getValue();
+ appendKmList(
+ indent,
+ "ArrayValue",
+ sb,
+ value,
+ (newIndent, annoArg) -> {
+ appendKmArgument(newIndent, sb, annoArg);
+ });
+ } else {
+ sb.append(annotationArgument.toString());
+ }
+ }
+
private static void appendKmVersionRequirement(
String indent, StringBuilder sb, List<KmVersionRequirement> kmVersionRequirements) {
appendKeyValue(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
new file mode 100644
index 0000000..6fae341
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
@@ -0,0 +1,182 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmTypeAliasSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteAnnotationTest extends KotlinMetadataTestBase {
+
+ private final String EXPECTED_SMOKE =
+ StringUtils.lines(
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Bar",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "UP",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "LEFT",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "RIGHT",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "DOWN",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "UP");
+ private static final String PKG_LIB = PKG + ".annotation_lib";
+ private static final String PKG_APP = PKG + ".annotation_app";
+ private static final String FOO_FINAL_NAME = "a.b.c";
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteAnnotationTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private final TestParameters parameters;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED_SMOKE);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ String fooTypeName = PKG_LIB + ".Foo";
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ /// Keep the annotations
+ .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassAndEnum")
+ .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassArr")
+ // Keep Foo but rename to test arguments
+ .addKeepClassAndMembersRulesWithAllowObfuscation(fooTypeName)
+ .addApplyMapping(fooTypeName + " -> " + FOO_FINAL_NAME + ":")
+ // Keep Direction but rename the enum
+ .addKeepClassAndMembersRules(PKG_LIB + ".Direction")
+ // Keep Bar and Baz and Quux because we are directly reflecting on them
+ .addKeepClassAndMembersRules(PKG_LIB + ".Bar")
+ .addKeepClassAndMembersRules(PKG_LIB + ".Baz")
+ .addKeepClassAndMembersRules(PKG_LIB + ".Quux")
+ // Keep the static class for the type alias
+ .addKeepClassAndMembersRules(PKG_LIB + ".LibKt")
+ .addKeepRuntimeVisibleAnnotations()
+ .addKeepAttributes(
+ ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+ ProguardKeepAttributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
+ ProguardKeepAttributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addProgramFiles(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ // TODO(b/155053894): Fails because reflect cannot find the class.
+ .assertFailureWithErrorThatMatches(containsString("KotlinReflectionInternalError"));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Assert that foo is renamed.
+ ClassSubject foo = inspector.clazz(PKG_LIB + ".Foo");
+ assertThat(foo, isRenamed());
+ assertEquals(FOO_FINAL_NAME, foo.getFinalName());
+ // Assert that bar exists and is not renamed.
+ ClassSubject bar = inspector.clazz(PKG_LIB + ".Bar");
+ assertThat(bar, isPresent());
+ assertThat(bar, isNotRenamed());
+ // Check that the annotation type on the type alias has been renamed
+ inspectTypeAliasAnnotation(inspector, foo, bar);
+ }
+
+ private void inspectTypeAliasAnnotation(
+ CodeInspector inspector, ClassSubject foo, ClassSubject bar) {
+ ClassSubject libKt = inspector.clazz(PKG_LIB + ".LibKt");
+ assertThat(libKt, isPresent());
+ assertThat(libKt.getKmPackage(), isPresent());
+ KmTypeAliasSubject qux = libKt.getKmPackage().kmTypeAliasWithUniqueName("Qux");
+ assertThat(qux, isPresent());
+ assertEquals(1, qux.annotations().size());
+ KmAnnotation annotation = qux.annotations().get(0);
+ assertEquals(
+ DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB) + "/AnnoWithClassArr",
+ annotation.getClassName());
+ Map<String, KmAnnotationArgument<?>> arguments = annotation.getArguments();
+ assertEquals(1, arguments.size());
+ ArrayValue classes = (ArrayValue) arguments.get("classes");
+ // TODO(b/155053894): This should be renamed.
+ assertEquals(
+ "KClassValue(value=" + foo.getOriginalBinaryName() + ")",
+ classes.getValue().get(0).toString());
+ assertEquals(
+ "KClassValue(value=" + bar.getFinalBinaryName() + ")",
+ classes.getValue().get(1).toString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
index c6dd4fd..1ebdddb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -76,7 +76,6 @@
Path libJar =
testForR8(parameters.getBackend())
.addProgramFiles(libJars.get(targetVersion))
- // Allow renaming A to ensure that we rename in the flexible upper bound type.
.addKeepAllClassesRule()
.addKeepAllAttributes()
.compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
new file mode 100644
index 0000000..34d39b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
@@ -0,0 +1,37 @@
+// 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.kotlin.metadata.annotation_app
+
+import com.android.tools.r8.kotlin.metadata.annotation_lib.AnnoWithClassAndEnum
+import com.android.tools.r8.kotlin.metadata.annotation_lib.AnnoWithClassArr
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Bar
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Baz
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Quux
+import kotlin.reflect.full.primaryConstructor
+import kotlin.reflect.full.valueParameters
+
+fun main() {
+ Bar::class.primaryConstructor?.annotations?.get(0)?.printAnnoWithClassArr()
+ Baz::class.annotations.get(0).printAnnoWithClassArr()
+ Baz::class.annotations.get(1).printAnnoWithClassAndEnum()
+ Baz::prop.annotations.get(0).printAnnoWithClassAndEnum()
+ Baz::baz.annotations.get(0).printAnnoWithClassAndEnum()
+ Baz::baz.valueParameters.get(0).annotations.get(0).printAnnoWithClassAndEnum()
+ // We cannot reflect on annotations on typealiases:
+ // https://youtrack.jetbrains.com/issue/KT-21489
+ Quux::methodWithTypeAnnotations
+ .returnType.arguments.get(0).type?.annotations?.get(0)?.printAnnoWithClassAndEnum()
+}
+
+fun Annotation.printAnnoWithClassArr() {
+ val annoWithClassArr = this as AnnoWithClassArr
+ annoWithClassArr.classes.forEach { println(it) }
+}
+
+fun Annotation.printAnnoWithClassAndEnum() {
+ val annoWithClassAndEnum = this as AnnoWithClassAndEnum
+ println(annoWithClassAndEnum.clazz)
+ println(annoWithClassAndEnum.direction)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt
new file mode 100644
index 0000000..38a574c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt
@@ -0,0 +1,47 @@
+// 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.kotlin.metadata.annotation_lib
+
+import kotlin.reflect.KClass
+
+enum class Direction {
+ UP, RIGHT, DOWN, LEFT
+}
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.PROPERTY,
+ AnnotationTarget.TYPE)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AnnoWithClassAndEnum(val clazz : KClass<*>, val direction : Direction)
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.TYPEALIAS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AnnoWithClassArr(val classes : Array<KClass<*>>)
+
+class Foo
+
+class Bar @AnnoWithClassArr([Foo::class]) constructor()
+
+@AnnoWithClassArr([Foo::class, Bar::class])
+@AnnoWithClassAndEnum(Foo::class, Direction.UP) class Baz {
+
+ @AnnoWithClassAndEnum(Foo::class, Direction.LEFT) val prop : Int = 0
+
+ @AnnoWithClassAndEnum(Foo::class, Direction.RIGHT) fun baz(@AnnoWithClassAndEnum(Foo::class, Direction.DOWN) foo: Int): Int {
+ return 1
+ }
+}
+
+@AnnoWithClassArr([Foo::class, Bar::class])
+typealias Qux = Foo
+
+
+class Quux {
+
+ fun methodWithTypeAnnotations() : Array<@AnnoWithClassAndEnum(Foo::class, Direction.UP) Int> {
+ return arrayOf(1)
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 1491ce0..81b988e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -94,6 +94,11 @@
}
@Override
+ public String getOriginalBinaryName() {
+ return null;
+ }
+
+ @Override
public String getFinalName() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
index 78ac796..622db02 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
@@ -5,7 +5,9 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.errors.Unreachable;
+import com.google.common.collect.ImmutableList;
import java.util.List;
+import kotlinx.metadata.KmAnnotation;
public class AbsentKmTypeAliasSubject extends KmTypeAliasSubject {
@@ -48,4 +50,9 @@
public KmTypeSubject underlyingType() {
return null;
}
+
+ @Override
+ public List<KmAnnotation> annotations() {
+ return ImmutableList.of();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index bede2a0..97a3b29 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -165,6 +165,8 @@
public abstract String getOriginalDescriptor();
+ public abstract String getOriginalBinaryName();
+
public abstract String getFinalName();
public abstract String getFinalDescriptor();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 6774c2a..2ce3024 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -263,6 +263,11 @@
}
}
+ @Override
+ public String getOriginalBinaryName() {
+ return DescriptorUtils.getBinaryNameFromDescriptor(getOriginalDescriptor());
+ }
+
public DexType getOriginalDexType(DexItemFactory dexItemFactory) {
return dexItemFactory.createType(getOriginalDescriptor());
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
index a07125c..396ef98 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
@@ -9,6 +9,7 @@
import java.util.List;
import java.util.stream.Collectors;
+import kotlinx.metadata.KmAnnotation;
import kotlinx.metadata.KmTypeAlias;
public class FoundKmTypeAliasSubject extends KmTypeAliasSubject {
@@ -62,4 +63,9 @@
public KmTypeSubject underlyingType() {
return new KmTypeSubject(codeInspector, kmTypeAlias.underlyingType);
}
+
+ @Override
+ public List<KmAnnotation> annotations() {
+ return kmTypeAlias.getAnnotations();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
index f87d368..8ff3c90 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils.codeinspector;
import java.util.List;
+import kotlinx.metadata.KmAnnotation;
public abstract class KmTypeAliasSubject extends Subject {
@@ -17,4 +18,6 @@
public abstract KmTypeSubject expandedType();
public abstract KmTypeSubject underlyingType();
+
+ public abstract List<KmAnnotation> annotations();
}