Keep runtime affecting annotations with class retention
The annotations in the package dalvik.annotation.optimization
are inspected by Art at runtime. Some of these have class retention
but are needed at runtime regardless. They are not visible to the
program only to the runtime.
Bug: 209701182
Change-Id: I0bb968101c123eda7e5f4f04d847b86aed29d051
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 6952bf7..366ad9c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -114,9 +114,10 @@
if (options.retainCompileTimeAnnotations) {
return true;
}
- if (annotation == options.itemFactory.dalvikFastNativeAnnotation
- || annotation == options.itemFactory.dalvikCriticalNativeAnnotation
- || annotation == options.itemFactory.annotationSynthesizedClass) {
+ if (annotation == options.itemFactory.annotationSynthesizedClass
+ || annotation
+ .getDescriptor()
+ .startsWith(options.itemFactory.dalvikAnnotationOptimizationPrefix)) {
return true;
}
if (options.processCovariantReturnTypeAnnotations) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index c108238..c064992 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -74,6 +74,8 @@
public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
public static final String recordDescriptorString = "Ljava/lang/Record;";
+ public static final String dalvikAnnotationOptimizationPrefixString =
+ "Ldalvik/annotation/optimization/";
/** Set of types that may be synthesized during compilation. */
private final Set<DexType> possibleCompilerSynthesizedTypes = Sets.newIdentityHashSet();
@@ -334,6 +336,10 @@
public final DexString valueString = createString("value");
public final DexString kindString = createString("kind");
+ // Prefix for runtime affecting yet potential class-retained annotations.
+ public final DexString dalvikAnnotationOptimizationPrefix =
+ createString(dalvikAnnotationOptimizationPrefixString);
+
public final DexType booleanType = createStaticallyKnownType(booleanDescriptor);
public final DexType byteType = createStaticallyKnownType(byteDescriptor);
public final DexType charType = createStaticallyKnownType(charDescriptor);
@@ -680,12 +686,6 @@
public final DexType annotationReachabilitySensitive =
createStaticallyKnownType("Ldalvik/annotation/optimization/ReachabilitySensitive;");
- // Runtime affecting yet class-retained annotations.
- public final DexType dalvikFastNativeAnnotation =
- createStaticallyKnownType("Ldalvik/annotation/optimization/FastNative;");
- public final DexType dalvikCriticalNativeAnnotation =
- createStaticallyKnownType("Ldalvik/annotation/optimization/CriticalNative;");
-
private static final String METAFACTORY_METHOD_NAME = "metafactory";
private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
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 244257a..7796626 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -136,6 +136,12 @@
return isAnnotationTypeLive;
case DexAnnotation.VISIBILITY_BUILD:
+ if (annotation
+ .getAnnotationType()
+ .getDescriptor()
+ .startsWith(options.itemFactory.dalvikAnnotationOptimizationPrefix)) {
+ return true;
+ }
if (kind.isParameter()) {
if (!options.isKeepRuntimeInvisibleParameterAnnotationsEnabled()) {
return false;
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 5185a84..cd62dc2 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -96,6 +96,14 @@
return -1;
}
+ public static <S, T> List<T> map(S[] list, Function<S, T> fn) {
+ List<T> result = new ArrayList<>();
+ for (S element : list) {
+ result.add(fn.apply(element));
+ }
+ return result;
+ }
+
public static <S, T> List<T> map(Iterable<S> list, Function<S, T> fn) {
List<T> result = new ArrayList<>();
for (S element : list) {
diff --git a/src/test/java/com/android/tools/r8/annotations/DalvikAnnotationOptimizationTest.java b/src/test/java/com/android/tools/r8/annotations/DalvikAnnotationOptimizationTest.java
new file mode 100644
index 0000000..59e8923
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/DalvikAnnotationOptimizationTest.java
@@ -0,0 +1,201 @@
+// Copyright (c) 2021, 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.AnnotationVisitor;
+
+@RunWith(Parameterized.class)
+public class DalvikAnnotationOptimizationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean optimizationPackage;
+
+ @Parameter(2)
+ public boolean addAnnotationsOnLibraryPath;
+
+ @Parameters(name = "{0}, optimizationPackage = {1}, addAnnotationsOnLibraryPath = {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevels().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ private static final String dalvikOptimizationPrefix =
+ DexItemFactory.dalvikAnnotationOptimizationPrefixString;
+ private static final String dalvikCodegenPrefix = "Ldalvik/annotation/codegen/";
+ private static final String ourClassName =
+ DescriptorUtils.javaTypeToDescriptor(DalvikAnnotationOptimizationTest.class.getTypeName());
+ private static final String innerClassPrefix =
+ ourClassName.substring(0, ourClassName.length() - 1) + "$";
+
+ private static String changePackage(boolean optimizationPackage, String descriptor) {
+ return (optimizationPackage ? dalvikOptimizationPrefix : dalvikCodegenPrefix)
+ + descriptor.substring(innerClassPrefix.length());
+ }
+
+ private void checkExpectedAnnotations(CodeInspector inspector) {
+ Set<String> expected =
+ optimizationPackage
+ ? ImmutableSet.of(
+ dalvikOptimizationPrefix + "CriticalNative;",
+ dalvikOptimizationPrefix + "FastNative;",
+ dalvikOptimizationPrefix + "NeverCompile;",
+ dalvikOptimizationPrefix + "AndAnotherOne;")
+ : ImmutableSet.of();
+ assertEquals(
+ expected,
+ inspector.clazz(TestClass.class).uniqueMethodWithName("main").annotations().stream()
+ .map(s -> s.getAnnotation().type.getDescriptor().toSourceString())
+ .collect(Collectors.toSet()));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(TestClass.class).addMethodTransformer(getMethodTransformer()).transform())
+ .setMinApi(parameters.getApiLevel())
+ .applyIf(
+ addAnnotationsOnLibraryPath,
+ b -> {
+ b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST));
+ b.addLibraryProvider(new ClassPathProviderForAnnotations(optimizationPackage));
+ })
+ .compile()
+ .inspect(this::checkExpectedAnnotations);
+ }
+
+ private MethodTransformer getMethodTransformer() {
+ return new MethodTransformer() {
+ @Override
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ assert descriptor.startsWith(innerClassPrefix);
+ return new AnnotationVisitor(
+ ASM7,
+ super.visitAnnotation(changePackage(optimizationPackage, descriptor), visible)) {};
+ }
+ };
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(TestClass.class).addMethodTransformer(getMethodTransformer()).transform())
+ .applyIf(
+ addAnnotationsOnLibraryPath,
+ b -> {
+ b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST));
+ b.addLibraryProvider(new ClassPathProviderForAnnotations(optimizationPackage));
+ },
+ b ->
+ b.addDontWarn(
+ optimizationPackage
+ ? "dalvik.annotation.optimization.*"
+ : "dalvik.annotation.codegen.*"))
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .inspect(this::checkExpectedAnnotations);
+ }
+
+ static class ClassPathProviderForAnnotations implements ClassFileResourceProvider {
+ private final boolean optimizationPackage;
+ private final Map<String, byte[]> resources = new HashMap<>();
+
+ ClassPathProviderForAnnotations(boolean optimizationPackage) throws IOException {
+ this.optimizationPackage = optimizationPackage;
+ for (Class<?> clazz :
+ new Class<?>[] {
+ CriticalNative.class, FastNative.class, NeverCompile.class, AndAnotherOne.class
+ }) {
+ resources.put(
+ changePackage(
+ optimizationPackage, DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())),
+ transformPackageName(clazz));
+ }
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ return resources.keySet();
+ }
+
+ @Override
+ public ProgramResource getProgramResource(String descriptor) {
+ byte[] bytes = resources.get(descriptor);
+ return bytes == null
+ ? null
+ : ProgramResource.fromBytes(
+ Origin.unknown(), Kind.CF, bytes, Collections.singleton(descriptor));
+ }
+
+ private byte[] transformPackageName(Class<?> clazz) throws IOException {
+ return transformer(clazz)
+ .setClassDescriptor(
+ changePackage(
+ optimizationPackage, DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName())))
+ .transform();
+ }
+ }
+
+ // Keep all CLASS retention annotations in the dalvik.annotation.optimization package.
+ // See b/209701182
+ @Retention(RetentionPolicy.CLASS)
+ @interface CriticalNative {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @interface FastNative {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @interface NeverCompile {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @interface AndAnotherOne {}
+
+ static class TestClass {
+ @CriticalNative
+ @FastNative
+ @NeverCompile
+ @AndAnotherOne
+ public static void main(String[] args) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
index a97f040..d06b289 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
@@ -6,7 +6,10 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.List;
public class FoundAnnotationSubject extends AnnotationSubject {
@@ -16,6 +19,10 @@
this.annotation = annotation;
}
+ public static List<AnnotationSubject> listFromDex(DexAnnotationSet annotations) {
+ return ListUtils.map(annotations.annotations, FoundAnnotationSubject::new);
+ }
+
@Override
public boolean isPresent() {
return true;
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 69ebe80..0d62343 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
@@ -355,11 +355,7 @@
@Override
public List<AnnotationSubject> annotations() {
- List<AnnotationSubject> result = new ArrayList<>();
- for (DexAnnotation annotation : dexClass.annotations().annotations) {
- result.add(new FoundAnnotationSubject(annotation));
- }
- return result;
+ return FoundAnnotationSubject.listFromDex(dexClass.annotations());
}
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 40fcc0f..813739b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -326,7 +326,7 @@
@Override
public List<AnnotationSubject> annotations() {
- throw new Unimplemented();
+ return FoundAnnotationSubject.listFromDex(dexMethod.annotations());
}
@Override