Add keptBy predicate and support for referencing a keep rule.
Change-Id: I24cfd768742fd578121d28785df12abf757034c9
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedClassTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedClassTestRunner.java
index ea61c9a..c758507 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedClassTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedClassTestRunner.java
@@ -7,9 +7,11 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -50,10 +52,11 @@
// The only root should be the keep annotation rule.
assertEquals(1, inspector.getRoots().size());
+ QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
// Check that the call chain goes from root -> main(unchanged) -> bar(renamed).
inspector.method(barMethod).assertRenamed().assertInvokedFrom(mainMethod);
- inspector.method(mainMethod).assertNotRenamed().assertKeptByRootRule();
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(root);
// Check baz is removed.
inspector.method(bazMethod).assertAbsent();
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTestRunner.java
index 8ac68f9..04d5706 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTestRunner.java
@@ -7,9 +7,11 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -50,10 +52,11 @@
// The only root should be the keep main-method rule.
assertEquals(1, inspector.getRoots().size());
+ QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
// Check that the call chain goes from root -> main(unchanged) -> bar(renamed).
inspector.method(barMethod).assertRenamed().assertInvokedFrom(mainMethod);
- inspector.method(mainMethod).assertNotRenamed().assertKeptByRootRule();
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(root);
// Check baz is removed.
inspector.method(bazMethod).assertAbsent();
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 b1a5e33..b8ce79a 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
@@ -13,7 +13,12 @@
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
@@ -53,19 +58,21 @@
}
}
- public interface QueryNode {
- boolean isPresent();
+ public abstract static class QueryNode {
- boolean isRenamed();
+ abstract boolean isPresent();
- boolean isInvokedFrom(MethodReference method);
+ abstract boolean isRoot();
- // TODO(b/120959039): Add support for referencing keep rules.
- boolean isKeptByRootRule();
+ abstract boolean isRenamed();
- String getNodeDescription();
+ abstract boolean isInvokedFrom(MethodReference method);
- default String errorMessage(String expected, String actual) {
+ abstract boolean isKeptBy(QueryNode node);
+
+ abstract String getNodeDescription();
+
+ protected String errorMessage(String expected, String actual) {
return "Failed query on "
+ getNodeDescription()
+ ", expected: "
@@ -74,40 +81,46 @@
+ actual;
}
- default QueryNode assertPresent() {
+ public QueryNode assertPresent() {
assertTrue(errorMessage("present", "absent"), isPresent());
return this;
}
- default QueryNode assertAbsent() {
+ public QueryNode assertAbsent() {
assertTrue(errorMessage("absent", "present"), !isPresent());
return this;
}
- default QueryNode assertRenamed() {
+ public QueryNode assertRoot() {
+ assertTrue(errorMessage("root", "non-root"), isRoot());
+ return this;
+ }
+
+ public QueryNode assertRenamed() {
assertTrue(errorMessage("renamed", "not-renamed"), isRenamed());
return this;
}
- default QueryNode assertNotRenamed() {
+ public QueryNode assertNotRenamed() {
assertTrue(errorMessage("not-renamed", "renamed"), !isRenamed());
return this;
}
- default QueryNode assertInvokedFrom(MethodReference method) {
+ public QueryNode assertInvokedFrom(MethodReference method) {
assertTrue(
errorMessage("invokation from " + method.toString(), "none"), isInvokedFrom(method));
return this;
}
- // TODO(b/120959039): Add support for referencing keep rules.
- default QueryNode assertKeptByRootRule() {
- assertTrue(errorMessage("kept by a root rule", "none"), isKeptByRootRule());
+ public QueryNode assertKeptBy(QueryNode node) {
+ assertTrue("Invalid call to assertKeptBy with: " + node.getNodeDescription(),
+ node.isPresent());
+ assertTrue(errorMessage("kept by " + getNodeDescription(), "none"), isKeptBy(node));
return this;
}
}
- private static class AbsentQueryNode implements QueryNode {
+ private static class AbsentQueryNode extends QueryNode {
private final String failedQueryNodeDescription;
public AbsentQueryNode(String failedQueryNodeDescription) {
@@ -125,6 +138,12 @@
}
@Override
+ boolean isRoot() {
+ fail("Invalid call to isRoot on " + getNodeDescription());
+ throw new Unreachable();
+ }
+
+ @Override
public boolean isRenamed() {
fail("Invalid call to isRenamed on " + getNodeDescription());
throw new Unreachable();
@@ -137,8 +156,8 @@
}
@Override
- public boolean isKeptByRootRule() {
- fail("Invalid call to isKeptByRule on " + getNodeDescription());
+ public boolean isKeptBy(QueryNode node) {
+ fail("Invalid call to isKeptBy on " + getNodeDescription());
throw new Unreachable();
}
}
@@ -146,7 +165,7 @@
// 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 implements QueryNode {
+ private static class QueryNodeImpl extends QueryNode {
private final GraphInspector inspector;
private final GraphNode graphNode;
@@ -167,6 +186,11 @@
}
@Override
+ boolean isRoot() {
+ return inspector.roots.contains(graphNode);
+ }
+
+ @Override
public boolean isRenamed() {
if (graphNode instanceof ClassGraphNode) {
ClassGraphNode classNode = (ClassGraphNode) this.graphNode;
@@ -196,12 +220,12 @@
}
@Override
- public boolean isKeptByRootRule() {
- return filterSources(
- (node, infos) ->
- inspector.getRoots().contains(node) && EdgeKindPredicate.keepRule.test(infos))
- .findFirst()
- .isPresent();
+ public boolean isKeptBy(QueryNode node) {
+ if (!(node instanceof QueryNodeImpl)) {
+ return false;
+ }
+ QueryNodeImpl impl = (QueryNodeImpl) node;
+ return filterSources((source, infos) -> impl.graphNode == source).findFirst().isPresent();
}
private Stream<GraphNode> filterSources(BiPredicate<GraphNode, Set<GraphEdgeInfo>> test) {
@@ -217,6 +241,7 @@
private final CodeInspector inspector;
private final Set<GraphNode> roots = new HashSet<>();
+ private final Set<KeepRuleGraphNode> rules = new HashSet<>();
private final Map<ClassReference, ClassGraphNode> classes;
private final Map<MethodReference, MethodGraphNode> methods;
private final Map<FieldReference, FieldGraphNode> fields;
@@ -240,6 +265,9 @@
} else if (target instanceof FieldGraphNode) {
FieldGraphNode node = (FieldGraphNode) target;
fields.put(node.getReference(), node);
+ } else if (target instanceof KeepRuleGraphNode) {
+ KeepRuleGraphNode node = (KeepRuleGraphNode) target;
+ rules.add(node);
} else {
throw new Unimplemented("Incomplet support for graph node type: " + target.getClass());
}
@@ -248,6 +276,9 @@
if (!targets.contains(source)) {
roots.add(source);
}
+ if (source instanceof KeepRuleGraphNode) {
+ rules.add((KeepRuleGraphNode) source);
+ }
}
}
}
@@ -256,6 +287,36 @@
return Collections.unmodifiableSet(roots);
}
+ public QueryNode rule(Origin origin, int line, int column) {
+ String ruleReferenceString = getReferenceStringForRule(origin, line, column);
+ KeepRuleGraphNode found = null;
+ for (KeepRuleGraphNode rule : rules) {
+ if (rule.getOrigin().equals(origin)) {
+ Position position = rule.getPosition();
+ if (position instanceof TextRange) {
+ TextRange range = (TextRange) position;
+ if (range.getStart().getLine() == line && range.getStart().getColumn() == column) {
+ if (found != null) {
+ fail(
+ "Found two matching rules at "
+ + ruleReferenceString
+ + ": "
+ + found
+ + " and "
+ + rule);
+ }
+ found = rule;
+ }
+ }
+ }
+ }
+ return getQueryNode(found, ruleReferenceString);
+ }
+
+ private static String getReferenceStringForRule(Origin origin, int line, int column) {
+ return "rule@" + origin + ":" + new TextPosition(0, line, column);
+ }
+
public QueryNode method(MethodReference method) {
return getQueryNode(methods.get(method), method.toString());
}