| // 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.DexEncodedMethod; | 
 | import com.android.tools.r8.graph.DexField; | 
 | import com.android.tools.r8.graph.DexItem; | 
 | import com.android.tools.r8.graph.DexMethod; | 
 | import com.android.tools.r8.graph.DexReference; | 
 | import com.android.tools.r8.graph.DexString; | 
 | import com.android.tools.r8.graph.DexType; | 
 | import com.android.tools.r8.graph.InnerClassAttribute; | 
 | import com.android.tools.r8.graph.ResolutionResult; | 
 | import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming; | 
 | import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming; | 
 | import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming; | 
 | import com.android.tools.r8.utils.DescriptorUtils; | 
 | import com.android.tools.r8.utils.InternalOptions; | 
 | import com.google.common.collect.ImmutableMap; | 
 | import java.util.ArrayList; | 
 | import java.util.IdentityHashMap; | 
 | import java.util.List; | 
 | import java.util.Map; | 
 | import java.util.function.Consumer; | 
 | import java.util.function.Function; | 
 | import java.util.function.Predicate; | 
 |  | 
 | class MinifiedRenaming extends NamingLens { | 
 |  | 
 |   final AppView<?> appView; | 
 |   private final Map<String, String> packageRenaming; | 
 |   private final Map<DexItem, DexString> renaming = new IdentityHashMap<>(); | 
 |  | 
 |   MinifiedRenaming( | 
 |       AppView<?> appView, | 
 |       ClassRenaming classRenaming, | 
 |       MethodRenaming methodRenaming, | 
 |       FieldRenaming fieldRenaming) { | 
 |     this.appView = appView; | 
 |     this.packageRenaming = classRenaming.packageRenaming; | 
 |     renaming.putAll(classRenaming.classRenaming); | 
 |     renaming.putAll(methodRenaming.renaming); | 
 |     renaming.putAll(methodRenaming.callSiteRenaming); | 
 |     renaming.putAll(fieldRenaming.renaming); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public String lookupPackageName(String packageName) { | 
 |     return packageRenaming.getOrDefault(packageName, packageName); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public DexString lookupDescriptor(DexType type) { | 
 |     return renaming.getOrDefault(type, type.descriptor); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options) { | 
 |     if (attribute.getInnerName() == null) { | 
 |       return null; | 
 |     } | 
 |     DexType innerType = attribute.getInner(); | 
 |     String inner = DescriptorUtils.descriptorToInternalName(innerType.descriptor.toString()); | 
 |     // At this point we assume the input was of the form: <OuterType>$<index><InnerName> | 
 |     // Find the mapped type and if it remains the same return that, otherwise split at | 
 |     // either the input separator ($, $$, or anything that starts with $) or $ (that we recover | 
 |     // while minifying, e.g., empty separator, under bar, etc.). | 
 |     String innerTypeMapped = | 
 |         DescriptorUtils.descriptorToInternalName(lookupDescriptor(innerType).toString()); | 
 |     if (inner.equals(innerTypeMapped)) { | 
 |       return attribute.getInnerName(); | 
 |     } | 
 |     String separator = DescriptorUtils.computeInnerClassSeparator( | 
 |         attribute.getOuter(), innerType, attribute.getInnerName()); | 
 |     if (separator == null) { | 
 |       separator = String.valueOf(DescriptorUtils.INNER_CLASS_SEPARATOR); | 
 |     } | 
 |     int index = innerTypeMapped.lastIndexOf(separator); | 
 |     if (index < 0) { | 
 |       assert !options.keepInnerClassStructure() | 
 |               || options.getProguardConfiguration().hasApplyMappingFile() | 
 |           : innerType + " -> " + innerTypeMapped; | 
 |       String descriptor = lookupDescriptor(innerType).toString(); | 
 |       return options.itemFactory.createString( | 
 |           DescriptorUtils.getUnqualifiedClassNameFromDescriptor(descriptor)); | 
 |     } | 
 |     return options.itemFactory.createString(innerTypeMapped.substring(index + separator.length())); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public DexString lookupName(DexMethod method) { | 
 |     DexString renamed = renaming.get(method); | 
 |     if (renamed != null) { | 
 |       return renamed; | 
 |     } | 
 |     // If the method does not have a direct renaming, return the resolutions mapping. | 
 |     ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method); | 
 |     if (resolutionResult.isSingleResolution()) { | 
 |       return renaming.getOrDefault(resolutionResult.getSingleTarget().method, method.name); | 
 |     } | 
 |     // If resolution fails, the method must be renamed consistently with the targets that give rise | 
 |     // to the failure. | 
 |     if (resolutionResult.isFailedResolution()) { | 
 |       List<DexEncodedMethod> targets = new ArrayList<>(); | 
 |       resolutionResult.asFailedResolution().forEachFailureDependency(targets::add); | 
 |       if (!targets.isEmpty()) { | 
 |         DexString firstRename = renaming.get(targets.get(0).method); | 
 |         assert targets.stream().allMatch(target -> renaming.get(target.method) == firstRename); | 
 |         if (firstRename != null) { | 
 |           return firstRename; | 
 |         } | 
 |       } | 
 |     } | 
 |     // If no renaming can be found the default is the methods name. | 
 |     return method.name; | 
 |   } | 
 |  | 
 |   @Override | 
 |   public DexString lookupMethodName(DexCallSite callSite) { | 
 |     return renaming.getOrDefault(callSite, callSite.methodName); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public DexString lookupName(DexField field) { | 
 |     return renaming.getOrDefault(field, field.name); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public boolean verifyNoOverlap(Map<DexType, DexString> map) { | 
 |     for (DexType alreadyRenamedInOtherLens : map.keySet()) { | 
 |       assert !renaming.containsKey(alreadyRenamedInOtherLens); | 
 |       assert !renaming.containsKey( | 
 |           appView.dexItemFactory().createType(map.get(alreadyRenamedInOtherLens))); | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   @Override | 
 |   void forAllRenamedTypes(Consumer<DexType> consumer) { | 
 |     DexReference.filterDexType(DexReference.filterDexReference(renaming.keySet().stream())) | 
 |         .forEach(consumer); | 
 |   } | 
 |  | 
 |   @Override | 
 |   <T extends DexItem> Map<String, T> getRenamedItems( | 
 |       Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) { | 
 |     return renaming.keySet().stream() | 
 |         .filter(item -> (clazz.isInstance(item) && predicate.test(clazz.cast(item)))) | 
 |         .map(clazz::cast) | 
 |         .collect(ImmutableMap.toImmutableMap(namer, i -> i)); | 
 |   } | 
 |  | 
 |   /** | 
 |    * Checks that the renaming of the method reference {@param method} is consistent with the | 
 |    * renaming of the resolution target of {@param method}. | 
 |    */ | 
 |   @Override | 
 |   public boolean verifyRenamingConsistentWithResolution(DexMethod method) { | 
 |     if (method.holder.isArrayType()) { | 
 |       // Array methods are never renamed, so do not bother to check. | 
 |       return true; | 
 |     } | 
 |  | 
 |     ResolutionResult resolution = appView.appInfo().resolveMethod(method.holder, method); | 
 |     assert resolution != null; | 
 |  | 
 |     if (resolution.isSingleResolution()) { | 
 |       // If we can resolve `item`, then the renaming for `item` and its resolution should be the | 
 |       // same. | 
 |       DexEncodedMethod resolvedMethod = resolution.asSingleResolution().getResolvedMethod(); | 
 |       assert lookupName(method) == lookupName(resolvedMethod.method); | 
 |       return true; | 
 |     } | 
 |  | 
 |     assert resolution.isFailedResolution(); | 
 |  | 
 |     // If we can't resolve `item` it is questionable to record a renaming for it. However, it can | 
 |     // be required to preserve errors. | 
 |     // | 
 |     // Example: | 
 |     //   class A { private void m() } | 
 |     //   class B extends A {} | 
 |     //   class Main { public static void main() { new B().m(); } } | 
 |     // | 
 |     // In this example, the invoke-virtual instruction targeting m() in Main does not resolve, | 
 |     // since the method is private. On the JVM this fails with an IllegalAccessError. | 
 |     // | 
 |     // If A.m() is renamed to A.a(), and the invoke-virtual instruction in Main is not changed to | 
 |     // target a(), then the program will start failing with a NoSuchMethodError instead of an | 
 |     // IllegalAccessError. | 
 |     resolution | 
 |         .asFailedResolution() | 
 |         .forEachFailureDependency( | 
 |             failureDependence -> { | 
 |               assert lookupName(method) == lookupName(failureDependence.method); | 
 |             }); | 
 |     return true; | 
 |   } | 
 |  | 
 |   @Override | 
 |   public String toString() { | 
 |     StringBuilder builder = new StringBuilder(); | 
 |     renaming.forEach( | 
 |         (item, str) -> { | 
 |           if (item instanceof DexType) { | 
 |             builder.append("[c] "); | 
 |           } else if (item instanceof DexMethod) { | 
 |             builder.append("[m] "); | 
 |           } else if (item instanceof DexField) { | 
 |             builder.append("[f] "); | 
 |           } | 
 |           builder.append(item.toSourceString()); | 
 |           builder.append(" -> "); | 
 |           builder.append(str.toSourceString()); | 
 |           builder.append('\n'); | 
 |         }); | 
 |     return builder.toString(); | 
 |   } | 
 | } |