Fix rewriting of @AnnotationDefault annotations
This fixes an issue where @AnnotationDefault annotations were not correctly rewritten in presence of enum unboxing.
This also extends the annotation remover so that it removes @AnnotationDefault annotation elements corresponding to methods removed by tree shaking.
Fixes: b/448634317
Bug: b/191741002
Change-Id: If54cef675d27657024fa520ad06321bc25d50feb
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 905267d..3154d72 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -107,6 +107,7 @@
import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.shaking.AnnotationFixer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.Reporter;
@@ -722,6 +723,11 @@
.fixupTypeReferences(converter, executorService, timing);
EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens();
+ // Run annotation fixer so that enum field references in @AnnotationDefault annotations are
+ // correctly rewritten.
+ new AnnotationFixer(appView, enumUnboxingLens)
+ .run(appView.appInfo().classes(), executorService);
+
// Enqueue the (lens rewritten) methods that require reprocessing.
//
// Note that the reprocessing set must be rewritten to the new enum unboxing lens before pruning
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
index 3dea441..69b0f34 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
@@ -20,8 +20,10 @@
import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
import com.android.tools.r8.graph.DexValue.DexValueArray;
import com.android.tools.r8.graph.DexValue.DexValueEnum;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.Collection;
@@ -116,6 +118,12 @@
}
} else if (value.isDexValueEnum()) {
DexField original = value.asDexValueEnum().value;
+ if (lens.isEnumUnboxerLens()) {
+ EnumDataMap unboxedEnums = lens.asEnumUnboxerLens().getUnboxedEnums();
+ if (unboxedEnums.hasUnboxedValueFor(original)) {
+ return DexValueInt.create(unboxedEnums.getUnboxedValue(original));
+ }
+ }
DexField rewritten = lens.lookupField(original, annotationLens);
if (original != rewritten) {
return new DexValueEnum(rewritten);
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 5672c39..53d895b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -13,10 +13,13 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.ProgramDefinition;
@@ -25,6 +28,7 @@
import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
import com.android.tools.r8.kotlin.KotlinPropertyInfo;
import com.android.tools.r8.shaking.Enqueuer.Mode;
+import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ImmutableList;
@@ -274,35 +278,64 @@
// Check if we should keep this annotation first.
if (filterAnnotations(holder, original, kind, keepInfo)) {
// Then, filter out values that refer to dead definitions.
- return original.rewrite(this::rewriteEncodedAnnotation);
+ return original.rewrite(annotation -> rewriteEncodedAnnotation(annotation, holder));
}
return null;
}
- private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
+ private DexEncodedAnnotation rewriteEncodedAnnotation(
+ DexEncodedAnnotation original, ProgramDefinition holder) {
GraphLens graphLens = appView.graphLens();
DexType annotationType = original.type.getBaseType();
DexType rewrittenType = graphLens.lookupType(annotationType);
DexEncodedAnnotation rewrite =
original.rewrite(
- graphLens::lookupType, element -> rewriteAnnotationElement(rewrittenType, element));
+ graphLens::lookupType,
+ element -> rewriteAnnotationElement(rewrittenType, element, holder));
assert rewrite != null;
- DexClass annotationClass = appView.appInfo().definitionFor(rewrittenType);
- assert annotationClass == null
+ assert appView.appInfo().definitionFor(rewrittenType) == null
|| appView.appInfo().isNonProgramTypeOrLiveProgramType(rewrittenType);
+ if (rewrite.getType().isIdenticalTo(appView.dexItemFactory().annotationDefault)
+ && rewrite.getNumberOfElements() == 0) {
+ return null;
+ }
return rewrite;
}
- @SuppressWarnings("ReferenceEquality")
private DexAnnotationElement rewriteAnnotationElement(
- DexType annotationType, DexAnnotationElement original) {
+ DexType annotationType, DexAnnotationElement original, ProgramDefinition holder) {
// 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;
+ // If the annotation matches the structural requirement keep it, but prune the default values
+ // that no longer match a method on the annotation class. If no default values remain after
+ // pruning, then remove the AnnotationDefault altogether.
+ DexString name = original.getName();
+ if (appView.dexItemFactory().annotationDefault.isIdenticalTo(annotationType)
+ && appView.dexItemFactory().valueString.isIdenticalTo(name)) {
+ DexEncodedAnnotation annotationValue = original.getValue().asDexValueAnnotation().getValue();
+ DexAnnotationElement[] elements = annotationValue.elements;
+ DexAnnotationElement[] rewrittenElements =
+ ArrayUtils.map(
+ elements,
+ element -> {
+ DexString innerName = element.getName();
+ DexEncodedMethod method =
+ holder
+ .getContextClass()
+ .lookupMethod(m -> m.getName().isIdenticalTo(innerName));
+ return method != null ? element : null;
+ },
+ DexAnnotationElement.EMPTY_ARRAY);
+ if (rewrittenElements == elements) {
+ return original;
+ }
+ return ArrayUtils.isEmpty(rewrittenElements)
+ ? null
+ : new DexAnnotationElement(
+ name,
+ new DexValueAnnotation(
+ new DexEncodedAnnotation(annotationValue.getType(), rewrittenElements)));
}
// 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.
@@ -314,7 +347,7 @@
boolean liveGetter =
definition
.getMethodCollection()
- .hasVirtualMethods(method -> method.getReference().name == original.name);
+ .hasVirtualMethods(method -> method.getName().isIdenticalTo(name));
return liveGetter ? original : null;
}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForUnusedAnnotationShrinkingTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForUnusedAnnotationShrinkingTest.java
index a01b251..fd581f5 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForUnusedAnnotationShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/annotationdefault/DefaultValueForUnusedAnnotationShrinkingTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking.annotations.annotationdefault;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -50,17 +51,16 @@
compileResult -> {
CodeInspector inspector = compileResult.inspector();
- // MyAnnotation has a @Retention annotation and an @AnnotationDefault annotation.
- // TODO(b/191741002): MyAnnotation.value() is unused, thus there is no need to retain
- // the @AnnotationDefault annotation.
+ // MyAnnotation has a @Retention annotation. The @AnnotationDefault annotation has
+ // been removed since MyAnnotation.value() is unused.
ClassSubject annotationClassSubject = inspector.clazz(MyAnnotation.class);
assertThat(annotationClassSubject, isPresent());
assertThat(
annotationClassSubject.annotation(Retention.class.getTypeName()), isPresent());
assertThat(
annotationClassSubject.annotation("dalvik.annotation.AnnotationDefault"),
- isPresent());
- assertEquals(2, annotationClassSubject.getDexProgramClass().annotations().size());
+ isAbsent());
+ assertEquals(1, annotationClassSubject.annotations().size());
compileResult
.run(parameters.getRuntime(), Main.class)