| // 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.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.Collection; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| 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. |
| 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 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 ? true : 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); |
| } |
| |
| 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().slowCompareTo(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 ? true : 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 Equivalence<DexMethod> equivalence; |
| private final Equivalence<DexEncodedMethod> definitionEquivalence; |
| private final MethodNameMinifier.State minifierState; |
| |
| private final Map<DexCallSite, DexString> callSiteRenamings = new IdentityHashMap<>(); |
| |
| /** 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) { |
| this.appView = appView; |
| this.minifierState = minifierState; |
| this.equivalence = |
| appView.options().getProguardConfiguration().isOverloadAggressively() |
| ? MethodSignatureEquivalence.get() |
| : MethodJavaSignatureEquivalence.get(); |
| this.definitionEquivalence = |
| new Equivalence<DexEncodedMethod>() { |
| @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); |
| } |
| |
| Map<DexCallSite, DexString> getCallSiteRenamings() { |
| return callSiteRenamings; |
| } |
| |
| private void reserveNamesInInterfaces(Collection<DexClass> interfaces) { |
| for (DexClass iface : interfaces) { |
| assert iface.isInterface(); |
| minifierState.allocateReservationStateAndReserve(iface.type, iface.type); |
| InterfaceReservationState iFaceState = new InterfaceReservationState(iface); |
| iFaceState.reservationTypes.add(iface.type); |
| interfaceStateMap.put(iface.type, iFaceState); |
| } |
| } |
| |
| void assignNamesToInterfaceMethods(Timing timing, Collection<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(); |
| 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; |
| // 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); |
| if (implementedMethods.isEmpty()) { |
| return; |
| } |
| 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.appInfo()); |
| 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.method, virtualMethod.method)) { |
| 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); |
| for (DexCallSite callSite : groupState.callSites) { |
| assert !callSiteRenamings.containsKey(callSite); |
| callSiteRenamings.put(callSite, 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); |
| } |
| } |
| for (DexCallSite callSite : groupState.callSites) { |
| assert !callSiteRenamings.containsKey(callSite); |
| callSiteRenamings.put(callSite, newName); |
| } |
| } |
| |
| // 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() { |
| appView |
| .appInfo() |
| .forEachTypeInHierarchyOfLiveProgramClasses( |
| clazz -> { |
| // TODO(b/133091438): Extend the if check to test for !clazz.isLibrary(). |
| if (!clazz.isInterface()) { |
| for (DexType directlyImplemented : |
| appView.appInfo().implementedInterfaces(clazz.type)) { |
| InterfaceReservationState iState = interfaceStateMap.get(directlyImplemented); |
| if (iState != null) { |
| DexType frontierType = minifierState.getFrontier(clazz.type); |
| assert minifierState.getReservationState(frontierType) != null; |
| iState.reservationTypes.add(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(); |
| } |
| } |