blob: 1d5301315e343b8dd8326b271169658925119b72 [file] [log] [blame]
// 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.utils.graphinspector;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
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;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class GraphInspector {
// Convenience predicates.
public static class EdgeKindPredicate implements Predicate<Set<GraphEdgeInfo>> {
public static final EdgeKindPredicate keepRule = new EdgeKindPredicate(EdgeKind.KeepRule);
public static final EdgeKindPredicate invokedFrom = new EdgeKindPredicate(EdgeKind.InvokedFrom);
public static final EdgeKindPredicate reflectedFrom =
new EdgeKindPredicate(EdgeKind.ReflectiveUseFrom);
private final EdgeKind edgeKind;
public EdgeKindPredicate(EdgeKind edgeKind) {
this.edgeKind = edgeKind;
}
@Override
public boolean test(Set<GraphEdgeInfo> infos) {
for (GraphEdgeInfo info : infos) {
if (info.edgeKind() == edgeKind) {
return true;
}
}
return false;
}
}
public abstract static class QueryNode {
abstract boolean isPresent();
abstract boolean isRoot();
abstract boolean isRenamed();
abstract boolean isInvokedFrom(MethodReference method);
abstract boolean isReflectedFrom(MethodReference method);
abstract boolean isKeptBy(QueryNode node);
abstract String getNodeDescription();
protected String errorMessage(String expected, String actual) {
return "Failed query on "
+ getNodeDescription()
+ ", expected: "
+ expected
+ ", got: "
+ actual;
}
public QueryNode assertPresent() {
assertTrue(errorMessage("present", "absent"), isPresent());
return this;
}
public QueryNode assertAbsent() {
assertTrue(errorMessage("absent", "present"), !isPresent());
return this;
}
public QueryNode assertRoot() {
assertTrue(errorMessage("root", "non-root"), isRoot());
return this;
}
public QueryNode assertRenamed() {
assertTrue(errorMessage("renamed", "not-renamed"), isRenamed());
return this;
}
public QueryNode assertNotRenamed() {
assertTrue(errorMessage("not-renamed", "renamed"), !isRenamed());
return this;
}
public QueryNode assertInvokedFrom(MethodReference method) {
assertTrue(
errorMessage("invocation from " + method.toString(), "none"), isInvokedFrom(method));
return this;
}
public QueryNode assertNotInvokedFrom(MethodReference method) {
assertFalse(
errorMessage("no invocation from " + method.toString(), "invoke"), isInvokedFrom(method));
return this;
}
public QueryNode assertReflectedFrom(MethodReference method) {
assertTrue(
errorMessage("reflection from " + method.toString(), "none"), isReflectedFrom(method));
return this;
}
public QueryNode assertNotReflectedFrom(MethodReference method) {
assertFalse(
errorMessage("no reflection from " + method.toString(), "reflection"),
isReflectedFrom(method));
return this;
}
public QueryNode assertKeptBy(QueryNode node) {
assertTrue(
"Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
assertTrue(
errorMessage("kept by " + node.getNodeDescription(), "was not kept by it"),
isKeptBy(node));
return this;
}
public QueryNode assertNotKeptBy(QueryNode node) {
assertTrue(
"Invalid call to assertNotKeptBy with: " + node.getNodeDescription(), node.isPresent());
assertFalse(
errorMessage("not kept by " + node.getNodeDescription(), "was kept by it"),
isKeptBy(node));
return this;
}
}
private static class AbsentQueryNode extends QueryNode {
private final String failedQueryNodeDescription;
public AbsentQueryNode(String failedQueryNodeDescription) {
this.failedQueryNodeDescription = failedQueryNodeDescription;
}
@Override
public String getNodeDescription() {
return "absent node: " + failedQueryNodeDescription;
}
@Override
public boolean isPresent() {
return false;
}
@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();
}
@Override
public boolean isInvokedFrom(MethodReference method) {
fail("Invalid call to isInvokedFrom on " + getNodeDescription());
throw new Unreachable();
}
@Override
public boolean isReflectedFrom(MethodReference method) {
fail("Invalid call to isReflectedFrom on " + getNodeDescription());
throw new Unreachable();
}
@Override
public boolean isKeptBy(QueryNode node) {
fail("Invalid call to isKeptBy on " + getNodeDescription());
throw new Unreachable();
}
}
// 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 final GraphInspector inspector;
private final GraphNode graphNode;
public QueryNodeImpl(GraphInspector inspector, GraphNode graphNode) {
this.inspector = inspector;
this.graphNode = graphNode;
}
@Override
public String getNodeDescription() {
return graphNode.toString();
}
@Override
public boolean isPresent() {
return true;
}
@Override
boolean isRoot() {
return inspector.roots.contains(graphNode);
}
@Override
public boolean isRenamed() {
if (graphNode instanceof ClassGraphNode) {
ClassGraphNode classNode = (ClassGraphNode) this.graphNode;
return inspector.inspector.clazz(classNode.getReference()).isRenamed();
} else if (graphNode instanceof MethodGraphNode) {
MethodGraphNode methodNode = (MethodGraphNode) this.graphNode;
return inspector.inspector.method(methodNode.getReference()).isRenamed();
} else if (graphNode instanceof FieldGraphNode) {
FieldGraphNode fieldNode = (FieldGraphNode) this.graphNode;
return inspector.inspector.field(fieldNode.getReference()).isRenamed();
} else {
fail("Invalid call to isRenamed on " + getNodeDescription());
throw new Unreachable();
}
}
@Override
public boolean isInvokedFrom(MethodReference method) {
GraphNode sourceMethod = inspector.methods.get(method);
if (sourceMethod == null) {
return false;
}
return filterSources(
(node, infos) -> node == sourceMethod && EdgeKindPredicate.invokedFrom.test(infos))
.findFirst()
.isPresent();
}
@Override
public boolean isReflectedFrom(MethodReference method) {
GraphNode sourceMethod = inspector.methods.get(method);
if (sourceMethod == null) {
return false;
}
return filterSources(
(node, infos) -> node == sourceMethod && EdgeKindPredicate.reflectedFrom.test(infos))
.findFirst()
.isPresent();
}
@Override
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) {
Map<GraphNode, Set<GraphEdgeInfo>> sources =
inspector.consumer.getSourcesTargeting(graphNode);
return sources.entrySet().stream()
.filter(e -> test.test(e.getKey(), e.getValue()))
.map(Entry::getKey);
}
}
private final CollectingGraphConsumer consumer;
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;
public GraphInspector(CollectingGraphConsumer consumer, CodeInspector inspector) {
this.consumer = consumer;
this.inspector = inspector;
Set<GraphNode> targets = consumer.getTargets();
classes = new IdentityHashMap<>(targets.size());
methods = new IdentityHashMap<>(targets.size());
fields = new IdentityHashMap<>(targets.size());
for (GraphNode target : targets) {
if (target instanceof ClassGraphNode) {
ClassGraphNode node = (ClassGraphNode) target;
classes.put(node.getReference(), node);
} else if (target instanceof MethodGraphNode) {
MethodGraphNode node = (MethodGraphNode) target;
methods.put(node.getReference(), node);
} 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());
}
Map<GraphNode, Set<GraphEdgeInfo>> sources = consumer.getSourcesTargeting(target);
for (GraphNode source : sources.keySet()) {
if (!targets.contains(source)) {
roots.add(source);
}
if (source instanceof KeepRuleGraphNode) {
rules.add((KeepRuleGraphNode) source);
}
}
}
}
public Set<GraphNode> getRoots() {
return Collections.unmodifiableSet(roots);
}
public QueryNode rule(String ruleContent) {
KeepRuleGraphNode found = null;
for (KeepRuleGraphNode rule : rules) {
if (rule.getContent().equals(ruleContent)) {
if (found != null) {
fail("Found two matching rules matching " + ruleContent + ": " + found + " and " + rule);
}
found = rule;
}
}
return getQueryNode(found, ruleContent);
}
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 clazz(ClassReference clazz) {
return getQueryNode(classes.get(clazz), clazz.toString());
}
public QueryNode method(MethodReference method) {
return getQueryNode(methods.get(method), method.toString());
}
public QueryNode field(FieldReference field) {
return getQueryNode(fields.get(field), field.toString());
}
private QueryNode getQueryNode(GraphNode node, String absentString) {
return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl(this, node);
}
}