|  | // Copyright (c) 2019, 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.naming; | 
|  |  | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | import com.android.tools.r8.graph.DexCallSite; | 
|  | import com.android.tools.r8.graph.DexClass; | 
|  | import com.android.tools.r8.graph.DexEncodedMethod; | 
|  | import com.android.tools.r8.graph.DexMethod; | 
|  | import com.android.tools.r8.graph.DexString; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.graph.SubtypingInfo; | 
|  | import com.android.tools.r8.ir.desugar.LambdaDescriptor; | 
|  | import com.android.tools.r8.naming.MethodNameMinifier.State; | 
|  | import com.android.tools.r8.shaking.AppInfoWithLiveness; | 
|  | import com.android.tools.r8.utils.DisjointSets; | 
|  | import com.android.tools.r8.utils.MethodJavaSignatureEquivalence; | 
|  | import com.android.tools.r8.utils.MethodSignatureEquivalence; | 
|  | import com.android.tools.r8.utils.Timing; | 
|  | import com.google.common.base.Equivalence; | 
|  | import com.google.common.base.Equivalence.Wrapper; | 
|  | import com.google.common.collect.Lists; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.io.PrintStream; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Comparator; | 
|  | import java.util.HashMap; | 
|  | import java.util.HashSet; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  | import java.util.function.BiConsumer; | 
|  | import java.util.function.BiFunction; | 
|  | import java.util.function.Consumer; | 
|  | import java.util.function.Function; | 
|  | import java.util.stream.Collectors; | 
|  |  | 
|  | /** | 
|  | * Assigning names to interface methods can be done in different ways, but here we try to assign the | 
|  | * same name to equivalent methods. Arguments for grouping equivalent methods is that clients will | 
|  | * work out of the box if they implement multiple interfaces and the penalty of not having more | 
|  | * locality is insignificant in DEX because the proto will only be listed once in the DEX file. | 
|  | * | 
|  | * ----------- Library ----------- | 
|  | * | 
|  | * class A { } | 
|  | * | 
|  | * class Z extends A { a(); } | 
|  | * | 
|  | * ----------- Program ----------- | 
|  | * | 
|  | *      interface I { x(); c() } | 
|  | * | 
|  | *          /                 \ | 
|  | *         /                   \ | 
|  | *        /                     \ | 
|  | *       v                       v | 
|  | * | 
|  | *  interface J { b() }     interface K { d() }           interface L { b() } | 
|  | * | 
|  | * B extends A implements J { } | 
|  | * | 
|  | * C extends B implements K { } | 
|  | * | 
|  | * -keep L { *; } | 
|  | * | 
|  | * Because of the way this algorithm work, we will try to bundle the naming together into groups. In | 
|  | * the example above, the group states should identify that: | 
|  | * | 
|  | * - We are bundling J.b() with L.b() so we need to keep the name of both | 
|  | * - When giving name to I.x() or I.c() we cannot use b() because those names would collide in C. | 
|  | * | 
|  | * A further complication is that call sites can implement methods with the same name but different | 
|  | * proto's. The canonical example of this is the identity function. We compute all callsites that | 
|  | * needs to be named together by using union-find. | 
|  | * | 
|  | * A small sample of the state of the above example could be like so: | 
|  | * Group a()       -> { State(A) } | 
|  | * Group a(Object) -> { State(J), State(I), State(A) } | 
|  | * Group b()       -> { State(J), State(I), State(L), State(A) } | 
|  | * Group c()       -> { State(J), State(I), State(A) } | 
|  | * | 
|  | * Because of the frontier state computation in {@link MethodNameMinifier}, all reservations are | 
|  | * bubbled up to the library frontier and naming is top-down to not re-use the same names. The | 
|  | * {@link InterfaceMethodNameMinifier} is run after ordinary method reservation but before new | 
|  | * method name assignment. Thus each group only has to keep track of the states in the interface | 
|  | * inheritance tree and the frontiers of their implementations. | 
|  | * | 
|  | * To cache all interface reservation states we use interfaceStateMap that maps each type to its | 
|  | * {@link InterfaceReservationState} that allows for querying and updating the interface inheritance | 
|  | * tree. This caching is crucial for the time spent computing interface names because most states | 
|  | * will not have a high depth. | 
|  | * | 
|  | * We then map each group from Equivalence(Method) to {@link InterfaceMethodGroupState} that | 
|  | * maintains a collection of {@link InterfaceReservationState} for each method the group represent. | 
|  | */ | 
|  | class InterfaceMethodNameMinifier { | 
|  |  | 
|  | class InterfaceReservationState { | 
|  |  | 
|  | // Used for iterating the parent hierarchy tree. | 
|  | final DexClass iface; | 
|  | // Used for iterating the sub trees that has this node as root. | 
|  | final Set<DexType> children = new HashSet<>(); | 
|  | // Collection of the frontier reservation types and the interface type itself. | 
|  | private final Set<DexType> reservationTypes = new HashSet<>(); | 
|  |  | 
|  | InterfaceReservationState(DexClass iface) { | 
|  | this.iface = iface; | 
|  | } | 
|  |  | 
|  | DexString getReservedName(DexEncodedMethod method) { | 
|  | // If an interface is kept and we are using applymapping, the renamed name for this method | 
|  | // is tracked on this level. | 
|  | if (appView.options().getProguardConfiguration().hasApplyMappingFile()) { | 
|  | DexString reservedName = minifierState.getReservedName(method, iface); | 
|  | if (reservedName != null) { | 
|  | return reservedName; | 
|  | } | 
|  | } | 
|  | // Otherwise, we just search the hierarchy for the first identity reservation since | 
|  | // applymapping is no longer in effect. | 
|  | Boolean isReserved = | 
|  | forAny( | 
|  | s -> { | 
|  | for (DexType reservationType : s.reservationTypes) { | 
|  | Set<DexString> reservedNamesFor = | 
|  | minifierState | 
|  | .getReservationState(reservationType) | 
|  | .getReservedNamesFor(method.getReference()); | 
|  | assert reservedNamesFor == null || !reservedNamesFor.isEmpty(); | 
|  | if (reservedNamesFor != null && reservedNamesFor.contains(method.getName())) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return null; | 
|  | }); | 
|  | return isReserved == null ? null : method.getName(); | 
|  | } | 
|  |  | 
|  | void addReservationType(DexType type) { | 
|  | this.reservationTypes.add(type); | 
|  | } | 
|  |  | 
|  | void reserveName(DexString reservedName, DexEncodedMethod method) { | 
|  | forAll( | 
|  | s -> { | 
|  | s.reservationTypes.forEach( | 
|  | resType -> { | 
|  | MethodReservationState<?> state = minifierState.getReservationState(resType); | 
|  | state.reserveName(reservedName, method); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | boolean isAvailable(DexString candidate, DexEncodedMethod method) { | 
|  | Boolean result = | 
|  | forAny( | 
|  | s -> { | 
|  | for (DexType resType : s.reservationTypes) { | 
|  | MethodNamingState<?> state = minifierState.getNamingState(resType); | 
|  | if (!state.isAvailable(candidate, method.getReference())) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return null; | 
|  | }); | 
|  | return result == null || result; | 
|  | } | 
|  |  | 
|  | void addRenaming(DexString newName, DexEncodedMethod method) { | 
|  | forAll( | 
|  | s -> | 
|  | s.reservationTypes.forEach( | 
|  | resType -> minifierState.getNamingState(resType).addRenaming(newName, method))); | 
|  | } | 
|  |  | 
|  | <T> void forAll(Consumer<InterfaceReservationState> action) { | 
|  | forAny( | 
|  | s -> { | 
|  | action.accept(s); | 
|  | return null; | 
|  | }); | 
|  | } | 
|  |  | 
|  | private <T> T forAny(Function<InterfaceReservationState, T> action) { | 
|  | T result = action.apply(this); | 
|  | if (result != null) { | 
|  | return result; | 
|  | } | 
|  | result = forChildren(action); | 
|  | if (result != null) { | 
|  | return result; | 
|  | } | 
|  | return forParents(action); | 
|  | } | 
|  |  | 
|  | private <T> T forParents(Function<InterfaceReservationState, T> action) { | 
|  | for (DexType parent : iface.interfaces.values) { | 
|  | InterfaceReservationState parentState = interfaceStateMap.get(parent); | 
|  | if (parentState != null) { | 
|  | T returnValue = action.apply(parentState); | 
|  | if (returnValue != null) { | 
|  | return returnValue; | 
|  | } | 
|  | returnValue = parentState.forParents(action); | 
|  | if (returnValue != null) { | 
|  | return returnValue; | 
|  | } | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | private <T> T forChildren(Function<InterfaceReservationState, T> action) { | 
|  | for (DexType child : children) { | 
|  | InterfaceReservationState childState = interfaceStateMap.get(child); | 
|  | if (childState != null) { | 
|  | T returnValue = action.apply(childState); | 
|  | if (returnValue != null) { | 
|  | return returnValue; | 
|  | } | 
|  | returnValue = childState.forChildren(action); | 
|  | if (returnValue != null) { | 
|  | return returnValue; | 
|  | } | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | boolean containsReservation(DexType reservationType) { | 
|  | return reservationTypes.contains(reservationType); | 
|  | } | 
|  | } | 
|  |  | 
|  | class InterfaceMethodGroupState implements Comparable<InterfaceMethodGroupState> { | 
|  |  | 
|  | private final Set<DexCallSite> callSites = new HashSet<>(); | 
|  | private final Map<DexEncodedMethod, Set<InterfaceReservationState>> methodStates = | 
|  | new HashMap<>(); | 
|  | private final List<DexEncodedMethod> callSiteCollidingMethods = new ArrayList<>(); | 
|  |  | 
|  | void addState(DexEncodedMethod method, InterfaceReservationState interfaceState) { | 
|  | methodStates.computeIfAbsent(method, m -> new HashSet<>()).add(interfaceState); | 
|  | } | 
|  |  | 
|  | void appendMethodGroupState(InterfaceMethodGroupState state) { | 
|  | callSites.addAll(state.callSites); | 
|  | callSiteCollidingMethods.addAll(state.callSiteCollidingMethods); | 
|  | for (DexEncodedMethod key : state.methodStates.keySet()) { | 
|  | methodStates.computeIfAbsent(key, k -> new HashSet<>()).addAll(state.methodStates.get(key)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void addCallSite(DexCallSite callSite) { | 
|  | // We cannot assert !callSites.contains(callSite) because the equivalence on methods | 
|  | // may group different implementations to the same InterfaceMethodGroupState. | 
|  | callSites.add(callSite); | 
|  | } | 
|  |  | 
|  | @SuppressWarnings("ReferenceEquality") | 
|  | DexString getReservedName() { | 
|  | if (methodStates.isEmpty()) { | 
|  | return null; | 
|  | } | 
|  | // It is perfectly fine to have multiple reserved names inside a group. If we have an identity | 
|  | // reservation, we have to prioritize that over the others, otherwise we just propose the | 
|  | // first ordered reserved name since we do not allow overwriting the name. | 
|  | List<DexEncodedMethod> sortedMethods = Lists.newArrayList(methodStates.keySet()); | 
|  | sortedMethods.sort((x, y) -> x.getReference().compareTo(y.getReference())); | 
|  | DexString reservedName = null; | 
|  | for (DexEncodedMethod method : sortedMethods) { | 
|  | for (InterfaceReservationState state : methodStates.get(method)) { | 
|  | DexString stateReserved = state.getReservedName(method); | 
|  | if (stateReserved == method.getName()) { | 
|  | return method.getName(); | 
|  | } else if (stateReserved != null) { | 
|  | reservedName = stateReserved; | 
|  | } | 
|  | } | 
|  | } | 
|  | return reservedName; | 
|  | } | 
|  |  | 
|  | void reserveName(DexString reservedName) { | 
|  | // The proposed reserved name is basically a suggestion. Try to reserve it in as many states | 
|  | // as possible. | 
|  | forEachState( | 
|  | (method, state) -> { | 
|  | DexString stateReserved = state.getReservedName(method); | 
|  | if (stateReserved != null) { | 
|  | state.reserveName(stateReserved, method); | 
|  | minifierState.putRenaming(method, stateReserved); | 
|  | } else { | 
|  | state.reserveName(reservedName, method); | 
|  | minifierState.putRenaming(method, reservedName); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | boolean isAvailable(DexString candidate) { | 
|  | Boolean result = | 
|  | forAnyState( | 
|  | (m, s) -> { | 
|  | if (!s.isAvailable(candidate, m)) { | 
|  | return false; | 
|  | } | 
|  | return null; | 
|  | }); | 
|  | return result == null || result; | 
|  | } | 
|  |  | 
|  | void addRenaming(DexString newName, MethodNameMinifier.State minifierState) { | 
|  | forEachState( | 
|  | (m, s) -> { | 
|  | s.addRenaming(newName, m); | 
|  | minifierState.putRenaming(m, newName); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void forEachState(BiConsumer<DexEncodedMethod, InterfaceReservationState> action) { | 
|  | forAnyState( | 
|  | (s, i) -> { | 
|  | action.accept(s, i); | 
|  | return null; | 
|  | }); | 
|  | } | 
|  |  | 
|  | <T> T forAnyState(BiFunction<DexEncodedMethod, InterfaceReservationState, T> callback) { | 
|  | T returnValue; | 
|  | for (Map.Entry<DexEncodedMethod, Set<InterfaceReservationState>> entry : | 
|  | methodStates.entrySet()) { | 
|  | for (InterfaceReservationState state : entry.getValue()) { | 
|  | returnValue = callback.apply(entry.getKey(), state); | 
|  | if (returnValue != null) { | 
|  | return returnValue; | 
|  | } | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | boolean containsReservation(DexEncodedMethod method, DexType reservationType) { | 
|  | Set<InterfaceReservationState> states = methodStates.get(method); | 
|  | if (states != null) { | 
|  | for (InterfaceReservationState state : states) { | 
|  | if (state.containsReservation(reservationType)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int compareTo(InterfaceMethodGroupState o) { | 
|  | // Sort by most naming states to smallest. | 
|  | return o.methodStates.size() - methodStates.size(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private final AppView<AppInfoWithLiveness> appView; | 
|  | private final SubtypingInfo subtypingInfo; | 
|  | private final Equivalence<DexMethod> equivalence; | 
|  | private final Equivalence<DexEncodedMethod> definitionEquivalence; | 
|  | private final MethodNameMinifier.State minifierState; | 
|  |  | 
|  | /** A map from DexMethods to all the states linked to interfaces they appear in. */ | 
|  | private final Map<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> globalStateMap = | 
|  | new HashMap<>(); | 
|  |  | 
|  | /** A map for caching all interface states. */ | 
|  | private final Map<DexType, InterfaceReservationState> interfaceStateMap = new HashMap<>(); | 
|  |  | 
|  | InterfaceMethodNameMinifier( | 
|  | AppView<AppInfoWithLiveness> appView, State minifierState, SubtypingInfo subtypingInfo) { | 
|  | this.appView = appView; | 
|  | this.minifierState = minifierState; | 
|  | this.subtypingInfo = subtypingInfo; | 
|  | this.equivalence = | 
|  | appView.options().getProguardConfiguration().isOverloadAggressively() | 
|  | ? MethodSignatureEquivalence.get() | 
|  | : MethodJavaSignatureEquivalence.get(); | 
|  | this.definitionEquivalence = | 
|  | new Equivalence<>() { | 
|  | @Override | 
|  | protected boolean doEquivalent(DexEncodedMethod method, DexEncodedMethod other) { | 
|  | return equivalence.equivalent(method.getReference(), other.getReference()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected int doHash(DexEncodedMethod method) { | 
|  | return equivalence.hash(method.getReference()); | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | private Comparator<Wrapper<DexEncodedMethod>> getDefaultInterfaceMethodOrdering() { | 
|  | return Comparator.comparing(globalStateMap::get); | 
|  | } | 
|  |  | 
|  | private void reserveNamesInInterfaces(Iterable<DexClass> interfaces) { | 
|  | for (DexClass iface : interfaces) { | 
|  | assert iface.isInterface(); | 
|  | minifierState.allocateReservationStateAndReserve(iface.type, iface.type); | 
|  | InterfaceReservationState iFaceState = new InterfaceReservationState(iface); | 
|  | iFaceState.addReservationType(iface.type); | 
|  | interfaceStateMap.put(iface.type, iFaceState); | 
|  | } | 
|  | } | 
|  |  | 
|  | @SuppressWarnings("ReferenceEquality") | 
|  | void assignNamesToInterfaceMethods(Timing timing, Iterable<DexClass> interfaces) { | 
|  | timing.begin("Interface minification"); | 
|  | // Reserve all the names that are required for interfaces. | 
|  | timing.begin("Reserve direct and compute hierarchy"); | 
|  | reserveNamesInInterfaces(interfaces); | 
|  | // Patch up root and children for all interfaces. Together with interfaceStateMap one can query | 
|  | // and update the entire tree. | 
|  | patchUpChildrenInReservationStates(); | 
|  | timing.end(); | 
|  |  | 
|  | // Compute a map from method signatures to a set of naming states for interfaces and | 
|  | // frontier states of classes that implement them. We add the frontier states so that we can | 
|  | // reserve the names for later method naming. | 
|  | timing.begin("Compute map"); | 
|  | computeReservationFrontiersForAllImplementingClasses(interfaces); | 
|  | for (DexClass iface : interfaces) { | 
|  | InterfaceReservationState inheritanceState = interfaceStateMap.get(iface.type); | 
|  | assert inheritanceState != null; | 
|  | for (DexEncodedMethod method : iface.methods()) { | 
|  | Wrapper<DexEncodedMethod> key = definitionEquivalence.wrap(method); | 
|  | globalStateMap | 
|  | .computeIfAbsent(key, k -> new InterfaceMethodGroupState()) | 
|  | .addState(method, inheritanceState); | 
|  | } | 
|  | } | 
|  | timing.end(); | 
|  |  | 
|  | // Collect the live call sites for multi-interface lambda expression renaming. For code with | 
|  | // desugared lambdas this is a conservative estimate, as we don't track if the generated | 
|  | // lambda classes survive into the output. As multi-interface lambda expressions are rare | 
|  | // this is not a big deal. | 
|  | Set<DexCallSite> liveCallSites = appView.appInfo().callSites.keySet(); | 
|  | // If the input program contains a multi-interface lambda expression that implements | 
|  | // interface methods with different protos, we need to make sure tha the implemented lambda | 
|  | // methods are renamed to the same name. | 
|  | // Union-find structure to keep track of methods that must be renamed together. | 
|  | // Note that if the input does not use multi-interface lambdas unificationParent will remain | 
|  | // empty. | 
|  | timing.begin("Union-find"); | 
|  | DisjointSets<Wrapper<DexEncodedMethod>> unification = new DisjointSets<>(); | 
|  |  | 
|  | liveCallSites.forEach( | 
|  | callSite -> { | 
|  | Set<Wrapper<DexEncodedMethod>> callSiteMethods = new HashSet<>(); | 
|  | // Don't report errors, as the set of call sites is a conservative estimate, and can | 
|  | // refer to interfaces which has been removed. | 
|  | Set<DexEncodedMethod> implementedMethods = | 
|  | appView.appInfo().lookupLambdaImplementedMethods(callSite, appView); | 
|  | for (DexEncodedMethod method : implementedMethods) { | 
|  | Wrapper<DexEncodedMethod> wrapped = definitionEquivalence.wrap(method); | 
|  | InterfaceMethodGroupState groupState = globalStateMap.get(wrapped); | 
|  | assert groupState != null : wrapped; | 
|  | groupState.addCallSite(callSite); | 
|  | callSiteMethods.add(wrapped); | 
|  | } | 
|  | if (callSiteMethods.isEmpty()) { | 
|  | return; | 
|  | } | 
|  | // For intersection types, we have to iterate all the multiple interfaces to look for | 
|  | // methods with the same signature. | 
|  | List<DexType> implementedInterfaces = LambdaDescriptor.getInterfaces(callSite, appView); | 
|  | if (implementedInterfaces != null) { | 
|  | for (int i = 1; i < implementedInterfaces.size(); i++) { | 
|  | // Add the merging state for all additional implemented interfaces into the state | 
|  | // for the group, if the name is different, to ensure that we do not pick the same | 
|  | // name. | 
|  | DexClass iface = appView.definitionFor(implementedInterfaces.get(i)); | 
|  | assert iface.isInterface(); | 
|  | for (DexEncodedMethod implementedMethod : implementedMethods) { | 
|  | for (DexEncodedMethod virtualMethod : iface.virtualMethods()) { | 
|  | boolean differentName = implementedMethod.getName() != virtualMethod.getName(); | 
|  | if (differentName | 
|  | && MethodJavaSignatureEquivalence.getEquivalenceIgnoreName() | 
|  | .equivalent( | 
|  | implementedMethod.getReference(), virtualMethod.getReference())) { | 
|  | InterfaceMethodGroupState interfaceMethodGroupState = | 
|  | globalStateMap.computeIfAbsent( | 
|  | definitionEquivalence.wrap(implementedMethod), | 
|  | k -> new InterfaceMethodGroupState()); | 
|  | interfaceMethodGroupState.callSiteCollidingMethods.add(virtualMethod); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (callSiteMethods.size() > 1) { | 
|  | // Implemented interfaces have different protos. Unify them. | 
|  | Wrapper<DexEncodedMethod> mainKey = callSiteMethods.iterator().next(); | 
|  | Wrapper<DexEncodedMethod> representative = unification.findOrMakeSet(mainKey); | 
|  | for (Wrapper<DexEncodedMethod> key : callSiteMethods) { | 
|  | unification.unionWithMakeSet(representative, key); | 
|  | } | 
|  | } | 
|  | }); | 
|  |  | 
|  | timing.end(); | 
|  |  | 
|  | // We now have roots for all unions. Add all of the states for the groups to the method state | 
|  | // for the unions to allow consistent naming across different protos. | 
|  | timing.begin("States for union"); | 
|  | Map<Wrapper<DexEncodedMethod>, Set<Wrapper<DexEncodedMethod>>> unions = | 
|  | unification.collectSets(); | 
|  |  | 
|  | for (Wrapper<DexEncodedMethod> wrapped : unions.keySet()) { | 
|  | InterfaceMethodGroupState groupState = globalStateMap.get(wrapped); | 
|  | assert groupState != null; | 
|  |  | 
|  | for (Wrapper<DexEncodedMethod> groupedMethod : unions.get(wrapped)) { | 
|  | DexEncodedMethod method = groupedMethod.get(); | 
|  | assert method != null; | 
|  | groupState.appendMethodGroupState(globalStateMap.get(groupedMethod)); | 
|  | } | 
|  | } | 
|  | timing.end(); | 
|  |  | 
|  | timing.begin("Sort"); | 
|  | // Filter out the groups that is included both in the unification and in the map. We sort the | 
|  | // methods by the number of dependent states, so that we use short names for method that are | 
|  | // referenced in many places. | 
|  | List<Wrapper<DexEncodedMethod>> interfaceMethodGroups = | 
|  | globalStateMap.keySet().stream() | 
|  | .filter(unification::isRepresentativeOrNotPresent) | 
|  | .sorted( | 
|  | appView | 
|  | .options() | 
|  | .testing | 
|  | .minifier | 
|  | .getInterfaceMethodOrderingOrDefault(getDefaultInterfaceMethodOrdering())) | 
|  | .collect(Collectors.toList()); | 
|  | timing.end(); | 
|  |  | 
|  | assert verifyAllMethodsAreRepresentedIn(interfaceMethodGroups); | 
|  | assert verifyAllCallSitesAreRepresentedIn(interfaceMethodGroups); | 
|  |  | 
|  | timing.begin("Reserve in groups"); | 
|  | // It is important that this entire phase is run before given new names, to ensure all | 
|  | // reservations are propagated to all naming states. | 
|  | List<Wrapper<DexEncodedMethod>> nonReservedMethodGroups = new ArrayList<>(); | 
|  | for (Wrapper<DexEncodedMethod> interfaceMethodGroup : interfaceMethodGroups) { | 
|  | InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup); | 
|  | assert groupState != null; | 
|  | DexString reservedName = groupState.getReservedName(); | 
|  | if (reservedName == null) { | 
|  | nonReservedMethodGroups.add(interfaceMethodGroup); | 
|  | } else { | 
|  | // Propagate reserved name to all states. | 
|  | groupState.reserveName(reservedName); | 
|  | } | 
|  | } | 
|  | timing.end(); | 
|  |  | 
|  | timing.begin("Rename in groups"); | 
|  | for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) { | 
|  | InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup); | 
|  | assert groupState != null; | 
|  | assert groupState.getReservedName() == null; | 
|  | DexString newName = assignNewName(interfaceMethodGroup.get(), groupState); | 
|  | assert newName != null; | 
|  | Set<String> loggingFilter = appView.options().extensiveInterfaceMethodMinifierLoggingFilter; | 
|  | if (!loggingFilter.isEmpty()) { | 
|  | Set<DexEncodedMethod> sourceMethods = groupState.methodStates.keySet(); | 
|  | if (sourceMethods.stream() | 
|  | .map(DexEncodedMethod::toSourceString) | 
|  | .anyMatch(loggingFilter::contains)) { | 
|  | print(interfaceMethodGroup.get().getReference(), sourceMethods, System.out); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // After all naming is completed for callsites, we must ensure to rename all interface methods | 
|  | // that can collide with the callsite method name. | 
|  | for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) { | 
|  | InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup); | 
|  | if (groupState.callSiteCollidingMethods.isEmpty()) { | 
|  | continue; | 
|  | } | 
|  | DexEncodedMethod key = interfaceMethodGroup.get(); | 
|  | MethodNamingState<?> keyNamingState = minifierState.getNamingState(key.getHolderType()); | 
|  | DexString existingRenaming = keyNamingState.newOrReservedNameFor(key); | 
|  | assert existingRenaming != null; | 
|  | for (DexEncodedMethod collidingMethod : groupState.callSiteCollidingMethods) { | 
|  | DexString newNameInGroup = newNameInGroup(collidingMethod, keyNamingState, groupState); | 
|  | minifierState.putRenaming(collidingMethod, newNameInGroup); | 
|  | MethodNamingState<?> methodNamingState = | 
|  | minifierState.getNamingState(collidingMethod.getReference().holder); | 
|  | methodNamingState.addRenaming(newNameInGroup, collidingMethod); | 
|  | keyNamingState.addRenaming(newNameInGroup, collidingMethod); | 
|  | } | 
|  | } | 
|  | timing.end(); | 
|  |  | 
|  | timing.end(); // end compute timing | 
|  | } | 
|  |  | 
|  | private DexString assignNewName(DexEncodedMethod method, InterfaceMethodGroupState groupState) { | 
|  | assert groupState.getReservedName() == null; | 
|  | assert groupState.methodStates.containsKey(method); | 
|  | assert groupState.containsReservation(method, method.getHolderType()); | 
|  | MethodNamingState<?> namingState = minifierState.getNamingState(method.getHolderType()); | 
|  | // Check if the name is available in all states. | 
|  | DexString newName = | 
|  | namingState.newOrReservedNameFor( | 
|  | method, (candidate, ignore) -> groupState.isAvailable(candidate)); | 
|  | groupState.addRenaming(newName, minifierState); | 
|  | return newName; | 
|  | } | 
|  |  | 
|  | private DexString newNameInGroup( | 
|  | DexEncodedMethod method, | 
|  | MethodNamingState<?> namingState, | 
|  | InterfaceMethodGroupState groupState) { | 
|  | // Check if the name is available in all states. | 
|  | return namingState.nextName(method, (candidate, ignore) -> groupState.isAvailable(candidate)); | 
|  | } | 
|  |  | 
|  | private void patchUpChildrenInReservationStates() { | 
|  | for (Map.Entry<DexType, InterfaceReservationState> entry : interfaceStateMap.entrySet()) { | 
|  | for (DexType parent : entry.getValue().iface.interfaces.values) { | 
|  | InterfaceReservationState parentState = interfaceStateMap.get(parent); | 
|  | if (parentState != null) { | 
|  | parentState.children.add(entry.getKey()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void computeReservationFrontiersForAllImplementingClasses(Iterable<DexClass> interfaces) { | 
|  | interfaces.forEach( | 
|  | iface -> | 
|  | subtypingInfo | 
|  | .subtypes(iface.getType()) | 
|  | .forEach( | 
|  | subType -> { | 
|  | DexClass subClass = appView.contextIndependentDefinitionFor(subType); | 
|  | if (subClass == null || subClass.isInterface()) { | 
|  | return; | 
|  | } | 
|  | DexType frontierType = minifierState.getFrontier(subType); | 
|  | if (minifierState.getReservationState(frontierType) == null) { | 
|  | // The reservation state should already be added. If it does not exist | 
|  | // it is because it is not reachable from the type hierarchy of program | 
|  | // classes and we can therefore disregard this interface. | 
|  | return; | 
|  | } | 
|  | InterfaceReservationState iState = interfaceStateMap.get(iface.getType()); | 
|  | if (iState != null) { | 
|  | iState.addReservationType(frontierType); | 
|  | } | 
|  | })); | 
|  | } | 
|  |  | 
|  | private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) { | 
|  | Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups); | 
|  | Set<DexCallSite> unifiedSeen = new HashSet<>(); | 
|  | Set<DexCallSite> seen = new HashSet<>(); | 
|  | for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state : | 
|  | globalStateMap.entrySet()) { | 
|  | for (DexCallSite callSite : state.getValue().callSites) { | 
|  | seen.add(callSite); | 
|  | if (unifiedMethods.contains(state.getKey())) { | 
|  | boolean added = unifiedSeen.add(callSite); | 
|  | assert added; | 
|  | } | 
|  | } | 
|  | } | 
|  | assert seen.size() == unifiedSeen.size(); | 
|  | assert unifiedSeen.containsAll(seen); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private boolean verifyAllMethodsAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) { | 
|  | Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups); | 
|  | Set<DexEncodedMethod> unifiedSeen = Sets.newIdentityHashSet(); | 
|  | Set<DexEncodedMethod> seen = Sets.newIdentityHashSet(); | 
|  | for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state : | 
|  | globalStateMap.entrySet()) { | 
|  | for (DexEncodedMethod method : state.getValue().methodStates.keySet()) { | 
|  | seen.add(method); | 
|  | if (unifiedMethods.contains(state.getKey())) { | 
|  | boolean added = unifiedSeen.add(method); | 
|  | assert added; | 
|  | } | 
|  | } | 
|  | } | 
|  | assert seen.size() == unifiedSeen.size(); | 
|  | assert unifiedSeen.containsAll(seen); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private void print(DexMethod method, Set<DexEncodedMethod> sourceMethods, PrintStream out) { | 
|  | out.println("-----------------------------------------------------------------------"); | 
|  | out.println("assignNameToInterfaceMethod(`" + method.toSourceString() + "`)"); | 
|  | out.println("-----------------------------------------------------------------------"); | 
|  | out.println("Source methods:"); | 
|  | for (DexEncodedMethod sourceMethod : sourceMethods) { | 
|  | out.println("  " + sourceMethod.toSourceString()); | 
|  | } | 
|  | out.println("States:"); | 
|  | out.println(); | 
|  | } | 
|  | } |