| // 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.policies; |
| |
| import com.android.tools.r8.graph.AppView; |
| 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.DexProto; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode; |
| import com.android.tools.r8.horizontalclassmerging.MergeGroup; |
| import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing; |
| import com.android.tools.r8.utils.ArrayUtils; |
| import com.android.tools.r8.utils.IterableUtils; |
| import com.android.tools.r8.utils.ListUtils; |
| import com.android.tools.r8.utils.WorkList; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.IdentityHashMap; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * In the final round, we're not allowed to resolve constructor collisions by appending null |
| * arguments to constructor calls. |
| * |
| * <p>As an example, if a class in the program declares the constructors {@code <init>(A)} and |
| * {@code <init>(B)}, the classes A and B must not be merged. |
| * |
| * <p>To avoid collisions of this kind, we run over all the classes in the program, and apply the |
| * current set of merge groups to the constructor signatures of each class. Then, in case of a |
| * collision, we extract all the mapped types from the constructor signatures, and prevent merging |
| * of these types. |
| */ |
| public class NoConstructorCollisions extends MultiClassPolicyWithPreprocessing<Set<DexType>> { |
| |
| private final AppView<?> appView; |
| private final DexItemFactory dexItemFactory; |
| |
| public NoConstructorCollisions(AppView<?> appView, Mode mode) { |
| assert mode.isFinal(); |
| this.appView = appView; |
| this.dexItemFactory = appView.dexItemFactory(); |
| } |
| |
| /** |
| * Removes the classes in {@param collisionResolution} from {@param group}, and returns the new |
| * filtered group. |
| */ |
| @Override |
| public Collection<MergeGroup> apply(MergeGroup group, Set<DexType> collisionResolution) { |
| MergeGroup newGroup = |
| new MergeGroup( |
| Iterables.filter(group, clazz -> !collisionResolution.contains(clazz.getType()))); |
| return newGroup.isTrivial() ? Collections.emptyList() : ListUtils.newLinkedList(newGroup); |
| } |
| |
| /** |
| * Computes the set of classes that must not be merged, because the merging of these classes could |
| * lead to constructor collisions. |
| */ |
| @Override |
| public Set<DexType> preprocess(Collection<MergeGroup> groups) { |
| // Build a mapping from types to groups. |
| Map<DexType, MergeGroup> groupsByType = new IdentityHashMap<>(); |
| for (MergeGroup group : groups) { |
| for (DexProgramClass clazz : group) { |
| groupsByType.put(clazz.getType(), group); |
| } |
| } |
| |
| // Find the set of types that must not be merged, because they could lead to a constructor |
| // collision. |
| Set<DexType> collisionResolution = Sets.newIdentityHashSet(); |
| WorkList<DexProgramClass> workList = WorkList.newIdentityWorkList(appView.appInfo().classes()); |
| while (workList.hasNext()) { |
| // Iterate over all the instance initializers of the current class. If the current class is in |
| // a merge group, we must include all constructors of the entire merge group. |
| DexProgramClass current = workList.next(); |
| Iterable<DexProgramClass> group = |
| groupsByType.containsKey(current.getType()) |
| ? groupsByType.get(current.getType()) |
| : IterableUtils.singleton(current); |
| Set<DexMethod> seen = Sets.newIdentityHashSet(); |
| for (DexProgramClass clazz : group) { |
| for (DexEncodedMethod method : |
| clazz.directMethods(DexEncodedMethod::isInstanceInitializer)) { |
| // Rewrite the constructor reference using the current merge groups. |
| DexMethod newReference = rewriteReference(method.getReference(), groupsByType); |
| if (!seen.add(newReference)) { |
| // Found a collision. Block all referenced types from being merged. |
| for (DexType type : method.getProto().getBaseTypes(dexItemFactory)) { |
| if (type.isClassType() && groupsByType.containsKey(type)) { |
| collisionResolution.add(type); |
| } |
| } |
| } |
| } |
| } |
| workList.markAsSeen(group); |
| } |
| return collisionResolution; |
| } |
| |
| private DexProto rewriteProto(DexProto proto, Map<DexType, MergeGroup> groups) { |
| DexType[] parameters = |
| ArrayUtils.map( |
| proto.getParameters().values, |
| parameter -> rewriteType(parameter, groups), |
| DexType.EMPTY_ARRAY); |
| return dexItemFactory.createProto(rewriteType(proto.getReturnType(), groups), parameters); |
| } |
| |
| private DexMethod rewriteReference(DexMethod method, Map<DexType, MergeGroup> groups) { |
| return dexItemFactory.createMethod( |
| rewriteType(method.getHolderType(), groups), |
| rewriteProto(method.getProto(), groups), |
| method.getName()); |
| } |
| |
| private DexType rewriteType(DexType type, Map<DexType, MergeGroup> groups) { |
| if (type.isArrayType()) { |
| DexType baseType = type.toBaseType(dexItemFactory); |
| DexType rewrittenBaseType = rewriteType(baseType, groups); |
| if (rewrittenBaseType == baseType) { |
| return type; |
| } |
| return type.replaceBaseType(rewrittenBaseType, dexItemFactory); |
| } |
| if (type.isClassType()) { |
| if (!groups.containsKey(type)) { |
| return type; |
| } |
| return groups.get(type).getClasses().getFirst().getType(); |
| } |
| assert type.isPrimitiveType() || type.isVoidType(); |
| return type; |
| } |
| |
| @Override |
| public String getName() { |
| return "NoConstructorCollisions"; |
| } |
| } |