blob: e2fc31ec35ba2034bc343f3ca2da8fd19a1e7a02 [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.errors.Unreachable;
import com.android.tools.r8.graphinfo.ClassGraphNode;
import com.android.tools.r8.graphinfo.FieldGraphNode;
import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.graphinfo.GraphEdgeInfo;
import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
import com.android.tools.r8.graphinfo.GraphNode;
import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
import com.android.tools.r8.graphinfo.MethodGraphNode;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
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.utils.DescriptorUtils;
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.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class WhyAreYouKeepingConsumer implements GraphConsumer {
// 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;
}
}
// Possible sub-consumer that is also inspecting the kept-graph.
private final GraphConsumer subConsumer;
// Directional map backwards from targets to direct sources.
private final Map<GraphNode, Map<GraphNode, Set<GraphEdgeInfo>>> target2sources =
new IdentityHashMap<>();
public WhyAreYouKeepingConsumer(GraphConsumer subConsumer) {
this.subConsumer = subConsumer;
}
@Override
public void acceptEdge(GraphNode source, GraphNode target, GraphEdgeInfo info) {
target2sources
.computeIfAbsent(target, k -> new IdentityHashMap<>())
.computeIfAbsent(source, k -> new HashSet<>())
.add(info);
if (subConsumer != null) {
subConsumer.acceptEdge(source, target, info);
}
}
/** Print the shortest path from a root to a node in the graph. */
public void printWhyAreYouKeeping(ClassReference clazz, PrintStream out) {
for (GraphNode node : target2sources.keySet()) {
if (node.identity().equals(clazz.toDescriptor())) {
printWhyAreYouKeeping(node, out);
return;
}
}
printNothingKeeping(clazz, 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.toDescriptor()));
}
private List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
Deque<GraphPath> queue;
{
Map<GraphNode, Set<GraphEdgeInfo>> sources = target2sources.get(node);
if (sources == null) {
// The node is not targeted at all (it is not reachable).
return null;
}
queue = new LinkedList<>();
for (GraphNode source : sources.keySet()) {
queue.addLast(new GraphPath(source, null));
}
}
Map<GraphNode, GraphNode> seen = new IdentityHashMap<>();
while (!queue.isEmpty()) {
GraphPath path = queue.removeFirst();
Map<GraphNode, Set<GraphEdgeInfo>> sources = target2sources.get(path.node);
if (sources == null) {
return getCanonicalPath(path, node);
}
for (GraphNode source : sources.keySet()) {
if (seen.containsKey(source)) {
continue;
}
seen.put(source, source);
queue.addLast(new GraphPath(source, path));
}
}
throw new Unreachable("Failed to find a root from node: " + 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) {
assert path != null;
List<Pair<GraphNode, GraphEdgeInfo>> canonical = new ArrayList<>();
while (path.path != null) {
GraphNode source = path.node;
GraphNode target = path.path.node;
Set<GraphEdgeInfo> infos = target2sources.get(target).get(source);
canonical.add(new Pair<>(source, getCanonicalInfo(infos)));
path = path.path;
}
Set<GraphEdgeInfo> infos = target2sources.get(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;
}
}
}
throw new Unreachable("Unexpected empty set of graph edge info");
}
private void printEdge(GraphNode node, GraphEdgeInfo info, Formatter formatter) {
formatter.addReason("is " + getInfoPrefix(info) + ":");
addNodeMessage(node, formatter);
}
private String getInfoPrefix(GraphEdgeInfo info) {
switch (info.edgeKind()) {
case KeepRule:
case CompatibilityRule:
return "referenced in keep rule";
case InstantiatedIn:
return "instantiated in";
case InvokedViaSuper:
return "invoked via super from";
case TargetedBySuper:
return "targeted by super from";
case InvokedFrom:
return "invoked from";
case InvokedFromLambdaCreatedIn:
return "invoked from lambda created in";
case ReferencedFrom:
return "referenced from";
case ReachableFromLiveType:
return "reachable from";
case ReferencedInAnnotation:
return "referenced in annotation";
case IsLibraryMethod:
return "defined in library";
default:
throw new Unreachable("Unexpected edge kind: " + info.edgeKind());
}
}
private String getNodeString(GraphNode node) {
if (node instanceof ClassGraphNode) {
return DescriptorUtils.descriptorToJavaType(((ClassGraphNode) node).getDescriptor());
}
if (node instanceof MethodGraphNode) {
MethodGraphNode methodNode = (MethodGraphNode) node;
MethodSignature signature =
MethodSignature.fromSignature(
methodNode.getMethodName(), methodNode.getMethodDescriptor());
return signature.type
+ ' '
+ DescriptorUtils.descriptorToJavaType(methodNode.getHolderDescriptor())
+ '.'
+ methodNode.getMethodName()
+ StringUtils.join(Arrays.asList(signature.parameters), ",", BraceType.PARENS);
}
if (node instanceof FieldGraphNode) {
FieldGraphNode fieldNode = (FieldGraphNode) node;
return DescriptorUtils.descriptorToJavaType(fieldNode.getFieldDescriptor())
+ ' '
+ DescriptorUtils.descriptorToJavaType(fieldNode.getHolderDescriptor())
+ '.'
+ fieldNode.getFieldName();
}
if (node instanceof KeepRuleGraphNode) {
KeepRuleGraphNode keepRuleNode = (KeepRuleGraphNode) node;
return keepRuleNode.getOrigin() == Origin.unknown()
? keepRuleNode.getContent()
: keepRuleNode.getOrigin() + ":" + shortPositionInfo(keepRuleNode.getPosition());
}
throw new Unreachable("Unexpected graph node type: " + 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--;
}
}
}