blob: 79bbfe664e5112328ce79a3b07ee29ada4af206c [file] [log] [blame]
// 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 static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiSafeForMemberRebinding;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexDefinitionSupplier;
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.FieldAccessInfoCollection;
import com.android.tools.r8.graph.LibraryMethod;
import com.android.tools.r8.graph.MemberRebindingBridgeCode;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.MethodAccessInfoCollection;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BiForEachable;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.TriConsumer;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiFunction;
import java.util.function.Function;
public class MemberRebindingAnalysis {
private final AndroidApiLevelCompute androidApiLevelCompute;
private final AppView<AppInfoWithLiveness> appView;
private final InternalOptions options;
private final MemberRebindingLens.Builder lensBuilder;
private final Map<DexMethod, DexClassAndMethod> insertedLibraryBridges = new IdentityHashMap<>();
public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
assert appView.graphLens().isContextFreeForMethods();
this.androidApiLevelCompute = appView.apiLevelCompute();
this.appView = appView;
this.options = appView.options();
this.lensBuilder = MemberRebindingLens.builder(appView);
}
private DexMethod validMemberRebindingTargetForNonProgramMethod(
DexClassAndMethod resolvedMethod,
SingleResolutionResult<?> resolutionResult,
ProgramMethodSet contexts,
Type invokeType,
DexMethod original) {
assert !resolvedMethod.isProgramMethod();
if (invokeType.isDirect()) {
return original;
}
if (!invokeType.isSuper() || !options.canHaveSuperInvokeBug()) {
LibraryMethod eligibleLibraryMethod = null;
SingleResolutionResult<?> currentResolutionResult = resolutionResult;
while (currentResolutionResult != null) {
DexClassAndMethod currentResolvedMethod = currentResolutionResult.getResolutionPair();
if (canRebindDirectlyToLibraryMethod(
currentResolvedMethod,
currentResolutionResult.withInitialResolutionHolder(
currentResolutionResult.getResolvedHolder()),
contexts,
invokeType,
original)) {
eligibleLibraryMethod = currentResolvedMethod.asLibraryMethod();
}
if (appView.getAssumeInfoCollection().contains(currentResolvedMethod)) {
break;
}
DexClass currentResolvedHolder = currentResolvedMethod.getHolder();
if (resolvedMethod.getDefinition().belongsToVirtualPool()
&& !currentResolvedHolder.isInterface()
&& currentResolvedHolder.getSuperType() != null) {
currentResolutionResult =
appView
.appInfo()
.resolveMethodOnClassLegacy(currentResolvedHolder.getSuperType(), original)
.asSingleResolution();
} else {
break;
}
}
if (eligibleLibraryMethod != null) {
return eligibleLibraryMethod.getReference();
}
}
if (resolvedMethod.getDefinition().isStatic()) {
return original;
}
// If we could not find a valid library method to rebind to then create a bridge on the top most
// program class before crossing into library.
DexClass newHolder =
resolvedMethod.getHolder().isInterface()
? firstProgramClassForTarget(
appView, resolvedMethod.getReference(), original.getHolderType())
: firstProgramClass(appView, original.getHolderType());
if (newHolder == null || newHolder.isNotProgramClass()) {
return original;
}
// We cannot insert default methods on interfaces after desugaring, so we return the resolved
// method.
if (newHolder.isInterface() && !options.canUseDefaultAndStaticInterfaceMethods()) {
return resolvedMethod.getReference();
}
if (!appView.getAssumeInfoCollection().get(resolvedMethod).isEmpty()) {
return original;
}
DexMethod bridge = original.withHolder(newHolder, appView.dexItemFactory());
insertedLibraryBridges.put(bridge, resolvedMethod);
return bridge;
}
private boolean canRebindDirectlyToLibraryMethod(
DexClassAndMethod resolvedMethod,
SingleResolutionResult<?> resolutionResult,
ProgramMethodSet contexts,
Type invokeType,
DexMethod original) {
// TODO(b/194422791): It could potentially be that `original.holder` is not a subtype of
// `resolvedMethod.holder` on all API levels, in which case it is not OK to rebind to the
// resolved method.
return resolvedMethod.isLibraryMethod()
&& isAccessibleInAllContexts(resolvedMethod, resolutionResult, contexts)
&& !isInvokeSuperToInterfaceMethod(resolvedMethod, invokeType)
&& !isInvokeSuperToAbstractMethod(resolvedMethod, invokeType)
&& isApiSafeForMemberRebinding(
resolvedMethod.asLibraryMethod(), original, androidApiLevelCompute, options);
}
private boolean isAccessibleInAllContexts(
DexClassAndMethod resolvedMethod,
SingleResolutionResult<?> resolutionResult,
ProgramMethodSet contexts) {
if (resolvedMethod.getHolder().isPublic() && resolvedMethod.getDefinition().isPublic()) {
return true;
}
return Iterables.all(
contexts,
context -> resolutionResult.isAccessibleFrom(context, appView.appInfo()).isTrue());
}
private boolean isInvokeSuperToInterfaceMethod(DexClassAndMethod method, Type invokeType) {
return method.getHolder().isInterface() && invokeType.isSuper();
}
private boolean isInvokeSuperToAbstractMethod(DexClassAndMethod method, Type invokeType) {
return method.getAccessFlags().isAbstract() && invokeType.isSuper();
}
public static DexField validMemberRebindingTargetFor(
DexDefinitionSupplier definitions, DexClassAndField field, DexField original) {
if (field.isProgramField()) {
return field.getReference();
}
DexClass fieldHolder = field.getHolder();
DexType newHolder =
firstLibraryClassOrFirstInterfaceTarget(
fieldHolder,
definitions,
field.getReference(),
original.getHolderType(),
DexClass::lookupField);
return newHolder != null
? original.withHolder(newHolder, definitions.dexItemFactory())
: original;
}
private static <T> DexType firstLibraryClassOrFirstInterfaceTarget(
DexClass holder,
DexDefinitionSupplier definitions,
T target,
DexType current,
BiFunction<DexClass, T, ?> lookup) {
return holder.isInterface()
? firstLibraryClassForInterfaceTarget(definitions, target, current, lookup)
: firstLibraryClass(definitions, current);
}
private static <T> DexType firstLibraryClassForInterfaceTarget(
DexDefinitionSupplier definitions,
T target,
DexType current,
BiFunction<DexClass, T, ?> lookup) {
DexClass clazz = definitions.definitionFor(current);
if (clazz == null) {
return null;
}
Object potential = lookup.apply(clazz, target);
if (potential != null) {
// Found, return type.
return current;
}
if (clazz.superType != null) {
DexType matchingSuper =
firstLibraryClassForInterfaceTarget(definitions, target, clazz.superType, lookup);
if (matchingSuper != null) {
// Found in supertype, return first library class.
return clazz.isNotProgramClass() ? current : matchingSuper;
}
}
for (DexType iface : clazz.getInterfaces()) {
DexType matchingIface =
firstLibraryClassForInterfaceTarget(definitions, target, iface, lookup);
if (matchingIface != null) {
// Found in interface, return first library class.
return clazz.isNotProgramClass() ? current : matchingIface;
}
}
return null;
}
private static DexClass firstProgramClassForTarget(
DexDefinitionSupplier definitions, DexMethod target, DexType current) {
DexClass clazz = definitions.contextIndependentDefinitionFor(current);
if (clazz == null) {
return null;
}
DexEncodedMethod potential = clazz.lookupMethod(target);
if (potential != null) {
// Found, return type.
return clazz;
}
if (clazz.superType != null) {
DexClass matchingSuper = firstProgramClassForTarget(definitions, target, clazz.superType);
if (matchingSuper != null) {
// Found in supertype, return first program class.
return matchingSuper.isNotProgramClass() ? clazz : matchingSuper;
}
}
for (DexType iface : clazz.getInterfaces()) {
DexClass matchingIface = firstProgramClassForTarget(definitions, target, iface);
if (matchingIface != null) {
// Found in interface, return first program class.
return matchingIface.isNotProgramClass() ? clazz : matchingIface;
}
}
return null;
}
private static DexType firstLibraryClass(DexDefinitionSupplier definitions, DexType bottom) {
DexClass searchClass = definitions.contextIndependentDefinitionFor(bottom);
while (searchClass != null && searchClass.isProgramClass()) {
searchClass =
definitions.definitionFor(searchClass.getSuperType(), searchClass.asProgramClass());
}
return searchClass != null ? searchClass.getType() : null;
}
private static DexProgramClass firstProgramClass(
DexDefinitionSupplier definitions, DexType bottom) {
DexProgramClass searchClass =
DexProgramClass.asProgramClassOrNull(definitions.contextIndependentDefinitionFor(bottom));
while (searchClass != null && searchClass.isProgramClass()) {
DexClass superClass =
definitions.definitionFor(searchClass.getSuperType(), searchClass.asProgramClass());
if (superClass.isNotProgramClass()) {
return searchClass;
}
searchClass = superClass.asProgramClass();
}
return null;
}
private MethodResolutionResult resolveMethodOnClass(DexMethod method) {
return appView.appInfo().resolveMethodOnClassLegacy(method.holder, method);
}
private MethodResolutionResult resolveMethodOnInterface(DexMethod method) {
return appView.appInfo().resolveMethodOnInterfaceLegacy(method.holder, method);
}
private MethodResolutionResult resolveMethod(DexMethod method) {
return appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method);
}
private void computeMethodRebinding(MethodAccessInfoCollection methodAccessInfoCollection) {
// Virtual invokes are on classes, so use class resolution.
computeMethodRebinding(
methodAccessInfoCollection::forEachVirtualInvoke, this::resolveMethodOnClass, Type.VIRTUAL);
// Interface invokes are always on interfaces, so use interface resolution.
computeMethodRebinding(
methodAccessInfoCollection::forEachInterfaceInvoke,
this::resolveMethodOnInterface,
Type.INTERFACE);
// Super invokes can be on both kinds, decide using the holder class.
computeMethodRebinding(
methodAccessInfoCollection::forEachSuperInvoke, this::resolveMethod, Type.SUPER);
// Likewise static invokes.
computeMethodRebinding(
methodAccessInfoCollection::forEachStaticInvoke, this::resolveMethod, Type.STATIC);
}
private void computeMethodRebinding(
BiForEachable<DexMethod, ProgramMethodSet> methodsWithContexts,
Function<DexMethod, MethodResolutionResult> resolver,
Type invokeType) {
Map<DexProgramClass, List<Pair<DexMethod, DexClassAndMethod>>> bridges =
new IdentityHashMap<>();
TriConsumer<DexProgramClass, DexMethod, DexClassAndMethod> addBridge =
(bridgeHolder, method, target) ->
bridges
.computeIfAbsent(bridgeHolder, k -> new ArrayList<>())
.add(new Pair<>(method, target));
methodsWithContexts.forEach(
(method, contexts) -> {
MethodResolutionResult resolutionResult = resolver.apply(method);
if (!resolutionResult.isSingleResolution()) {
return;
}
if (method.getHolderType().isArrayType()) {
assert resolutionResult.getResolvedHolder().getType()
== appView.dexItemFactory().objectType;
lensBuilder.map(
method, resolutionResult.getResolvedMethod().getReference(), invokeType);
return;
}
// TODO(b/128404854) Rebind to the lowest library class or program class. For now we allow
// searching in library for methods, but this should be done on classpath instead.
DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
if (resolvedMethod.getReference() == method) {
return;
}
DexClass initialResolutionHolder = resolutionResult.getInitialResolutionHolder();
DexMethod bridgeMethod = null;
if (initialResolutionHolder.isProgramClass()) {
// In Java bytecode, it is only possible to target interface methods that are in one of
// the immediate super-interfaces via a super-invocation (see
// IndirectSuperInterfaceTest).
// To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
// bridge method when we are about to rebind to an interface method that is not the
// original target.
if (needsBridgeForInterfaceMethod(
initialResolutionHolder, resolvedMethod, invokeType)) {
bridgeMethod =
insertBridgeForInterfaceMethod(
method, resolvedMethod, initialResolutionHolder.asProgramClass(), addBridge);
} else {
// If the target class is not public but the targeted method is, we might run into
// visibility problems when rebinding.
if (contexts.stream()
.anyMatch(context -> mayNeedBridgeForVisibility(context, resolvedMethod))) {
bridgeMethod =
insertBridgeForVisibilityIfNeeded(
method, resolvedMethod, initialResolutionHolder, addBridge);
}
}
}
if (bridgeMethod != null) {
lensBuilder.map(method, bridgeMethod, invokeType);
} else if (resolvedMethod.isProgramMethod()) {
lensBuilder.map(method, resolvedMethod.getReference(), invokeType);
} else {
lensBuilder.map(
method,
validMemberRebindingTargetForNonProgramMethod(
resolvedMethod,
resolutionResult.asSingleResolution(),
contexts,
invokeType,
method),
invokeType);
}
});
bridges.forEach(
(bridgeHolder, targets) -> {
// Sorting the list of bridges within a class maintains a deterministic order of entries
// in the method collection.
targets.sort(Comparator.comparing(Pair::getFirst));
for (Pair<DexMethod, DexClassAndMethod> pair : targets) {
DexMethod method = pair.getFirst();
DexClassAndMethod target = pair.getSecond();
DexMethod bridgeMethod =
method.withHolder(bridgeHolder.getType(), appView.dexItemFactory());
if (bridgeHolder.getMethodCollection().getMethod(bridgeMethod) == null) {
DexEncodedMethod targetDefinition = target.getDefinition();
DexEncodedMethod bridgeMethodDefinition =
targetDefinition.toForwardingMethod(
bridgeHolder,
appView,
builder -> {
if (!targetDefinition.isAbstract()
&& targetDefinition.getApiLevelForCode().isNotSetApiLevel()) {
assert !target.isProgramMethod();
builder.setApiLevelForCode(
appView
.apiLevelCompute()
.computeApiLevelForLibraryReference(
targetDefinition.getReference(),
appView.computedMinApiLevel()));
}
builder.setIsLibraryMethodOverrideIf(
!target.isProgramMethod() && !targetDefinition.isStatic(),
OptionalBool.TRUE);
});
bridgeHolder.addMethod(bridgeMethodDefinition);
}
assert resolver.apply(method).getResolvedMethod().getReference() == bridgeMethod;
}
});
}
private boolean needsBridgeForInterfaceMethod(
DexClass originalClass, DexClassAndMethod method, Type invokeType) {
return options.isGeneratingClassFiles()
&& invokeType == Type.SUPER
&& method.getHolder() != originalClass
&& method.getHolder().isInterface();
}
private DexMethod insertBridgeForInterfaceMethod(
DexMethod method,
DexClassAndMethod target,
DexProgramClass originalClass,
TriConsumer<DexProgramClass, DexMethod, DexClassAndMethod> bridges) {
// If `targetClass` is a class, then insert the bridge method on the upper-most super class that
// implements the interface. Otherwise, if it is an interface, then insert the bridge method
// directly on the interface (because that interface must be the immediate super type, assuming
// that the super-invocation is not broken in advance).
//
// Note that, to support compiling from DEX to CF, we would need to rewrite the targets of
// invoke-super instructions that hit indirect interface methods such that they always target
// a method in an immediate super-interface, since this works on Art but not on the JVM.
DexProgramClass bridgeHolder =
findHolderForInterfaceMethodBridge(originalClass, target.getHolderType());
assert bridgeHolder != null;
assert bridgeHolder != target.getHolder();
bridges.accept(bridgeHolder, method, target);
return target.getReference().withHolder(bridgeHolder.getType(), appView.dexItemFactory());
}
private DexProgramClass findHolderForInterfaceMethodBridge(DexProgramClass clazz, DexType iface) {
if (clazz.accessFlags.isInterface()) {
return clazz;
}
DexClass superClass = appView.definitionFor(clazz.superType);
if (superClass == null
|| superClass.isNotProgramClass()
|| !appView.appInfo().isSubtype(superClass.type, iface)) {
return clazz;
}
return findHolderForInterfaceMethodBridge(superClass.asProgramClass(), iface);
}
private boolean mayNeedBridgeForVisibility(ProgramMethod context, DexClassAndMethod method) {
DexType holderType = method.getHolderType();
DexClass holder = appView.definitionFor(holderType);
if (holder == null) {
return false;
}
ConstraintWithTarget classVisibility =
ConstraintWithTarget.deriveConstraint(
context, holderType, holder.getAccessFlags(), appView);
ConstraintWithTarget methodVisibility =
ConstraintWithTarget.deriveConstraint(
context, holderType, method.getAccessFlags(), appView);
// We may need bridge for visibility if the target class is not visible while the target method
// is visible from the calling context.
return classVisibility == ConstraintWithTarget.NEVER
&& methodVisibility != ConstraintWithTarget.NEVER;
}
private DexMethod insertBridgeForVisibilityIfNeeded(
DexMethod method,
DexClassAndMethod target,
DexClass originalClass,
TriConsumer<DexProgramClass, DexMethod, DexClassAndMethod> bridges) {
// 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(target.getHolderType().getPackageDescriptor())) {
DexProgramClass bridgeHolder =
findHolderForVisibilityBridge(originalClass, target.getHolder(), packageDescriptor);
assert bridgeHolder != null;
bridges.accept(bridgeHolder, method, target);
return target.getReference().withHolder(bridgeHolder.getType(), appView.dexItemFactory());
}
return target.getReference();
}
private DexProgramClass findHolderForVisibilityBridge(
DexClass originalClass, DexClass targetClass, String packageDescriptor) {
if (originalClass == targetClass || originalClass.isNotProgramClass()) {
return null;
}
DexProgramClass newHolder = null;
// Recurse through supertype chain.
if (appView.appInfo().isSubtype(originalClass.superType, targetClass.type)) {
DexClass superClass = appView.definitionFor(originalClass.superType);
newHolder = findHolderForVisibilityBridge(superClass, targetClass, packageDescriptor);
} else {
for (DexType iface : originalClass.interfaces.values) {
if (appView.appInfo().isSubtype(iface, targetClass.type)) {
DexClass interfaceClass = appView.definitionFor(iface);
newHolder = findHolderForVisibilityBridge(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 recordNonReboundFieldAccesses(ExecutorService executorService)
throws ExecutionException {
assert verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(executorService);
FieldAccessInfoCollection<?> fieldAccessInfoCollection =
appView.appInfo().getFieldAccessInfoCollection();
fieldAccessInfoCollection.forEach(lensBuilder::recordNonReboundFieldAccesses);
}
public MemberRebindingLens run(ExecutorService executorService) throws ExecutionException {
AppInfoWithLiveness appInfo = appView.appInfo();
computeMethodRebinding(appInfo.getMethodAccessInfoCollection());
recordNonReboundFieldAccesses(executorService);
appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
insertLibraryBridges();
return lensBuilder.build();
}
private void insertLibraryBridges() {
insertedLibraryBridges.forEach(
(method, resolvedMethod) -> {
assert !resolvedMethod.isProgramMethod();
DexProgramClass holder =
DexProgramClass.asProgramClassOrNull(appView.definitionFor(method.getHolderType()));
assert holder != null;
assert !holder.isInterface() || options.canUseDefaultAndStaticInterfaceMethods();
DexEncodedMethod resolvedDefinition = resolvedMethod.getDefinition();
holder.addMethod(
DexEncodedMethod.syntheticBuilder()
.setMethod(method)
.setApiLevelForDefinition(resolvedDefinition.getApiLevelForDefinition())
// Since we could not member rebind to this definition it is at least higher than
// min-api.
.setApiLevelForCode(ComputedApiLevel.unknown())
.setCode(
MemberRebindingBridgeCode.builder()
.setTarget(resolvedMethod.getDefinition().getReference())
.setInterface(resolvedMethod.getHolder().isInterface())
.build())
.setAccessFlags(MethodAccessFlags.builder().setPublic().setSynthetic().build())
.setClassFileVersion(resolvedDefinition.getClassFileVersion())
.setDeprecated(resolvedDefinition.deprecated)
.setIsLibraryMethodOverride(OptionalBool.TRUE)
.build());
});
}
private boolean verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(
ExecutorService executorService) throws ExecutionException {
Set<DexField> nonReboundFieldReferences = computeNonReboundFieldReferences(executorService);
FieldAccessInfoCollection<?> fieldAccessInfoCollection =
appView.appInfo().getFieldAccessInfoCollection();
fieldAccessInfoCollection.forEach(
info -> {
DexField reboundFieldReference = info.getField();
info.forEachIndirectAccess(
nonReboundFieldReference -> {
assert reboundFieldReference != nonReboundFieldReference;
assert reboundFieldReference
== appView
.appInfo()
.resolveField(nonReboundFieldReference)
.getResolvedFieldReference();
nonReboundFieldReferences.remove(nonReboundFieldReference);
});
});
assert nonReboundFieldReferences.isEmpty();
return true;
}
private Set<DexField> computeNonReboundFieldReferences(ExecutorService executorService)
throws ExecutionException {
Set<DexField> nonReboundFieldReferences = Sets.newConcurrentHashSet();
ThreadUtils.processItems(
appView.appInfo()::forEachMethod,
method -> {
if (method.getDefinition().hasCode()) {
method.registerCodeReferences(
new UseRegistry<ProgramMethod>(appView, method) {
@Override
public void registerInstanceFieldRead(DexField field) {
registerFieldReference(field);
}
@Override
public void registerInstanceFieldWrite(DexField field) {
registerFieldReference(field);
}
@Override
public void registerStaticFieldRead(DexField field) {
registerFieldReference(field);
}
@Override
public void registerStaticFieldWrite(DexField field) {
registerFieldReference(field);
}
private void registerFieldReference(DexField field) {
appView
.appInfo()
.resolveField(field)
.forEachSuccessfulFieldResolutionResult(
resolutionResult -> {
if (resolutionResult.getResolvedField().getReference() != field) {
nonReboundFieldReferences.add(field);
}
});
}
@Override
public void registerInitClass(DexType type) {
// Intentionally empty.
}
@Override
public void registerInvokeDirect(DexMethod method) {
// Intentionally empty.
}
@Override
public void registerInvokeInterface(DexMethod method) {
// Intentionally empty.
}
@Override
public void registerInvokeStatic(DexMethod method) {
// Intentionally empty.
}
@Override
public void registerInvokeSuper(DexMethod method) {
// Intentionally empty.
}
@Override
public void registerInvokeVirtual(DexMethod method) {
// Intentionally empty.
}
@Override
public void registerNewInstance(DexType type) {
// Intentionally empty.
}
@Override
public void registerInstanceOf(DexType type) {
// Intentionally empty.
}
@Override
public void registerTypeReference(DexType type) {
// Intentionally empty.
}
});
}
},
executorService);
return nonReboundFieldReferences;
}
}