blob: 1099f6221259172dc72a956688bb11623d96d7c9 [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.shaking;
import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
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.references.TypeReference;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Implementation of the whyareyoukeeping rule using an R8 kept-graph consumer.
*
* <p>This class is not intended for public use and clients should define their own consumer
* instead.
*/
public class WhyAreYouKeepingConsumer extends CollectingGraphConsumer {
// Single-linked path description when BF searching for a path.
private static class GraphPath {
final GraphNode node;
final GraphPath path;
public GraphPath(GraphNode node, GraphPath path) {
assert node != null;
this.node = node;
this.path = path;
}
}
public WhyAreYouKeepingConsumer(GraphConsumer subConsumer) {
super(subConsumer);
}
public ClassGraphNode getClassNode(ClassReference clazz) {
for (GraphNode node : getTargets()) {
if (node instanceof ClassGraphNode && ((ClassGraphNode) node).getReference() == clazz) {
return (ClassGraphNode) node;
}
}
return null;
}
public MethodGraphNode getMethodNode(MethodReference method) {
for (GraphNode node : getTargets()) {
if (node instanceof MethodGraphNode && ((MethodGraphNode) node).getReference() == method) {
return (MethodGraphNode) node;
}
}
return null;
}
public FieldGraphNode getFieldNode(FieldReference field) {
for (GraphNode node : getTargets()) {
if (node instanceof FieldGraphNode && ((FieldGraphNode) node).getReference() == field) {
return (FieldGraphNode) node;
}
}
return null;
}
public void printWhyAreYouKeeping(ClassReference reference, PrintStream out) {
ClassGraphNode node = getClassNode(reference);
printWhyAreYouKeeping(node != null ? node : new ClassGraphNode(false, reference), out);
}
public void printWhyAreYouKeeping(MethodReference reference, PrintStream out) {
MethodGraphNode node = getMethodNode(reference);
printWhyAreYouKeeping(node != null ? node : new MethodGraphNode(false, reference), out);
}
public void printWhyAreYouKeeping(FieldReference reference, PrintStream out) {
FieldGraphNode node = getFieldNode(reference);
printWhyAreYouKeeping(node != null ? node : new FieldGraphNode(false, reference), out);
}
public void printWhyAreYouKeeping(GraphNode node, PrintStream out) {
Formatter formatter = new Formatter(out);
List<Pair<GraphNode, GraphEdgeInfo>> path = findShortestPathTo(node);
if (path == null) {
printNothingKeeping(node, out);
return;
}
formatter.startItem(getNodeString(node));
for (int i = path.size() - 1; i >= 0; i--) {
Pair<GraphNode, GraphEdgeInfo> edge = path.get(i);
printEdge(edge.getFirst(), edge.getSecond(), formatter);
}
formatter.endItem();
}
private void printNothingKeeping(GraphNode node, PrintStream out) {
out.print("Nothing is keeping ");
out.println(getNodeString(node));
}
private void printNothingKeeping(ClassReference clazz, PrintStream out) {
out.print("Nothing is keeping ");
out.println(DescriptorUtils.descriptorToJavaType(clazz.getDescriptor()));
}
private List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
if (node == null) {
return null;
}
Map<GraphNode, GraphNode> seen = new IdentityHashMap<>();
Deque<GraphPath> queue = new LinkedList<>();
GraphPath path = null;
GraphNode current = node;
while (true) {
Map<GraphNode, Set<GraphEdgeInfo>> sources = getSourcesTargeting(current);
if (sources == null) {
// We have reached a root or the current node is not targeted at all.
return getCanonicalPath(path, node);
}
assert !sources.isEmpty();
for (GraphNode source : sources.keySet()) {
if (!seen.containsKey(source)) {
seen.put(source, source);
queue.addLast(new GraphPath(source, path));
}
}
// The source set was not empty, thus we don't have a real root, but all sources are seen!
if (queue.isEmpty()) {
return getCanonicalPath(new GraphPath(GraphNode.cycle(), path), node);
}
path = queue.removeFirst();
current = path.node;
}
}
// Convert a internal path representation to the external API and compute the edge reasons.
private List<Pair<GraphNode, GraphEdgeInfo>> getCanonicalPath(
GraphPath path, GraphNode endTarget) {
if (path == null) {
// If there is no path to endTarget, treat it as not kept by returning a null path.
return null;
}
List<Pair<GraphNode, GraphEdgeInfo>> canonical = new ArrayList<>();
while (path.path != null) {
GraphNode source = path.node;
if (source.isCycle()) {
canonical.add(new Pair<>(source, new GraphEdgeInfo(EdgeKind.Unknown)));
} else {
GraphNode target = path.path.node;
Set<GraphEdgeInfo> infos = getSourcesTargeting(target).get(source);
canonical.add(new Pair<>(source, getCanonicalInfo(infos)));
}
path = path.path;
}
Set<GraphEdgeInfo> infos = getSourcesTargeting(endTarget).get(path.node);
canonical.add(new Pair<>(path.node, getCanonicalInfo(infos)));
return canonical;
}
// Compute the most meaningful edge reason.
private GraphEdgeInfo getCanonicalInfo(Set<GraphEdgeInfo> infos) {
// TODO(b/120959039): this is pretty bad...
for (EdgeKind kind : EdgeKind.values()) {
for (GraphEdgeInfo info : infos) {
if (info.edgeKind() == kind) {
return info;
}
}
}
assert false : "Unexpected empty set of graph edge info";
return GraphEdgeInfo.unknown();
}
private void printEdge(GraphNode node, GraphEdgeInfo info, Formatter formatter) {
formatter.addReason("is " + info.getInfoPrefix() + ":");
addNodeMessage(node, formatter);
}
private String getNodeString(GraphNode node) {
if (node instanceof ClassGraphNode) {
return DescriptorUtils.descriptorToJavaType(
((ClassGraphNode) node).getReference().getDescriptor());
}
if (node instanceof MethodGraphNode) {
MethodReference method = ((MethodGraphNode) node).getReference();
return (method.getReturnType() == null ? "void" : method.getReturnType().getTypeName())
+ ' '
+ method.getHolderClass().getTypeName()
+ '.'
+ method.getMethodName()
+ StringUtils.join(
",",
ListUtils.map(method.getFormalTypes(), TypeReference::getTypeName),
BraceType.PARENS);
}
if (node instanceof FieldGraphNode) {
FieldReference field = ((FieldGraphNode) node).getReference();
return field.getFieldType().getTypeName()
+ ' '
+ field.getHolderClass().getTypeName()
+ '.'
+ field.getFieldName();
}
if (node instanceof KeepRuleGraphNode) {
KeepRuleGraphNode keepRuleNode = (KeepRuleGraphNode) node;
return keepRuleNode.getOrigin() == Origin.unknown()
? keepRuleNode.getContent()
: keepRuleNode.getOrigin() + ":" + shortPositionInfo(keepRuleNode.getPosition());
}
if (node == GraphNode.cycle()) {
return "only cyclic dependencies remain, failed to determine a path from a keep rule";
}
assert false : "Unexpected graph node type: " + node;
return Objects.toString(node);
}
private void addNodeMessage(GraphNode node, Formatter formatter) {
for (String line : StringUtils.splitLines(getNodeString(node))) {
formatter.addMessage(line);
}
}
private static String shortPositionInfo(Position position) {
if (position instanceof TextRange) {
TextPosition start = ((TextRange) position).getStart();
return start.getLine() + ":" + start.getColumn();
}
return position.getDescription();
}
private static class Formatter {
private final PrintStream output;
private int indentation = -1;
public Formatter(PrintStream output) {
this.output = output;
}
void startItem(String itemString) {
indentation++;
indent();
output.println(itemString);
}
private void indent() {
for (int i = 0; i < indentation; i++) {
output.print(" ");
}
}
void addReason(String thing) {
indent();
output.print("|- ");
output.println(thing);
}
void addMessage(String thing) {
indent();
output.print("| ");
output.println(thing);
}
void endItem() {
indentation--;
}
}
}