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