| // Copyright (c) 2016, 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.graph.DexApplication; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexDefinition; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItem; |
| import com.android.tools.r8.graph.DexType; |
| import com.google.common.collect.Sets; |
| import java.io.PrintStream; |
| import java.util.ArrayDeque; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.Map; |
| import java.util.Set; |
| |
| public class ReasonPrinter { |
| |
| private final Set<DexDefinition> itemsQueried; |
| private ReasonFormatter formatter; |
| |
| private final Map<DexEncodedField, KeepReason> liveFields; |
| private final Map<DexEncodedMethod, KeepReason> liveMethods; |
| private final Map<DexDefinition, KeepReason> reachablityReasons; |
| private final Map<DexType, KeepReason> instantiatedTypes; |
| |
| ReasonPrinter(Set<DexDefinition> itemsQueried, Map<DexEncodedField, KeepReason> liveFields, |
| Map<DexEncodedMethod, KeepReason> liveMethods, Map<DexDefinition, KeepReason> reachablityReasons, |
| Map<DexType, KeepReason> instantiatedTypes) { |
| this.itemsQueried = itemsQueried; |
| this.liveFields = liveFields; |
| this.liveMethods = liveMethods; |
| this.reachablityReasons = reachablityReasons; |
| this.instantiatedTypes = instantiatedTypes; |
| } |
| |
| public void run(DexApplication application) { |
| // TODO(herhut): Instead of traversing the app, sort the queried items. |
| formatter = new ReasonFormatter(); |
| for (DexClass clazz : application.classes()) { |
| if (itemsQueried.contains(clazz)) { |
| printReasonFor(clazz); |
| } |
| Arrays.stream(clazz.staticFields()).filter(itemsQueried::contains) |
| .forEach(this::printReasonFor); |
| Arrays.stream(clazz.instanceFields()).filter(itemsQueried::contains) |
| .forEach(this::printReasonFor); |
| Arrays.stream(clazz.directMethods()).filter(itemsQueried::contains) |
| .forEach(this::printReasonFor); |
| Arrays.stream(clazz.virtualMethods()).filter(itemsQueried::contains) |
| .forEach(this::printReasonFor); |
| } |
| } |
| |
| private void printNoIdeaWhy(DexItem item, ReasonFormatter formatter) { |
| formatter.startItem(item); |
| formatter.pushEmptyPrefix(); |
| formatter.addReason("is kept for unknown reason."); |
| formatter.popPrefix(); |
| formatter.endItem(); |
| } |
| |
| private void printOnlyAbstractShell(DexItem item, ReasonFormatter formatter) { |
| formatter.startItem(item); |
| KeepReason reachableReason = reachablityReasons.get(item); |
| if (reachableReason != null) { |
| formatter.pushPrefix( |
| "is not kept, only its abstract declaration is needed because it "); |
| reachableReason.print(formatter); |
| formatter.popPrefix(); |
| } else { |
| formatter.pushEmptyPrefix(); |
| formatter.addReason("is not kept, only its abstract declaration is."); |
| formatter.popPrefix(); |
| } |
| formatter.endItem(); |
| } |
| |
| private void printReasonFor(DexClass item) { |
| KeepReason reason = instantiatedTypes.get(item.type); |
| if (reason == null) { |
| if (item.accessFlags.isAbstract()) { |
| printOnlyAbstractShell(item, formatter); |
| } else { |
| printNoIdeaWhy(item, formatter); |
| } |
| } else { |
| formatter.startItem(item); |
| formatter.pushIsLivePrefix(); |
| reason.print(formatter); |
| formatter.popPrefix(); |
| formatter.endItem(); |
| } |
| } |
| |
| private void printReasonFor(DexEncodedMethod item) { |
| KeepReason reasonLive = liveMethods.get(item); |
| if (reasonLive == null) { |
| if (item.accessFlags.isAbstract()) { |
| printOnlyAbstractShell(item, formatter); |
| } else { |
| printNoIdeaWhy(item.method, formatter); |
| } |
| } else { |
| formatter.addMethodReferenceReason(item); |
| } |
| } |
| |
| private void printReasonFor(DexEncodedField item) { |
| KeepReason reason = liveFields.get(item); |
| if (reason == null) { |
| printNoIdeaWhy(item.field, formatter); |
| } else { |
| formatter.startItem(item.field); |
| formatter.pushIsLivePrefix(); |
| reason.print(formatter); |
| formatter.popPrefix(); |
| reason = reachablityReasons.get(item); |
| if (reason != null) { |
| formatter.pushIsReachablePrefix(); |
| reason.print(formatter); |
| formatter.popPrefix(); |
| } |
| formatter.endItem(); |
| } |
| } |
| |
| class ReasonFormatter { |
| |
| private final Set<DexItem> seen = Sets.newIdentityHashSet(); |
| private final Deque<String> prefixes = new ArrayDeque<>(); |
| |
| private int indentation = -1; |
| |
| private PrintStream output = System.out; |
| |
| void pushIsLivePrefix() { |
| prefixes.push("is live because "); |
| } |
| |
| void pushIsReachablePrefix() { |
| prefixes.push("is reachable because "); |
| } |
| |
| void pushPrefix(String prefix) { |
| prefixes.push(prefix); |
| } |
| |
| void pushEmptyPrefix() { |
| prefixes.push(""); |
| } |
| |
| void popPrefix() { |
| prefixes.pop(); |
| } |
| |
| void startItem(DexItem item) { |
| indentation++; |
| indent(); |
| output.println(item.toSourceString()); |
| } |
| |
| private void indent() { |
| for (int i = 0; i < indentation; i++) { |
| output.print(" "); |
| } |
| } |
| |
| void addReason(String thing) { |
| indent(); |
| output.print("|- "); |
| String prefix = prefixes.peek(); |
| output.print(prefix); |
| output.println(thing); |
| } |
| |
| void addMessage(String thing) { |
| indent(); |
| output.print("| "); |
| output.println(thing); |
| } |
| |
| void endItem() { |
| indentation--; |
| } |
| |
| void addMethodReferenceReason(DexEncodedMethod method) { |
| if (!seen.add(method.method)) { |
| return; |
| } |
| startItem(method); |
| KeepReason reason = reachablityReasons.get(method); |
| if (reason != null) { |
| pushIsReachablePrefix(); |
| reason.print(this); |
| popPrefix(); |
| } |
| reason = liveMethods.get(method); |
| if (reason != null) { |
| pushIsLivePrefix(); |
| reason.print(this); |
| popPrefix(); |
| } |
| endItem(); |
| } |
| |
| void addTypeLivenessReason(DexType type) { |
| if (!seen.add(type)) { |
| return; |
| } |
| startItem(type); |
| pushIsLivePrefix(); |
| KeepReason reason = instantiatedTypes.get(type); |
| if (reason != null) { |
| reason.print(this); |
| } |
| popPrefix(); |
| endItem(); |
| } |
| } |
| |
| public static ReasonPrinter getNoOpPrinter() { |
| return new NoOpReasonPrinter(); |
| } |
| |
| private static class NoOpReasonPrinter extends ReasonPrinter { |
| |
| NoOpReasonPrinter() { |
| super(Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), |
| Collections.emptyMap(), Collections.emptyMap()); |
| } |
| |
| @Override |
| public void run(DexApplication application) { |
| // Intentionally left empty. |
| } |
| } |
| } |