Support compiling with DefaultAnnotation definition.
Fixes: b/229997296
Change-Id: I58bb98e4ebba6800bfc7f6c38518435f27e5c76a
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 4b9385d..f6959c6 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -260,9 +260,17 @@
private DexAnnotationElement rewriteAnnotationElement(
DexType annotationType, DexAnnotationElement original) {
- DexClass definition = appView.definitionFor(annotationType);
+ // The dalvik.annotation.AnnotationDefault is typically not on bootclasspath. However, if it
+ // is present, the definition does not define the 'value' getter but that is the spec:
+ // https://source.android.com/devices/tech/dalvik/dex-format#dalvik-annotation-default
+ // If the annotation matches the structural requirement keep it.
+ if (appView.dexItemFactory().annotationDefault.equals(annotationType)
+ && appView.dexItemFactory().valueString.equals(original.name)) {
+ return original;
+ }
// We cannot strip annotations where we cannot look up the definition, because this will break
// apps that rely on the annotation to exist. See b/134766810 for more information.
+ DexClass definition = appView.definitionFor(annotationType);
if (definition == null) {
return original;
}
diff --git a/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java b/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java
new file mode 100644
index 0000000..a35b813
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2022, 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.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ThrowableConsumer;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultAnnotationTest extends TestBase {
+
+ static final String ANNO_NAME = typeName(MyAnnotation.class);
+ static final String EXPECTED = StringUtils.lines("@" + ANNO_NAME + "(hello=Hello World!)");
+ static final String EXPECTED_QUOTES =
+ StringUtils.lines("@" + ANNO_NAME + "(hello=\"Hello World!\")");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public DefaultAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNoDefinition() throws Exception {
+ runTest(b -> {});
+ }
+
+ @Test
+ public void testOnLibrary() throws Exception {
+ runTest(b -> b.addLibraryClassFileData(getDalvikAnnotationDefault()));
+ }
+
+ @Test
+ public void testOnProgram() throws Exception {
+ runTest(b -> b.addProgramClassFileData(getDalvikAnnotationDefault()));
+ }
+
+ private void runTest(ThrowableConsumer<R8FullTestBuilder> modification) throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MyAnnotation.class, MyAnnotatedClass.class, TestClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .apply(modification)
+ .addKeepAllAttributes()
+ .addKeepRules("-keep class * { *; }")
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ private String getExpected() {
+ if (parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK9)) {
+ return EXPECTED_QUOTES;
+ }
+ return EXPECTED;
+ }
+
+ private static byte[] getDalvikAnnotationDefault() throws Exception {
+ return transformer(WillBeDalvikAnnotationAnnotationDefault.class)
+ .setClassDescriptor("Ldalvik/annotation/AnnotationDefault;")
+ .transform();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.ANNOTATION_TYPE)
+ @interface WillBeDalvikAnnotationAnnotationDefault {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface MyAnnotation {
+ String hello() default "Hello World!";
+ }
+
+ @MyAnnotation
+ static class MyAnnotatedClass {}
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ for (Annotation annotation : MyAnnotatedClass.class.getAnnotations()) {
+ System.out.println(annotation.toString());
+ }
+ }
+ }
+}