Version 1.6.37
Cherry pick: Fixup annotations after vertical class merging
CL: https://r8-review.googlesource.com/c/r8/+/44420
Cherry pick: Rewrite merged types in method parameter annotations as
well
CL: https://r8-review.googlesource.com/c/r8/+/44421
Cherry pick: Reland "Do not remove runtime-visible-annotations if kept
and not resolved"
CL: https://r8-review.googlesource.com/c/r8/+/44423
Bug: 142661994
Bug: 142764521
Bug: 142507846
Bug: 134766810
Change-Id: I2b23f1a0847f6429c1c9040c08fbb95a993d3812
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 9cf1550..1be5bda 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.6.36";
+ public static final String LABEL = "1.6.37";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index f9b12d3..a120a5d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -54,6 +54,22 @@
return null;
}
+ public DexValueType asDexValueType() {
+ return null;
+ }
+
+ public DexValueArray asDexValueArray() {
+ return null;
+ }
+
+ public boolean isDexValueType() {
+ return false;
+ }
+
+ public boolean isDexValueArray() {
+ return false;
+ }
+
public static DexValue fromAsmBootstrapArgument(
Object value, JarApplicationReader application, DexType clazz) {
if (value instanceof Integer) {
@@ -805,6 +821,16 @@
DexMethod method, int instructionOffset) {
value.collectIndexedItems(indexedItems, method, instructionOffset);
}
+
+ @Override
+ public DexValueType asDexValueType() {
+ return this;
+ }
+
+ @Override
+ public boolean isDexValueType() {
+ return true;
+ }
}
static public class DexValueField extends NestedDexValue<DexField> {
@@ -949,6 +975,16 @@
public String toString() {
return "Array " + Arrays.toString(values);
}
+
+ @Override
+ public boolean isDexValueArray() {
+ return true;
+ }
+
+ @Override
+ public DexValueArray asDexValueArray() {
+ return this;
+ }
}
static public class DexValueAnnotation extends DexValue {
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
index 68c7da1..6ff5c75 100644
--- a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -5,8 +5,10 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.utils.ArrayUtils;
import java.util.Arrays;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -193,4 +195,12 @@
}
return new ParameterAnnotationsList(filtered, missingParameterAnnotations);
}
+
+ public ParameterAnnotationsList rewrite(Function<DexAnnotationSet, DexAnnotationSet> mapper) {
+ DexAnnotationSet[] rewritten = ArrayUtils.map(DexAnnotationSet[].class, values, mapper);
+ if (rewritten != values) {
+ return new ParameterAnnotationsList(rewritten, missingParameterAnnotations);
+ }
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
new file mode 100644
index 0000000..356f609
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, 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.shaking;
+
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.utils.ArrayUtils;
+
+public class AnnotationFixer {
+
+ private final GraphLense lense;
+
+ public AnnotationFixer(GraphLense lense) {
+ this.lense = lense;
+ }
+
+ public void run(Iterable<DexProgramClass> classes) {
+ for (DexProgramClass clazz : classes) {
+ clazz.annotations = clazz.annotations.rewrite(this::rewriteAnnotation);
+ clazz.forEachMethod(this::processMethod);
+ clazz.forEachField(this::processField);
+ }
+ }
+
+ private void processMethod(DexEncodedMethod method) {
+ method.annotations = method.annotations.rewrite(this::rewriteAnnotation);
+ method.parameterAnnotationsList =
+ method.parameterAnnotationsList.rewrite(
+ dexAnnotationSet -> dexAnnotationSet.rewrite(this::rewriteAnnotation));
+ }
+
+ private void processField(DexEncodedField field) {
+ field.annotations = field.annotations.rewrite(this::rewriteAnnotation);
+ }
+
+ private DexAnnotation rewriteAnnotation(DexAnnotation original) {
+ return original.rewrite(this::rewriteEncodedAnnotation);
+ }
+
+ private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
+ DexEncodedAnnotation rewritten =
+ original.rewrite(lense::lookupType, this::rewriteAnnotationElement);
+ assert rewritten != null;
+ return rewritten;
+ }
+
+ private DexAnnotationElement rewriteAnnotationElement(DexAnnotationElement original) {
+ DexValue rewrittenValue = rewriteValue(original.value);
+ if (rewrittenValue != original.value) {
+ return new DexAnnotationElement(original.name, rewrittenValue);
+ }
+ return original;
+ }
+
+ private DexValue rewriteValue(DexValue value) {
+ if (value.isDexValueType()) {
+ DexType originalType = value.asDexValueType().value;
+ DexType rewrittenType = lense.lookupType(originalType);
+ if (rewrittenType != originalType) {
+ return new DexValueType(rewrittenType);
+ }
+ } else if (value.isDexValueArray()) {
+ DexValue[] originalValues = value.asDexValueArray().getValues();
+ DexValue[] rewrittenValues =
+ ArrayUtils.map(DexValue[].class, originalValues, this::rewriteValue);
+ if (rewrittenValues != originalValues) {
+ return new DexValueArray(rewrittenValues);
+ }
+ }
+ return value;
+ }
+}
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 cadd59c..2cbe484 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -108,11 +108,6 @@
private boolean isAnnotationTypeLive(DexAnnotation annotation) {
DexType annotationType = annotation.annotation.type.toBaseType(appView.dexItemFactory());
- DexClass definition = appView.definitionFor(annotationType);
- // TODO(b/73102187): How to handle annotations without definition.
- if (appView.options().isShrinking() && definition == null) {
- return false;
- }
return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
}
@@ -276,7 +271,8 @@
private DexAnnotationElement rewriteAnnotationElement(
DexType annotationType, DexAnnotationElement original) {
DexClass definition = appView.definitionFor(annotationType);
- // TODO(b/73102187): How to handle annotations without definition.
+ // 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.
if (definition == null) {
return original;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index cd77544..ef9a378 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1454,7 +1454,9 @@
for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
synthesizedBridge.updateMethodSignatures(this::fixupMethod);
}
- return lensBuilder.build(appView, mergedClasses);
+ GraphLense graphLense = lensBuilder.build(appView, mergedClasses);
+ new AnnotationFixer(graphLense).run(appView.appInfo().classes());
+ return graphLense;
}
private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java
new file mode 100644
index 0000000..4e6adad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java
@@ -0,0 +1,181 @@
+// Copyright (c) 2019, 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.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.BaseCompilerCommand;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Function;
+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 LibraryAndMissingAnnotationsTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean includeOnLibraryPath;
+
+ @Parameters(name = "{0}, includeOnLibraryPath: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ private static Function<TestParameters, Path> compilationResults =
+ memoizeFunction(LibraryAndMissingAnnotationsTest::compileLibraryAnnotationToRuntime);
+
+ private static Path compileLibraryAnnotationToRuntime(TestParameters parameters)
+ throws CompilationFailedException, IOException {
+ return testForR8(getStaticTemp(), parameters.getBackend())
+ .addProgramClasses(LibraryAnnotation.class)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(LibraryAnnotation.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ }
+
+ public LibraryAndMissingAnnotationsTest(TestParameters parameters, boolean includeOnLibraryPath) {
+ this.parameters = parameters;
+ this.includeOnLibraryPath = includeOnLibraryPath;
+ }
+
+ @Test
+ public void testMainWithUseR8() throws Exception {
+ runTest(testForR8(parameters.getBackend()), MainWithUse.class);
+ }
+
+ @Test
+ public void testMainWithUseR8Compat() throws Exception {
+ runTest(testForR8Compat(parameters.getBackend()), MainWithUse.class);
+ }
+
+ @Test
+ public void testMainWithUseProguard() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ runTest(testForProguard(), MainWithUse.class);
+ }
+
+ @Test
+ public void testMainWithoutUseR8() throws Exception {
+ runTest(testForR8(parameters.getBackend()), MainWithoutUse.class);
+ }
+
+ @Test
+ public void testMainWithoutUseR8Compat() throws Exception {
+ runTest(testForR8Compat(parameters.getBackend()), MainWithoutUse.class);
+ }
+
+ @Test
+ public void testMainWithoutUseProguard() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ runTest(testForProguard(), MainWithoutUse.class);
+ }
+
+ private <
+ C extends BaseCompilerCommand,
+ B extends BaseCompilerCommand.Builder<C, B>,
+ CR extends TestCompileResult<CR, RR>,
+ RR extends TestRunResult<RR>,
+ T extends TestShrinkerBuilder<C, B, CR, RR, T>>
+ void runTest(TestShrinkerBuilder<C, B, CR, RR, T> builder, Class<?> mainClass)
+ throws Exception {
+ T t =
+ builder
+ .addProgramClasses(Foo.class, mainClass)
+ .addKeepAttributes("*Annotation*")
+ .addLibraryFiles(runtimeJar(parameters))
+ .addKeepClassAndMembersRules(Foo.class)
+ .addKeepRules("-dontwarn " + LibraryAndMissingAnnotationsTest.class.getTypeName())
+ .addKeepMainRule(mainClass)
+ .setMinApi(parameters.getApiLevel());
+ if (includeOnLibraryPath) {
+ t.addLibraryClasses(LibraryAnnotation.class);
+ } else {
+ t.addKeepRules("-dontwarn " + LibraryAnnotation.class.getTypeName());
+ }
+ t.compile()
+ .addRunClasspathFiles(compilationResults.apply(parameters))
+ .run(parameters.getRuntime(), mainClass)
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(Foo.class);
+ assertThat(clazz, isPresent());
+ assertThat(clazz.annotation(LibraryAnnotation.class.getTypeName()), isPresent());
+ MethodSubject foo = clazz.uniqueMethodWithName("foo");
+ assertThat(foo, isPresent());
+ assertThat(foo.annotation(LibraryAnnotation.class.getTypeName()), isPresent());
+ assertFalse(foo.getMethod().parameterAnnotationsList.isEmpty());
+ assertEquals(
+ LibraryAnnotation.class.getTypeName(),
+ foo.getMethod()
+ .parameterAnnotationsList
+ .get(0)
+ .annotations[0]
+ .getAnnotationType()
+ .toSourceString());
+ assertThat(
+ clazz
+ .uniqueFieldWithName("bar")
+ .annotation(LibraryAnnotation.class.getTypeName()),
+ isPresent());
+ })
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @Test
+ public void testMainWithoutUse() {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface LibraryAnnotation {}
+
+ @LibraryAnnotation
+ public static class Foo {
+
+ @LibraryAnnotation public String bar = "Hello World!";
+
+ @LibraryAnnotation
+ public void foo(@LibraryAnnotation String arg) {
+ System.out.println(arg);
+ }
+ }
+
+ public static class MainWithoutUse {
+
+ public static void main(String[] args) {
+ Foo foo = new Foo();
+ foo.foo(foo.bar);
+ }
+ }
+
+ public static class MainWithUse {
+ public static void main(String[] args) {
+ if (args.length > 0 && args[0].equals(LibraryAnnotation.class.getTypeName())) {
+ System.out.print("This will never be printed");
+ }
+ Foo foo = new Foo();
+ foo.foo(foo.bar);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
new file mode 100644
index 0000000..45b29f7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2018, 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.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+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 PrunedOrMergedAnnotationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public PrunedOrMergedAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRewritingInFactory()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(PrunedOrMergedAnnotationTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepAttributes("*Annotation*")
+ .addKeepClassAndMembersRules(Factory.class)
+ .enableInliningAnnotations()
+ .enableClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello", "World!")
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ DexType mergedType = inspector.clazz(B.class).getDexClass().type;
+ ClassSubject classC = inspector.clazz(C.class);
+ assertThat(classC, isPresent());
+ DexEncodedAnnotation annotation =
+ classC.annotation(Factory.class.getTypeName()).getAnnotation();
+ assertTrue(valueIsDexType(mergedType, annotation.elements[0].value));
+ assertTrue(
+ Arrays.stream(annotation.elements[1].value.asDexValueArray().getValues())
+ .allMatch(value -> valueIsDexType(mergedType, value)));
+ // Check that method parameter annotations are rewritten as well.
+ DexEncodedMethod method = inspector.clazz(Main.class).mainMethod().getMethod();
+ DexAnnotationSet annotationSet = method.parameterAnnotationsList.get(0);
+ DexEncodedAnnotation parameterAnnotation = annotationSet.annotations[0].annotation;
+ assertTrue(valueIsDexType(mergedType, parameterAnnotation.elements[0].value));
+ });
+ }
+
+ private boolean valueIsDexType(DexType type, DexValue value) {
+ assertTrue(value.isDexValueType());
+ assertEquals(type, value.asDexValueType().value);
+ return true;
+ }
+
+ public @interface Factory {
+
+ Class<?> extending() default Object.class;
+
+ Class<?>[] other() default Object[].class;
+ }
+
+ public static class A {}
+
+ @NeverClassInline
+ public static class B extends A {
+ @NeverInline
+ public void world() {
+ System.out.println("World!");
+ }
+ }
+
+ @Factory(
+ extending = A.class,
+ other = {A.class, B.class})
+ public static class C {
+ @NeverInline
+ public static void hello() {
+ System.out.println("Hello");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(@Factory(extending = A.class) String[] args) {
+ C.hello();
+ new B().world();
+ }
+ }
+}