| // Copyright (c) 2020, 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.horizontalclassmerging; | 
 |  | 
 | import com.android.tools.r8.cf.CfVersion; | 
 | import com.android.tools.r8.graph.AppInfoWithClassHierarchy; | 
 | import com.android.tools.r8.graph.AppView; | 
 | import com.android.tools.r8.graph.DexAnnotationSet; | 
 | import com.android.tools.r8.graph.DexEncodedMethod; | 
 | import com.android.tools.r8.graph.DexItemFactory; | 
 | import com.android.tools.r8.graph.DexMethod; | 
 | import com.android.tools.r8.graph.DexProgramClass; | 
 | import com.android.tools.r8.graph.DexType; | 
 | import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature; | 
 | import com.android.tools.r8.graph.MethodAccessFlags; | 
 | import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult; | 
 | import com.android.tools.r8.graph.ParameterAnnotationsList; | 
 | import com.android.tools.r8.graph.ProgramMethod; | 
 | import com.android.tools.r8.horizontalclassmerging.code.VirtualMethodEntryPointSynthesizedCode; | 
 | import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode; | 
 | import com.android.tools.r8.utils.ListUtils; | 
 | import com.android.tools.r8.utils.OptionalBool; | 
 | import com.android.tools.r8.utils.structural.Ordered; | 
 | import com.google.common.collect.Iterables; | 
 | import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; | 
 | import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; | 
 | import it.unimi.dsi.fastutil.objects.Reference2IntMap; | 
 | import java.util.ArrayList; | 
 | import java.util.List; | 
 |  | 
 | public class VirtualMethodMerger { | 
 |  | 
 |   private final AppView<? extends AppInfoWithClassHierarchy> appView; | 
 |   private final DexItemFactory dexItemFactory; | 
 |   private final MergeGroup group; | 
 |   private final List<ProgramMethod> methods; | 
 |   private final DexMethod superMethod; | 
 |  | 
 |   public VirtualMethodMerger( | 
 |       AppView<? extends AppInfoWithClassHierarchy> appView, | 
 |       MergeGroup group, | 
 |       List<ProgramMethod> methods, | 
 |       DexMethod superMethod) { | 
 |     this.appView = appView; | 
 |     this.dexItemFactory = appView.dexItemFactory(); | 
 |     this.group = group; | 
 |     this.methods = methods; | 
 |     this.superMethod = superMethod; | 
 |   } | 
 |  | 
 |   public static class Builder { | 
 |     private final List<ProgramMethod> methods = new ArrayList<>(); | 
 |  | 
 |     public Builder add(ProgramMethod method) { | 
 |       methods.add(method); | 
 |       return this; | 
 |     } | 
 |  | 
 |     /** Get the super method handle if this method overrides a parent method. */ | 
 |     private DexMethod superMethod( | 
 |         AppView<? extends AppInfoWithClassHierarchy> appView, DexProgramClass target) { | 
 |       DexMethod template = methods.iterator().next().getReference(); | 
 |       SingleResolutionResult resolutionResult = | 
 |           appView | 
 |               .appInfo() | 
 |               .resolveMethodOnClass(template, target.getSuperType()) | 
 |               .asSingleResolution(); | 
 |  | 
 |       if (resolutionResult == null || resolutionResult.getResolvedMethod().isAbstract()) { | 
 |         // If there is no super method or the method is abstract it should not be called. | 
 |         return null; | 
 |       } | 
 |       if (resolutionResult.getResolvedHolder().isInterface()) { | 
 |         // Ensure that invoke virtual isn't called on an interface method. | 
 |         return resolutionResult | 
 |             .getResolvedMethod() | 
 |             .getReference() | 
 |             .withHolder(target.getSuperType(), appView.dexItemFactory()); | 
 |       } | 
 |       return resolutionResult.getResolvedMethod().getReference(); | 
 |     } | 
 |  | 
 |     public VirtualMethodMerger build( | 
 |         AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) { | 
 |       // If not all the classes are in the merge group, find the fallback super method to call. | 
 |       DexMethod superMethod = | 
 |           methods.size() < group.size() ? superMethod(appView, group.getTarget()) : null; | 
 |       return new VirtualMethodMerger(appView, group, methods, superMethod); | 
 |     } | 
 |   } | 
 |  | 
 |   public DexMethod getMethodReference() { | 
 |     return methods.iterator().next().getReference(); | 
 |   } | 
 |  | 
 |   public int getArity() { | 
 |     return getMethodReference().getArity(); | 
 |   } | 
 |  | 
 |   private DexMethod moveMethod(ClassMethodsBuilder classMethodsBuilder, ProgramMethod oldMethod) { | 
 |     DexMethod oldMethodReference = oldMethod.getReference(); | 
 |     DexMethod method = | 
 |         dexItemFactory.createFreshMethodNameWithHolder( | 
 |             oldMethodReference.name.toSourceString(), | 
 |             oldMethod.getHolderType(), | 
 |             oldMethodReference.proto, | 
 |             group.getTarget().getType(), | 
 |             classMethodsBuilder::isFresh); | 
 |  | 
 |     DexEncodedMethod encodedMethod = oldMethod.getDefinition().toTypeSubstitutedMethod(method); | 
 |     MethodAccessFlags flags = encodedMethod.getAccessFlags(); | 
 |     flags.unsetProtected(); | 
 |     flags.unsetPublic(); | 
 |     flags.setPrivate(); | 
 |     classMethodsBuilder.addDirectMethod(encodedMethod); | 
 |  | 
 |     return encodedMethod.getReference(); | 
 |   } | 
 |  | 
 |   private MethodAccessFlags getAccessFlags() { | 
 |     Iterable<MethodAccessFlags> allFlags = | 
 |         Iterables.transform(methods, ProgramMethod::getAccessFlags); | 
 |     MethodAccessFlags result = allFlags.iterator().next().copy(); | 
 |     assert Iterables.all(allFlags, flags -> !flags.isNative()); | 
 |     assert !result.isStrict() || Iterables.all(allFlags, MethodAccessFlags::isStrict); | 
 |     assert !result.isSynchronized() || Iterables.all(allFlags, MethodAccessFlags::isSynchronized); | 
 |     if (result.isAbstract() && Iterables.any(allFlags, flags -> !flags.isAbstract())) { | 
 |       result.unsetAbstract(); | 
 |     } | 
 |     if (result.isBridge() && Iterables.any(allFlags, flags -> !flags.isBridge())) { | 
 |       result.unsetBridge(); | 
 |     } | 
 |     if (result.isFinal()) { | 
 |       if (methods.size() < group.size() || Iterables.any(allFlags, flags -> !flags.isFinal())) { | 
 |         result.unsetFinal(); | 
 |       } | 
 |     } | 
 |     if (result.isSynthetic() && Iterables.any(allFlags, flags -> !flags.isSynthetic())) { | 
 |       result.unsetSynthetic(); | 
 |     } | 
 |     if (result.isVarargs() && Iterables.any(allFlags, flags -> !flags.isVarargs())) { | 
 |       result.unsetVarargs(); | 
 |     } | 
 |     result.unsetDeclaredSynchronized(); | 
 |     return result; | 
 |   } | 
 |  | 
 |   private DexMethod getNewMethodReference() { | 
 |     return ListUtils.first(methods).getReference().withHolder(group.getTarget(), dexItemFactory); | 
 |   } | 
 |  | 
 |   /** | 
 |    * If there is a super method and all methods are abstract, then we can simply remove all abstract | 
 |    * methods. | 
 |    */ | 
 |   private boolean isNop() { | 
 |     return superMethod != null | 
 |         && Iterables.all(methods, method -> method.getDefinition().isAbstract()); | 
 |   } | 
 |  | 
 |   /** | 
 |    * If the method is present on all classes in the merge group, and there is at most one | 
 |    * non-abstract method, then we can simply move that method (or the first abstract method) to the | 
 |    * target class. | 
 |    */ | 
 |   private boolean isTrivial() { | 
 |     if (superMethod != null) { | 
 |       return false; | 
 |     } | 
 |     if (methods.size() == 1) { | 
 |       return true; | 
 |     } | 
 |     int numberOfNonAbstractMethods = | 
 |         Iterables.size(Iterables.filter(methods, method -> !method.getDefinition().isAbstract())); | 
 |     return numberOfNonAbstractMethods <= 1; | 
 |   } | 
 |  | 
 |   boolean isNopOrTrivial() { | 
 |     return isNop() || isTrivial(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * If there is only a single method that does not override anything then it is safe to just move | 
 |    * it to the target type if it is not already in it. | 
 |    */ | 
 |   private void mergeTrivial( | 
 |       ClassMethodsBuilder classMethodsBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder) { | 
 |     DexMethod newMethodReference = getNewMethodReference(); | 
 |  | 
 |     // Find the first non-abstract method. If all are abstract, then select the first method. | 
 |     ProgramMethod representative = | 
 |         Iterables.find(methods, method -> !method.getDefinition().isAbstract(), null); | 
 |     if (representative == null) { | 
 |       representative = ListUtils.first(methods); | 
 |     } | 
 |  | 
 |     for (ProgramMethod method : methods) { | 
 |       if (method.getReference() == representative.getReference()) { | 
 |         lensBuilder.moveMethod(method.getReference(), newMethodReference); | 
 |       } else { | 
 |         lensBuilder.mapMethod(method.getReference(), newMethodReference); | 
 |       } | 
 |     } | 
 |  | 
 |     DexEncodedMethod newMethod; | 
 |     if (representative.getHolder() == group.getTarget()) { | 
 |       newMethod = representative.getDefinition(); | 
 |     } else { | 
 |       // If the method is not in the target type, move it. | 
 |       OptionalBool isLibraryMethodOverride = | 
 |           representative.getDefinition().isLibraryMethodOverride(); | 
 |       newMethod = | 
 |           representative | 
 |               .getDefinition() | 
 |               .toTypeSubstitutedMethod( | 
 |                   newMethodReference, | 
 |                   builder -> builder.setIsLibraryMethodOverrideIfKnown(isLibraryMethodOverride)); | 
 |     } | 
 |  | 
 |     newMethod.getAccessFlags().unsetFinal(); | 
 |  | 
 |     classMethodsBuilder.addVirtualMethod(newMethod); | 
 |   } | 
 |  | 
 |   public void merge( | 
 |       ClassMethodsBuilder classMethodsBuilder, | 
 |       HorizontalClassMergerGraphLens.Builder lensBuilder, | 
 |       Reference2IntMap<DexType> classIdentifiers) { | 
 |     assert !methods.isEmpty(); | 
 |  | 
 |     // Handle trivial merges. | 
 |     if (isNopOrTrivial()) { | 
 |       mergeTrivial(classMethodsBuilder, lensBuilder); | 
 |       return; | 
 |     } | 
 |  | 
 |     Int2ReferenceSortedMap<DexMethod> classIdToMethodMap = new Int2ReferenceAVLTreeMap<>(); | 
 |  | 
 |     CfVersion classFileVersion = null; | 
 |     ProgramMethod representative = null; | 
 |     for (ProgramMethod method : methods) { | 
 |       if (method.getDefinition().isAbstract()) { | 
 |         continue; | 
 |       } | 
 |       if (method.getDefinition().hasClassFileVersion()) { | 
 |         CfVersion methodVersion = method.getDefinition().getClassFileVersion(); | 
 |         classFileVersion = Ordered.maxIgnoreNull(classFileVersion, methodVersion); | 
 |       } | 
 |       DexMethod newMethod = moveMethod(classMethodsBuilder, method); | 
 |       lensBuilder.recordNewMethodSignature(method.getReference(), newMethod); | 
 |       classIdToMethodMap.put(classIdentifiers.getInt(method.getHolderType()), newMethod); | 
 |       if (representative == null) { | 
 |         representative = method; | 
 |       } | 
 |     } | 
 |  | 
 |     assert representative != null; | 
 |  | 
 |     // Use the first of the original methods as the original method for the merged constructor. | 
 |     DexMethod originalMethodReference = | 
 |         appView.graphLens().getOriginalMethodSignature(representative.getReference()); | 
 |     DexMethod bridgeMethodReference = | 
 |         dexItemFactory.createFreshMethodNameWithoutHolder( | 
 |             originalMethodReference.getName().toSourceString() + "$bridge", | 
 |             originalMethodReference.proto, | 
 |             originalMethodReference.getHolderType(), | 
 |             classMethodsBuilder::isFresh); | 
 |     DexEncodedMethod representativeMethod = representative.getDefinition(); | 
 |     DexMethod newMethodReference = getNewMethodReference(); | 
 |     AbstractSynthesizedCode synthesizedCode = | 
 |         new VirtualMethodEntryPointSynthesizedCode( | 
 |             classIdToMethodMap, | 
 |             group.getClassIdField(), | 
 |             superMethod, | 
 |             newMethodReference, | 
 |             bridgeMethodReference, | 
 |             appView.dexItemFactory()); | 
 |     DexEncodedMethod newMethod = | 
 |         new DexEncodedMethod( | 
 |             newMethodReference, | 
 |             getAccessFlags(), | 
 |             MethodTypeSignature.noSignature(), | 
 |             DexAnnotationSet.empty(), | 
 |             ParameterAnnotationsList.empty(), | 
 |             synthesizedCode, | 
 |             true, | 
 |             classFileVersion, | 
 |             representativeMethod.getApiLevelForDefinition(), | 
 |             representativeMethod.getApiLevelForCode()); | 
 |     if (!representative.getDefinition().isLibraryMethodOverride().isUnknown()) { | 
 |       newMethod.setLibraryMethodOverride(representative.getDefinition().isLibraryMethodOverride()); | 
 |     } | 
 |  | 
 |     // Map each old non-abstract method to the newly synthesized method in the graph lens. | 
 |     for (ProgramMethod oldMethod : methods) { | 
 |       lensBuilder.mapMethod(oldMethod.getReference(), newMethodReference); | 
 |     } | 
 |  | 
 |     // Add a mapping from a synthetic name to the synthetic merged method. | 
 |     lensBuilder.recordNewMethodSignature(bridgeMethodReference, newMethodReference); | 
 |  | 
 |     classMethodsBuilder.addVirtualMethod(newMethod); | 
 |   } | 
 | } |