• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 
5 package com.android.tools.r8.ir.conversion;
6 
7 import com.android.tools.r8.graph.AppInfoWithSubtyping;
8 import com.android.tools.r8.graph.DexApplication;
9 import com.android.tools.r8.graph.DexClass;
10 import com.android.tools.r8.graph.DexEncodedMethod;
11 import com.android.tools.r8.graph.DexField;
12 import com.android.tools.r8.graph.DexMethod;
13 import com.android.tools.r8.graph.DexProgramClass;
14 import com.android.tools.r8.graph.DexType;
15 import com.android.tools.r8.graph.GraphLense;
16 import com.android.tools.r8.graph.UseRegistry;
17 import com.android.tools.r8.ir.code.Invoke;
18 import com.android.tools.r8.ir.code.Invoke.Type;
19 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
20 import com.google.common.collect.Sets;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.LinkedHashMap;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 
31 /**
32  * Call graph representation.
33  * <p>
34  * Each node in the graph contain the methods called and the calling methods. For virtual and
35  * interface calls all potential calls from subtypes are recorded.
36  * <p>
37  * Only methods in the program - not library methods - are represented.
38  * <p>
39  * The directional edges are represented as sets of nodes in each node (called methods and callees).
40  * <p>
41  * A call from method <code>a</code> to method <code>b</code> is only present once no matter how
42  * many calls of <code>a</code> there are in <code>a</code>.
43  * <p>
44  * Recursive calls are not present.
45  */
46 public class CallGraph {
47 
48   private class Node {
49 
50     public final DexEncodedMethod method;
51     private int invokeCount = 0;
52     private boolean isSelfRecursive = false;
53 
54     // Outgoing calls from this method.
55     private final Set<Node> callees = new LinkedHashSet<>();
56 
57     // Incoming calls to this method.
58     private final Set<Node> callers = new LinkedHashSet<>();
59 
Node(DexEncodedMethod method)60     private Node(DexEncodedMethod method) {
61       this.method = method;
62     }
63 
isBridge()64     public boolean isBridge() {
65       return method.accessFlags.isBridge();
66     }
67 
addCallee(Node method)68     private void addCallee(Node method) {
69       callees.add(method);
70     }
71 
addCaller(Node method)72     private void addCaller(Node method) {
73       callers.add(method);
74     }
75 
isSelfRecursive()76     boolean isSelfRecursive() {
77       return isSelfRecursive;
78     }
79 
isLeaf()80     boolean isLeaf() {
81       return callees.isEmpty();
82     }
83 
84     @Override
hashCode()85     public int hashCode() {
86       return method.hashCode();
87     }
88 
89     @Override
equals(Object obj)90     public boolean equals(Object obj) {
91       return this == obj;
92     }
93 
94     @Override
toString()95     public String toString() {
96       StringBuilder builder = new StringBuilder();
97       builder.append("MethodNode for: ");
98       builder.append(method.qualifiedName());
99       builder.append(" (");
100       builder.append(callees.size());
101       builder.append(" callees, ");
102       builder.append(callers.size());
103       builder.append(" callers");
104       if (isBridge()) {
105         builder.append(", bridge");
106       }
107       if (isSelfRecursive()) {
108         builder.append(", recursive");
109       }
110       builder.append(", invoke count " + invokeCount);
111       builder.append(").\n");
112       if (callees.size() > 0) {
113         builder.append("Callees:\n");
114         for (Node call : callees) {
115           builder.append("  ");
116           builder.append(call.method.qualifiedName());
117           builder.append("\n");
118         }
119       }
120       if (callers.size() > 0) {
121         builder.append("Callers:\n");
122         for (Node caller : callers) {
123           builder.append("  ");
124           builder.append(caller.method.qualifiedName());
125           builder.append("\n");
126         }
127       }
128       return builder.toString();
129     }
130   }
131 
132   private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
133   private final Map<DexEncodedMethod, Set<DexEncodedMethod>> breakers = new HashMap<>();
134 
135   // Returns whether the method->callee edge has been removed from the call graph
136   // to break a cycle in the call graph.
isBreaker(DexEncodedMethod method, DexEncodedMethod callee)137   public boolean isBreaker(DexEncodedMethod method, DexEncodedMethod callee) {
138     Set<DexEncodedMethod> value = breakers.get(method);
139     return (value != null) && value.contains(callee);
140   }
141 
142   private List<Node> leaves = null;
143   private Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
144   private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
145 
build(DexApplication application, AppInfoWithSubtyping appInfo, GraphLense graphLense)146   public static CallGraph build(DexApplication application, AppInfoWithSubtyping appInfo,
147       GraphLense graphLense) {
148     CallGraph graph = new CallGraph();
149     DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]);
150     Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type));
151     for (DexClass clazz : classes) {
152       for (DexEncodedMethod method : clazz.allMethodsSorted()) {
153         Node node = graph.ensureMethodNode(method);
154         InvokeExtractor extractor = new InvokeExtractor(appInfo, graphLense, node, graph);
155         method.registerReachableDefinitions(extractor);
156       }
157     }
158     assert allMethodsExists(application, graph);
159     graph.breakCycles();
160     assert graph.breakCycles() == 0;  // This time the cycles should be gone.
161     graph.fillCallSiteSets(appInfo);
162     graph.fillInitialLeaves();
163     return graph;
164   }
165 
166   /**
167    * Check if the <code>method</code> is guaranteed to only have a single call site.
168    * <p>
169    * For pinned methods (methods kept through Proguard keep rules) this will always answer
170    * <code>false</code>.
171    */
hasSingleCallSite(DexEncodedMethod method)172   public boolean hasSingleCallSite(DexEncodedMethod method) {
173     return singleCallSite.contains(method);
174   }
175 
hasDoubleCallSite(DexEncodedMethod method)176   public boolean hasDoubleCallSite(DexEncodedMethod method) {
177     return doubleCallSite.contains(method);
178   }
179 
fillCallSiteSets(AppInfoWithSubtyping appInfo)180   private void fillCallSiteSets(AppInfoWithSubtyping appInfo) {
181     assert singleCallSite.isEmpty();
182     AppInfoWithLiveness liveAppInfo = appInfo.withLiveness();
183     if (liveAppInfo == null) {
184       return;
185     }
186     for (Node value : nodes.values()) {
187       // For non-pinned methods we know the exact number of call sites.
188       if (!appInfo.withLiveness().pinnedItems.contains(value.method)) {
189         if (value.invokeCount == 1) {
190           singleCallSite.add(value.method);
191         } else if (value.invokeCount == 2) {
192           doubleCallSite.add(value.method);
193         }
194       }
195     }
196   }
197 
fillInitialLeaves()198   private void fillInitialLeaves() {
199     assert leaves == null;
200     leaves = new ArrayList<>();
201     for (Node node : nodes.values()) {
202       if (node.isLeaf()) {
203         leaves.add(node);
204       }
205     }
206   }
207 
allMethodsExists(DexApplication application, CallGraph graph)208   private static boolean allMethodsExists(DexApplication application, CallGraph graph) {
209     for (DexProgramClass clazz : application.classes()) {
210       clazz.forEachMethod( method -> { assert graph.nodes.get(method) != null; });
211     }
212     return true;
213   }
214 
215   /**
216    * Remove all leaves (nodes with an call (outgoing) degree of 0).
217    *
218    * @return List of {@link DexEncodedMethod} of the leaves removed.
219    */
removeLeaves()220   private List<DexEncodedMethod> removeLeaves() {
221     List<DexEncodedMethod> result = new ArrayList<>();
222     List<Node> newLeaves = new ArrayList<>();
223     for (Node leaf : leaves) {
224       assert nodes.containsKey(leaf.method) && nodes.get(leaf.method).callees.isEmpty();
225       remove(leaf, newLeaves);
226       result.add(leaf.method);
227     }
228     leaves = newLeaves;
229     return result;
230   }
231 
232   /**
233    * Pick the next set of leaves (nodes with an call (outgoing) degree of 0) if any.
234    * <p>
235    * If the graph has no leaves then some cycles in the graph will be broken to create a set of
236    * leaves. See {@link #breakCycles} on how cycles are broken. This ensures that at least one
237    * leave is returned if the graph is not empty.
238    * <p>
239    *
240    * @return  List of {@link DexEncodedMethod}.
241    */
extractLeaves()242   List<DexEncodedMethod> extractLeaves() {
243     if (isEmpty()) {
244       return null;
245     }
246     List<DexEncodedMethod> leaves = removeLeaves();
247     assert leaves.size() > 0;
248     leaves.forEach( leaf -> { assert !leaf.isProcessed(); });
249     return leaves;
250   }
251 
traverse(Node node, HashSet<Node> stack, HashSet<Node> marked)252   private int traverse(Node node, HashSet<Node> stack, HashSet<Node> marked) {
253     int numberOfCycles = 0;
254     if (!marked.contains(node)) {
255       assert !stack.contains(node);
256       stack.add(node);
257       ArrayList<Node> toBeRemoved = null;
258       // Sort the callees before calling traverse recursively.
259       // This will ensure cycles are broken the same way across
260       // multiple invocations of the R8 compiler.
261       Node[] callees = node.callees.toArray(new Node[node.callees.size()]);
262       Arrays.sort(callees, (Node a, Node b) -> a.method.method.slowCompareTo(b.method.method));
263       for (Node callee : callees) {
264         if (stack.contains(callee)) {
265           if (toBeRemoved == null) {
266             toBeRemoved = new ArrayList<>();
267           }
268           // We have a cycle; break it by removing node->callee.
269           toBeRemoved.add(callee);
270           callee.callers.remove(node);
271           breakers.computeIfAbsent(node.method, ignore -> new HashSet<>()).add(callee.method);
272         } else {
273           numberOfCycles += traverse(callee, stack, marked);
274         }
275       }
276       if (toBeRemoved != null) {
277         numberOfCycles += toBeRemoved.size();
278         node.callees.removeAll(toBeRemoved);
279       }
280       stack.remove(node);
281       marked.add(node);
282     }
283     return numberOfCycles;
284   }
285 
breakCycles()286   private int breakCycles() {
287     // Break cycles in this call graph by removing edges causing cycles.
288     // The remove edges are stored in @breakers.
289     int numberOfCycles = 0;
290     HashSet<Node> stack = new HashSet<>();
291     HashSet<Node> marked = new HashSet<>();
292     for(Node node : nodes.values()) {
293       numberOfCycles += traverse(node, stack, marked);
294     }
295     return numberOfCycles;
296   }
297 
ensureMethodNode(DexEncodedMethod method)298   synchronized private Node ensureMethodNode(DexEncodedMethod method) {
299     return nodes.computeIfAbsent(method, k -> new Node(method));
300   }
301 
addCall(Node caller, Node callee)302   synchronized private void addCall(Node caller, Node callee) {
303     assert caller != null;
304     assert callee != null;
305     if (caller != callee) {
306       caller.addCallee(callee);
307       callee.addCaller(caller);
308     } else {
309       caller.isSelfRecursive = true;
310     }
311     callee.invokeCount++;
312   }
313 
remove(Node node, List<Node> leaves)314   private void remove(Node node, List<Node> leaves) {
315     assert node != null;
316     for (Node caller : node.callers) {
317       boolean removed = caller.callees.remove(node);
318       if (caller.isLeaf()) {
319         leaves.add(caller);
320       }
321       assert removed;
322     }
323     nodes.remove(node.method);
324   }
325 
isEmpty()326   public boolean isEmpty() {
327     return nodes.size() == 0;
328   }
329 
dump()330   public void dump() {
331     nodes.forEach((m, n) -> System.out.println(n + "\n"));
332   }
333 
334   private static class InvokeExtractor extends UseRegistry {
335 
336     AppInfoWithSubtyping appInfo;
337     GraphLense graphLense;
338     Node caller;
339     CallGraph graph;
340 
InvokeExtractor(AppInfoWithSubtyping appInfo, GraphLense graphLense, Node caller, CallGraph graph)341     InvokeExtractor(AppInfoWithSubtyping appInfo, GraphLense graphLense, Node caller,
342         CallGraph graph) {
343       this.appInfo = appInfo;
344       this.graphLense = graphLense;
345       this.caller = caller;
346       this.graph = graph;
347     }
348 
processInvoke(DexEncodedMethod source, Invoke.Type type, DexMethod method)349     private void processInvoke(DexEncodedMethod source, Invoke.Type type, DexMethod method) {
350       method = graphLense.lookupMethod(method, source);
351       DexEncodedMethod definition = appInfo.lookup(type, method);
352       if (definition != null) {
353         assert !source.accessFlags.isBridge() || definition != caller.method;
354         DexType definitionHolder = definition.method.getHolder();
355         assert definitionHolder.isClassType();
356         if (!appInfo.definitionFor(definitionHolder).isLibraryClass()) {
357           Node callee = graph.ensureMethodNode(definition);
358           graph.addCall(caller, callee);
359           // For virtual and interface calls add all potential targets that could be called.
360           if (type == Type.VIRTUAL || type == Type.INTERFACE) {
361             Set<DexEncodedMethod> possibleTargets;
362             if (definitionHolder.isInterface()) {
363               possibleTargets = appInfo.lookupInterfaceTargets(definition.method);
364             } else {
365               possibleTargets = appInfo.lookupVirtualTargets(definition.method);
366             }
367             for (DexEncodedMethod possibleTarget : possibleTargets) {
368               if (possibleTarget != definition) {
369                 DexClass possibleTargetClass =
370                     appInfo.definitionFor(possibleTarget.method.getHolder());
371                 if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
372                   callee = graph.ensureMethodNode(possibleTarget);
373                   graph.addCall(caller, callee);
374                 }
375               }
376             }
377           }
378         }
379       }
380     }
381 
382     @Override
registerInvokeVirtual(DexMethod method)383     public boolean registerInvokeVirtual(DexMethod method) {
384       processInvoke(caller.method, Type.VIRTUAL, method);
385       return false;
386     }
387 
388     @Override
registerInvokeDirect(DexMethod method)389     public boolean registerInvokeDirect(DexMethod method) {
390       processInvoke(caller.method, Type.DIRECT, method);
391       return false;
392     }
393 
394     @Override
registerInvokeStatic(DexMethod method)395     public boolean registerInvokeStatic(DexMethod method) {
396       processInvoke(caller.method, Type.STATIC, method);
397       return false;
398     }
399 
400     @Override
registerInvokeInterface(DexMethod method)401     public boolean registerInvokeInterface(DexMethod method) {
402       processInvoke(caller.method, Type.INTERFACE, method);
403       return false;
404     }
405 
406     @Override
registerInvokeSuper(DexMethod method)407     public boolean registerInvokeSuper(DexMethod method) {
408       processInvoke(caller.method, Type.SUPER, method);
409       return false;
410     }
411 
412     @Override
registerInstanceFieldWrite(DexField field)413     public boolean registerInstanceFieldWrite(DexField field) {
414       return false;
415     }
416 
417     @Override
registerInstanceFieldRead(DexField field)418     public boolean registerInstanceFieldRead(DexField field) {
419       return false;
420     }
421 
422     @Override
registerNewInstance(DexType type)423     public boolean registerNewInstance(DexType type) {
424       return false;
425     }
426 
427     @Override
registerStaticFieldRead(DexField field)428     public boolean registerStaticFieldRead(DexField field) {
429       return false;
430     }
431 
432     @Override
registerStaticFieldWrite(DexField field)433     public boolean registerStaticFieldWrite(DexField field) {
434       return false;
435     }
436 
437     @Override
registerTypeReference(DexType type)438     public boolean registerTypeReference(DexType type) {
439       return false;
440     }
441   }
442 }
443