Test kept-graph in presence of annotations
Fixes: 190598684
Change-Id: I7069aefc45b84cc937b103f93ccad13be78ff5b8
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 6146c2c..fabdd04 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -333,13 +333,17 @@
}
}
+ private boolean hasKeptGraphConsumer() {
+ return keptGraphConsumer != null;
+ }
+
private boolean skipReporting(KeepReason reason) {
assert reason != null;
if (reason == KeepReasonWitness.INSTANCE) {
return true;
}
assert getSourceNode(reason) != null;
- return keptGraphConsumer == null;
+ return !hasKeptGraphConsumer();
}
public KeepReasonWitness registerInterface(DexProgramClass iface, KeepReason reason) {
@@ -359,11 +363,15 @@
public KeepReasonWitness registerAnnotation(
DexAnnotation annotation, ProgramDefinition annotatedItem) {
- KeepReason reason = KeepReason.annotatedOn(annotatedItem.getDefinition());
- if (skipReporting(reason)) {
- return KeepReasonWitness.INSTANCE;
+ if (hasKeptGraphConsumer()) {
+ registerEdge(
+ getAnnotationGraphNode(annotation, annotatedItem),
+ KeepReason.annotatedOn(annotatedItem.getDefinition()));
+ registerEdge(
+ getClassGraphNode(annotation.getAnnotationType()),
+ KeepReason.referencedInAnnotation(annotation, annotatedItem));
}
- return registerEdge(getAnnotationGraphNode(annotation, annotatedItem), reason);
+ return KeepReasonWitness.INSTANCE;
}
public KeepReasonWitness registerMethod(DexEncodedMethod method, KeepReason reason) {
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index a1a5f88..82d3e2a 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -110,6 +110,12 @@
return self();
}
+ public final <E extends Throwable> R8TestCompileResult inspectGraph(
+ ThrowingConsumer<GraphInspector, E> consumer) throws IOException, E {
+ consumer.accept(graphInspector());
+ return self();
+ }
+
public GraphInspector graphInspector() throws IOException {
assert graphConsumer != null;
return new GraphInspector(graphConsumer, inspector());
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReferenceInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReferenceInAnnotationTest.java
new file mode 100644
index 0000000..137706a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReferenceInAnnotationTest.java
@@ -0,0 +1,103 @@
+// 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.keptgraph;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+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;
+
+@RunWith(Parameterized.class)
+public class KeptByReferenceInAnnotationTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRuntimeVisibleAnnotations()
+ .enableGraphInspector()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectGraph(
+ inspector -> {
+ QueryNode keepMainMethodRule = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ QueryNode mainClassNode =
+ inspector
+ .clazz(Main.class)
+ .assertPresent()
+ .assertNotRenamed()
+ .assertKeptBy(keepMainMethodRule);
+
+ // The main() method is kept by the -keep rule.
+ QueryNode mainMethodNode =
+ inspector
+ .method(MethodReferenceUtils.mainMethod(Main.class))
+ .assertPresent()
+ .assertNotRenamed()
+ .assertKeptBy(keepMainMethodRule);
+
+ // MyAnnotation is referenced from main() and is also used to annotate class Main.
+ QueryNode annotationNode =
+ inspector
+ .clazz(MyAnnotation.class)
+ .assertPresent()
+ .assertRenamed()
+ .assertKeptBy(mainMethodNode);
+ annotationNode.assertKeptByReferenceInAnnotationOn(annotationNode, mainClassNode);
+
+ // ReferencedInAnnotation is referenced from inside the annotation on class Main.
+ inspector
+ .clazz(ReferencedInAnnotation.class)
+ .assertPresent()
+ .assertRenamed()
+ .assertKeptByReferenceInAnnotationOn(annotationNode, mainClassNode);
+
+ // Check the presence of an edge from main() to the annotation node.
+ inspector
+ .annotation(MyAnnotation.class, mainClassNode)
+ .assertPresent()
+ .assertKeptByAnnotationOn(mainClassNode);
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .apply(
+ result ->
+ result.assertSuccessWithOutputLines(
+ result.inspector().clazz(ReferencedInAnnotation.class).getFinalName()));
+ }
+
+ @MyAnnotation(ReferencedInAnnotation.class)
+ static class Main {
+
+ public static void main(String[] args) {
+ MyAnnotation annotation = Main.class.getAnnotation(MyAnnotation.class);
+ System.out.println(annotation.value().getName());
+ }
+ }
+
+ static class ReferencedInAnnotation {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface MyAnnotation {
+
+ Class<?> value();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index e51c162..45ffbf2 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -26,10 +26,12 @@
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableSet;
+import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -53,6 +55,10 @@
new EdgeKindPredicate(EdgeKind.ReflectiveUseFrom);
public static final EdgeKindPredicate isLibraryMethod =
new EdgeKindPredicate(EdgeKind.IsLibraryMethod);
+ public static final EdgeKindPredicate isAnnotatedOn =
+ new EdgeKindPredicate(EdgeKind.AnnotatedOn);
+ public static final EdgeKindPredicate isReferencedInAnnotation =
+ new EdgeKindPredicate(EdgeKind.ReferencedInAnnotation);
public static final EdgeKindPredicate overriding =
new EdgeKindPredicate(EdgeKind.OverridingMethod);
public static final EdgeKindPredicate compatibilityRule =
@@ -148,6 +154,11 @@
public abstract boolean isPureCompatKeptBy(QueryNode node);
+ public abstract boolean isKeptByAnnotationOn(QueryNode annotatedNode);
+
+ public abstract boolean isKeptByReferenceInAnnotationOn(
+ QueryNode annotationNode, QueryNode annotatedNode);
+
public abstract boolean isKeptByLibraryMethod(QueryNode node);
public abstract boolean isSatisfiedBy(QueryNode... nodes);
@@ -274,14 +285,46 @@
if (isSatisfiedBy(nodes)) {
return this;
}
- QueryNodeImpl impl = (QueryNodeImpl) this;
+ QueryNodeImpl<?> impl = (QueryNodeImpl<?>) this;
impl.runSatisfiedBy(Assert::fail, nodes);
throw new Unreachable();
}
+ public QueryNode assertKeptByAnnotationOn(QueryNode annotatedNode) {
+ assertTrue(
+ "Invalid call to assertKeptByAnnotation with: " + annotatedNode.getNodeDescription(),
+ annotatedNode.isPresent());
+ assertTrue(
+ errorMessage(
+ "kept by annotation on " + annotatedNode.getNodeDescription(),
+ "was not kept by an annotation"),
+ isKeptByAnnotationOn(annotatedNode));
+ return this;
+ }
+
+ public QueryNode assertKeptByReferenceInAnnotationOn(
+ QueryNode annotationNode, QueryNode annotatedNode) {
+ assertTrue(
+ "Invalid call to assertKeptByAnnotation with: " + annotationNode.getNodeDescription(),
+ annotationNode.isPresent());
+ assertTrue(
+ "Invalid call to assertKeptByAnnotation with: " + annotatedNode.getNodeDescription(),
+ annotatedNode.isPresent());
+ assertTrue(
+ errorMessage(
+ "kept by annotation "
+ + annotationNode.getNodeDescription()
+ + " on "
+ + annotatedNode.getNodeDescription(),
+ "was not kept by an annotation"),
+ isKeptByReferenceInAnnotationOn(annotationNode, annotatedNode));
+ return this;
+ }
+
public QueryNode assertKeptByLibraryMethod(QueryNode node) {
assertTrue(
- "Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
+ "Invalid call to assertKeptByLibraryMethod with: " + node.getNodeDescription(),
+ node.isPresent());
assertTrue(
errorMessage(
"kept by library method on " + node.getNodeDescription(),
@@ -376,14 +419,27 @@
}
@Override
+ public boolean isKeptByAnnotationOn(QueryNode annotatedNode) {
+ fail("Invalid call to isKeptByAnnotationOn on " + getNodeDescription());
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isKeptByReferenceInAnnotationOn(
+ QueryNode annotationNode, QueryNode annotatedNode) {
+ fail("Invalid call to isKeptByReferenceInAnnotationOn on " + getNodeDescription());
+ throw new Unreachable();
+ }
+
+ @Override
public boolean isKeptByLibraryMethod(QueryNode node) {
- fail("Invalid call to isKeptByLibrary on " + getNodeDescription());
+ fail("Invalid call to isKeptByLibraryMethod on " + getNodeDescription());
throw new Unreachable();
}
@Override
public boolean isSatisfiedBy(QueryNode... nodes) {
- fail("Invalid call to isTriggeredBy on " + getNodeDescription());
+ fail("Invalid call to isSatisfiedBy on " + getNodeDescription());
throw new Unreachable();
}
}
@@ -391,19 +447,19 @@
// Class representing a point in the kept-graph structure.
// The purpose of this class is to tersely specify what relationships are expected between nodes,
// thus most methods will throw assertion errors if the predicate is false.
- private static class QueryNodeImpl extends QueryNode {
+ private static class QueryNodeImpl<T extends GraphNode> extends QueryNode {
private final GraphInspector inspector;
- private final GraphNode graphNode;
+ private final T graphNode;
- public QueryNodeImpl(GraphInspector inspector, GraphNode graphNode) {
+ public QueryNodeImpl(GraphInspector inspector, T graphNode) {
this.inspector = inspector;
this.graphNode = graphNode;
}
@Override
public boolean equals(Object obj) {
- return obj instanceof QueryNodeImpl && graphNode.equals(((QueryNodeImpl) obj).graphNode);
+ return obj instanceof QueryNodeImpl && graphNode.equals(((QueryNodeImpl<?>) obj).graphNode);
}
@Override
@@ -484,7 +540,7 @@
if (!(node instanceof QueryNodeImpl)) {
return false;
}
- QueryNodeImpl impl = (QueryNodeImpl) node;
+ QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
return filterSources((source, infos) -> impl.graphNode == source).findFirst().isPresent();
}
@@ -493,7 +549,7 @@
if (!(node instanceof QueryNodeImpl)) {
return false;
}
- QueryNodeImpl impl = (QueryNodeImpl) node;
+ QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
return filterSources(
(source, infos) ->
impl.graphNode == source && EdgeKindPredicate.compatibilityRule.test(infos))
@@ -506,22 +562,63 @@
if (!isCompatKeptBy(node)) {
return false;
}
- QueryNodeImpl impl = (QueryNodeImpl) node;
+ QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
return filterSources((source, infos) -> impl.graphNode != source).count() == 0;
}
@Override
+ public boolean isKeptByAnnotationOn(QueryNode annotatedNode) {
+ // This should be an annotation node or a class node which it an annotation.
+ assert graphNode instanceof AnnotationGraphNode || graphNode instanceof ClassGraphNode;
+
+ // The annotated node (class, field or method) should be present.
+ assert annotatedNode.isPresent();
+ QueryNodeImpl<?> annotatedNodeImpl = (QueryNodeImpl<?>) annotatedNode;
+ assert annotatedNodeImpl.graphNode instanceof ClassGraphNode
+ || annotatedNodeImpl.graphNode instanceof FieldGraphNode
+ || annotatedNodeImpl.graphNode instanceof MethodGraphNode;
+
+ return hasSource(
+ (source, infos) ->
+ source.equals(annotatedNodeImpl.graphNode)
+ && EdgeKindPredicate.isAnnotatedOn.test(infos));
+ }
+
+ @Override
+ public boolean isKeptByReferenceInAnnotationOn(
+ QueryNode annotationNode, QueryNode annotatedNode) {
+ // The annotation node should be present.
+ assert annotationNode.isPresent();
+ QueryNodeImpl<ClassGraphNode> annotationNodeImpl =
+ (QueryNodeImpl<ClassGraphNode>) annotationNode;
+
+ // The annotated node (class, field or method) should be present.
+ assert annotatedNode.isPresent();
+ QueryNodeImpl<?> annotatedNodeImpl = (QueryNodeImpl<?>) annotatedNode;
+ assert annotatedNodeImpl.graphNode instanceof ClassGraphNode
+ || annotatedNodeImpl.graphNode instanceof FieldGraphNode
+ || annotatedNodeImpl.graphNode instanceof MethodGraphNode;
+
+ AnnotationGraphNode expectedSource =
+ new AnnotationGraphNode(annotatedNodeImpl.graphNode, annotationNodeImpl.graphNode);
+
+ return hasSource(
+ (source, infos) ->
+ source.equals(expectedSource)
+ && EdgeKindPredicate.isReferencedInAnnotation.test(infos));
+ }
+
+ @Override
public boolean isKeptByLibraryMethod(QueryNode node) {
assert graphNode instanceof MethodGraphNode;
- if (!(node instanceof QueryNodeImpl)) {
+ if (!node.isPresent()) {
return false;
}
- QueryNodeImpl impl = (QueryNodeImpl) node;
- return filterSources(
- (source, infos) ->
- impl.graphNode == source && EdgeKindPredicate.isLibraryMethod.test(infos))
- .findFirst()
- .isPresent();
+ assert node instanceof QueryNodeImpl;
+ QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
+ return hasSource(
+ (source, infos) ->
+ impl.graphNode == source && EdgeKindPredicate.isLibraryMethod.test(infos));
}
@Override
@@ -544,7 +641,7 @@
+ node.getNodeDescription());
return;
}
- QueryNodeImpl impl = (QueryNodeImpl) node;
+ QueryNodeImpl<?> impl = (QueryNodeImpl<?>) node;
if (!filterSources((source, infos) -> impl.graphNode == source).findFirst().isPresent()) {
onError.accept(
"Expected to find dependency from precondtion to dependent rule, but could not. "
@@ -564,7 +661,7 @@
if (nodes.length != preconditions.size()) {
for (GraphNode precondition : preconditions) {
if (Arrays.stream(nodes)
- .noneMatch(node -> ((QueryNodeImpl) node).graphNode == precondition)) {
+ .noneMatch(node -> ((QueryNodeImpl<?>) node).graphNode == precondition)) {
onError.accept("Unexpected item in precondtions: " + precondition.toString());
return;
}
@@ -614,6 +711,10 @@
.filter(e -> test.test(e.getKey(), e.getValue()))
.map(Entry::getKey);
}
+
+ private boolean hasSource(BiPredicate<GraphNode, Set<GraphEdgeInfo>> test) {
+ return filterSources(test).findAny().isPresent();
+ }
}
private final CollectingGraphConsumer consumer;
@@ -753,6 +854,52 @@
return "rule@" + origin + ":" + new TextPosition(0, line, column);
}
+ public QueryNode annotation(Class<? extends Annotation> clazz, QueryNode annotatedNode) {
+ return annotation(Reference.classFromClass(clazz), annotatedNode);
+ }
+
+ public QueryNode annotation(ClassReference annotationClassReference, QueryNode annotatedNode) {
+ // The annotation node (class, field or method) should be present.
+ assert annotatedNode.isPresent();
+ QueryNodeImpl<?> annotatedNodeImpl = (QueryNodeImpl<?>) annotatedNode;
+ assert annotatedNodeImpl.graphNode instanceof ClassGraphNode
+ || annotatedNodeImpl.graphNode instanceof FieldGraphNode
+ || annotatedNodeImpl.graphNode instanceof MethodGraphNode;
+
+ Map<ClassReference, AnnotationGraphNode> annotationsOnAnnotatedItem;
+ if (annotatedNodeImpl.graphNode instanceof ClassGraphNode) {
+ annotationsOnAnnotatedItem =
+ classAnnotations.get(((ClassGraphNode) annotatedNodeImpl.graphNode).getReference());
+ } else if (annotatedNodeImpl.graphNode instanceof FieldGraphNode) {
+ annotationsOnAnnotatedItem =
+ fieldAnnotations.get(((FieldGraphNode) annotatedNodeImpl.graphNode).getReference());
+ } else {
+ assert annotatedNodeImpl.graphNode instanceof MethodGraphNode;
+ annotationsOnAnnotatedItem =
+ methodAnnotations.get(((MethodGraphNode) annotatedNodeImpl.graphNode).getReference());
+ }
+
+ if (annotationsOnAnnotatedItem == null) {
+ return new AbsentQueryNode(
+ "Node " + annotatedNode.getNodeDescription() + " has no annotations");
+ }
+
+ AnnotationGraphNode annotationGraphNode =
+ annotationsOnAnnotatedItem.get(annotationClassReference);
+ if (annotationGraphNode == null) {
+ return new AbsentQueryNode(
+ "Node "
+ + annotatedNode.getNodeDescription()
+ + " has no annotation of type "
+ + annotationClassReference.getTypeName());
+ }
+ return new QueryNodeImpl<>(this, annotationGraphNode);
+ }
+
+ public QueryNode clazz(Class<?> clazz) {
+ return clazz(Reference.classFromClass(clazz));
+ }
+
public QueryNode clazz(ClassReference clazz) {
return getQueryNode(classes.get(clazz), clazz.toString());
}