| // Copyright (c) 2017, 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.optimize; |
| |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| 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.GraphLense; |
| import com.android.tools.r8.ir.optimize.Inliner.Constraint; |
| import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| import java.util.Collections; |
| import java.util.IdentityHashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.BiFunction; |
| import java.util.function.Function; |
| |
| public class MemberRebindingAnalysis { |
| |
| private final AppInfoWithLiveness appInfo; |
| private final GraphLense lense; |
| private final GraphLense.Builder builder = GraphLense.builder(); |
| |
| public MemberRebindingAnalysis(AppInfoWithLiveness appInfo, GraphLense lense) { |
| assert lense.isContextFreeForMethods(); |
| this.appInfo = appInfo; |
| this.lense = lense; |
| } |
| |
| private DexMethod validTargetFor(DexMethod target, DexMethod original) { |
| DexClass clazz = appInfo.definitionFor(target.getHolder()); |
| assert clazz != null; |
| if (!clazz.isLibraryClass()) { |
| return target; |
| } |
| DexType newHolder; |
| if (clazz.isInterface()) { |
| newHolder = |
| firstLibraryClassForInterfaceTarget(target, original.getHolder(), DexClass::lookupMethod); |
| } else { |
| newHolder = firstLibraryClass(target.getHolder(), original.getHolder()); |
| } |
| return appInfo.dexItemFactory.createMethod(newHolder, original.proto, original.name); |
| } |
| |
| private DexField validTargetFor(DexField target, DexField original, |
| BiFunction<DexClass, DexField, DexEncodedField> lookup) { |
| DexClass clazz = appInfo.definitionFor(target.getHolder()); |
| assert clazz != null; |
| if (!clazz.isLibraryClass()) { |
| return target; |
| } |
| DexType newHolder; |
| if (clazz.isInterface()) { |
| newHolder = firstLibraryClassForInterfaceTarget(target, original.getHolder(), lookup); |
| } else { |
| newHolder = firstLibraryClass(target.getHolder(), original.getHolder()); |
| } |
| return appInfo.dexItemFactory.createField(newHolder, original.type, original.name); |
| } |
| |
| private <T> DexType firstLibraryClassForInterfaceTarget(T target, DexType current, |
| BiFunction<DexClass, T, ?> lookup) { |
| DexClass clazz = appInfo.definitionFor(current); |
| Object potential = lookup.apply(clazz, target); |
| if (potential != null) { |
| // Found, return type. |
| return current; |
| } |
| if (clazz.superType != null) { |
| DexType matchingSuper = firstLibraryClassForInterfaceTarget(target, clazz.superType, lookup); |
| if (matchingSuper != null) { |
| // Found in supertype, return first library class. |
| return clazz.isLibraryClass() ? current : matchingSuper; |
| } |
| } |
| for (DexType iface : clazz.interfaces.values) { |
| DexType matchingIface = firstLibraryClassForInterfaceTarget(target, iface, lookup); |
| if (matchingIface != null) { |
| // Found in interface, return first library class. |
| return clazz.isLibraryClass() ? current : matchingIface; |
| } |
| } |
| return null; |
| } |
| |
| private DexType firstLibraryClass(DexType top, DexType bottom) { |
| assert appInfo.definitionFor(top).isLibraryClass(); |
| DexClass searchClass = appInfo.definitionFor(bottom); |
| while (!searchClass.isLibraryClass()) { |
| searchClass = appInfo.definitionFor(searchClass.superType); |
| } |
| return searchClass.type; |
| } |
| |
| private DexEncodedMethod classLookup(DexMethod method) { |
| return appInfo.resolveMethodOnClass(method.getHolder(), method).asResultOfResolve(); |
| } |
| |
| private DexEncodedMethod interfaceLookup(DexMethod method) { |
| return appInfo.resolveMethodOnInterface(method.getHolder(), method).asResultOfResolve(); |
| } |
| |
| private DexEncodedMethod anyLookup(DexMethod method) { |
| return appInfo.resolveMethod(method.getHolder(), method).asResultOfResolve(); |
| } |
| |
| private void computeMethodRebinding(Set<DexMethod> methods, |
| Function<DexMethod, DexEncodedMethod> lookupTarget) { |
| for (DexMethod method : methods) { |
| method = lense.lookupMethod(method); |
| // We can safely ignore array types, as the corresponding methods are defined in a library. |
| if (!method.getHolder().isClassType()) { |
| continue; |
| } |
| DexClass originalClass = appInfo.definitionFor(method.holder); |
| // We can safely ignore calls to library classes, as those cannot be rebound. |
| if (originalClass == null || originalClass.isLibraryClass()) { |
| continue; |
| } |
| DexEncodedMethod target = lookupTarget.apply(method); |
| // Rebind to the lowest library class or program class. |
| if (target != null && target.method != method) { |
| DexClass targetClass = appInfo.definitionFor(target.method.holder); |
| // If the target class is not public but the targeted method is, we might run into |
| // visibility problems when rebinding. |
| if (!targetClass.accessFlags.isPublic() && target.accessFlags.isPublic()) { |
| // If the original class is public and this method is public, it might have been called |
| // from anywhere, so we need a bridge. Likewise, if the original is in a different |
| // package, we might need a bridge, too. |
| String packageDescriptor = |
| originalClass.accessFlags.isPublic() ? null : method.holder.getPackageDescriptor(); |
| if (packageDescriptor == null |
| || !packageDescriptor.equals(targetClass.type.getPackageDescriptor())) { |
| DexProgramClass bridgeHolder = findBridgeMethodHolder(originalClass, targetClass, |
| packageDescriptor); |
| assert bridgeHolder != null; |
| DexEncodedMethod bridgeMethod = |
| target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory); |
| bridgeHolder.addMethod(bridgeMethod); |
| assert lookupTarget.apply(method) == bridgeMethod; |
| target = bridgeMethod; |
| } |
| } |
| builder.map(method, validTargetFor(target.method, method)); |
| } |
| } |
| } |
| |
| private DexProgramClass findBridgeMethodHolder(DexClass originalClass, DexClass targetClass, |
| String packageDescriptor) { |
| if (originalClass == targetClass || originalClass.isLibraryClass()) { |
| return null; |
| } |
| DexProgramClass newHolder = null; |
| // Recurse through supertype chain. |
| if (originalClass.superType.isSubtypeOf(targetClass.type, appInfo)) { |
| DexClass superClass = appInfo.definitionFor(originalClass.superType); |
| newHolder = findBridgeMethodHolder(superClass, targetClass, packageDescriptor); |
| } else { |
| for (DexType iface : originalClass.interfaces.values) { |
| if (iface.isSubtypeOf(targetClass.type, appInfo)) { |
| DexClass interfaceClass = appInfo.definitionFor(iface); |
| newHolder = findBridgeMethodHolder(interfaceClass, targetClass, packageDescriptor); |
| } |
| } |
| } |
| if (newHolder != null) { |
| // A supertype fulfills the visibility requirements. |
| return newHolder; |
| } else if (originalClass.accessFlags.isPublic() |
| || originalClass.type.getPackageDescriptor().equals(packageDescriptor)) { |
| // This class is visible. Return it if it is a program class, otherwise null. |
| return originalClass.asProgramClass(); |
| } |
| return null; |
| } |
| |
| private void computeFieldRebinding(Map<DexField, Set<DexEncodedMethod>> fields, |
| BiFunction<DexType, DexField, DexEncodedField> lookup, |
| BiFunction<DexClass, DexField, DexEncodedField> lookupTargetOnClass) { |
| for (Map.Entry<DexField, Set<DexEncodedMethod>> entry : fields.entrySet()) { |
| DexField field = entry.getKey(); |
| field = lense.lookupField(field); |
| DexEncodedField target = lookup.apply(field.getHolder(), field); |
| // Rebind to the lowest library class or program class. Do not rebind accesses to fields that |
| // are not visible from the access context. |
| Set<DexEncodedMethod> contexts = entry.getValue(); |
| if (target != null && target.field != field |
| && contexts.stream().allMatch(context -> |
| isVisibleFromOriginalContext(context.method.getHolder(), target))) { |
| builder.map(field, validTargetFor(target.field, field, lookupTargetOnClass)); |
| } |
| } |
| } |
| |
| private boolean isVisibleFromOriginalContext(DexType context, DexEncodedField field) { |
| DexType holderType = field.field.getHolder(); |
| DexClass holder = appInfo.definitionFor(holderType); |
| if (holder == null) { |
| return false; |
| } |
| Constraint classVisibility = |
| Constraint.deriveConstraint(context, holderType, holder.accessFlags, appInfo); |
| if (classVisibility == Constraint.NEVER) { |
| return false; |
| } |
| Constraint fieldVisibility = |
| Constraint.deriveConstraint(context, holderType, field.accessFlags, appInfo); |
| return fieldVisibility != Constraint.NEVER; |
| } |
| |
| private Map<DexField, Set<DexEncodedMethod>> mergeFieldAccessContexts( |
| Map<DexField, Set<DexEncodedMethod>> reads, |
| Map<DexField, Set<DexEncodedMethod>> writes) { |
| Map<DexField, Set<DexEncodedMethod>> result = new IdentityHashMap<>(); |
| Set<DexField> fields = Sets.union(reads.keySet(), writes.keySet()); |
| for (DexField field : fields) { |
| Set<DexEncodedMethod> contexts = Sets.newIdentityHashSet(); |
| contexts.addAll(reads.getOrDefault(field, ImmutableSet.of())); |
| contexts.addAll(writes.getOrDefault(field, ImmutableSet.of())); |
| result.put(field, contexts); |
| } |
| return Collections.unmodifiableMap(result); |
| } |
| |
| public GraphLense run() { |
| // Virtual invokes are on classes, so use class resolution. |
| computeMethodRebinding(appInfo.virtualInvokes, this::classLookup); |
| // Interface invokes are always on interfaces, so use interface resolution. |
| computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup); |
| // Super invokes can be on both kinds, decide using the holder class. |
| computeMethodRebinding(appInfo.superInvokes, this::anyLookup); |
| // Direct invokes (private/constructor) can also be on both kinds. |
| computeMethodRebinding(appInfo.directInvokes, this::anyLookup); |
| // Likewise static invokes. |
| computeMethodRebinding(appInfo.staticInvokes, this::anyLookup); |
| |
| computeFieldRebinding( |
| mergeFieldAccessContexts(appInfo.staticFieldReads, appInfo.staticFieldWrites), |
| appInfo::resolveFieldOn, DexClass::lookupField); |
| computeFieldRebinding( |
| mergeFieldAccessContexts(appInfo.instanceFieldReads, appInfo.instanceFieldWrites), |
| appInfo::resolveFieldOn, DexClass::lookupField); |
| |
| return builder.build(appInfo.dexItemFactory, lense); |
| } |
| } |