blob: 464728d12d95ba627d06ae2bfe62908f3545fc22 [file] [log] [blame]
// 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.tracereferences;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.diagnostic.DefinitionContext;
import com.android.tools.r8.diagnostic.internal.DefinitionContextUtils;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassResolutionResult;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMember;
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.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.lens.FieldLookupResult;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedReference;
import com.android.tools.r8.tracereferences.internal.TracedClassImpl;
import com.android.tools.r8.tracereferences.internal.TracedFieldImpl;
import com.android.tools.r8.tracereferences.internal.TracedMethodImpl;
import com.android.tools.r8.utils.BooleanBox;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class Tracer {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final DiagnosticsHandler diagnostics;
private final Predicate<DexType> targetPredicate;
public Tracer(
AppView<? extends AppInfoWithClassHierarchy> appView,
DiagnosticsHandler diagnostics,
Predicate<DexType> targetPredicate) {
this.appView = appView;
this.diagnostics = diagnostics;
this.targetPredicate = targetPredicate;
}
public void run(TraceReferencesConsumer consumer) {
UseCollector useCollector = new UseCollector(appView, consumer, diagnostics, targetPredicate);
for (DexProgramClass clazz : appView.appInfo().classes()) {
DefinitionContext classContext = DefinitionContextUtils.create(clazz);
clazz.forEachImmediateSupertype(
supertype -> useCollector.registerSuperType(clazz, supertype, classContext));
clazz.forEachProgramField(useCollector::registerField);
clazz.forEachProgramMethod(
method -> {
useCollector.registerMethod(method);
useCollector.traceCode(method);
});
// This iterates all annotations on the class, including on its methods and fields.
clazz
.annotations()
.forEach(
dexAnnotation -> useCollector.registerAnnotation(dexAnnotation, clazz, classContext));
}
consumer.finished(diagnostics);
}
// The graph lens is intentionally only made accessible to the MethodUseCollector, since the
// graph lens should only be applied to the code.
static class UseCollector {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final DexItemFactory factory;
private final TraceReferencesConsumer consumer;
private final DiagnosticsHandler diagnostics;
private final Predicate<DexType> targetPredicate;
private final Set<ClassReference> missingClasses = new HashSet<>();
private final Set<FieldReference> missingFields = new HashSet<>();
private final Set<MethodReference> missingMethods = new HashSet<>();
public final DexString dalvikAnnotationCodegenPrefix;
UseCollector(
AppView<? extends AppInfoWithClassHierarchy> appView,
TraceReferencesConsumer consumer,
DiagnosticsHandler diagnostics,
Predicate<DexType> targetPredicate) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.consumer = consumer;
this.diagnostics = diagnostics;
this.targetPredicate = targetPredicate;
this.dalvikAnnotationCodegenPrefix = factory.createString("Ldalvik/annotation/codegen/");
}
AppView<? extends AppInfoWithClassHierarchy> appView() {
return appView;
}
AppInfoWithClassHierarchy appInfo() {
return appView.appInfo();
}
GraphLens graphLens() {
return appView.graphLens();
}
private boolean isTargetType(DexType type) {
return targetPredicate.test(type);
}
private void addType(DexType type, DefinitionContext referencedFrom) {
if (type.isArrayType()) {
addType(type.toBaseType(factory), referencedFrom);
return;
}
if (type.isPrimitiveType() || type.isVoidType()) {
return;
}
assert type.isClassType();
addClassType(type, referencedFrom);
}
private void addTypes(DexTypeList types, DefinitionContext referencedFrom) {
for (DexType type : types) {
addType(type, referencedFrom);
}
}
private void addClassType(
DexType type,
DefinitionContext referencedFrom,
Consumer<DexClass> resolvedClassesConsumer) {
assert type.isClassType();
ClassResolutionResult result =
appView.contextIndependentDefinitionForWithResolutionResult(type);
if (result.hasClassResolutionResult()) {
result.forEachClassResolutionResult(resolvedClassesConsumer);
} else {
TracedClassImpl tracedClass = new TracedClassImpl(type, referencedFrom);
collectMissingClass(tracedClass);
consumer.acceptType(tracedClass, diagnostics);
}
}
private void addClassType(DexType type, DefinitionContext referencedFrom) {
addClassType(type, referencedFrom, clazz -> addClass(clazz, referencedFrom));
}
private void addClass(DexClass clazz, DefinitionContext referencedFrom) {
if (isTargetType(clazz.getType())) {
TracedClassImpl tracedClass = new TracedClassImpl(clazz, referencedFrom);
consumer.acceptType(tracedClass, diagnostics);
if (clazz.getAccessFlags().isPackagePrivateOrProtected()) {
consumer.acceptPackage(
Reference.packageFromString(clazz.getType().getPackageName()), diagnostics);
}
}
}
private void handleMemberResolution(
DexMember<?, ?> reference,
DexClassAndMember<?, ?> member,
DexProgramClass context,
DefinitionContext referencedFrom) {
DexClass holder = member.getHolder();
assert isTargetType(holder.getType());
if (member.getHolderType().isNotIdenticalTo(reference.getHolderType())) {
TracedClassImpl tracedClass = new TracedClassImpl(holder, referencedFrom);
consumer.acceptType(tracedClass, diagnostics);
}
ensurePackageAccessToMember(member, context);
}
private void ensurePackageAccessToMember(
DexClassAndMember<?, ?> member, DexProgramClass context) {
if (member.getAccessFlags().isPackagePrivateOrProtected()) {
if (member.getAccessFlags().isPackagePrivate()
|| !appInfo().isSubtype(context, member.getHolder())) {
consumer.acceptPackage(
Reference.packageFromString(member.getHolderType().getPackageName()), diagnostics);
}
}
}
private void addSuperMethodFromTarget(
DexClassAndMethod method, ProgramMethod context, DefinitionContext referencedFrom) {
assert !method.isProgramMethod();
assert isTargetType(method.getHolderType());
// There should be no need to register the types referenced from the method signature:
// - The return type and the parameter types are registered when visiting the source method
// that overrides this target method,
// - The holder type is registered from visiting the extends/implements clause of the sub
// class.
TracedMethodImpl tracedMethod = new TracedMethodImpl(method.getDefinition(), referencedFrom);
consumer.acceptMethod(tracedMethod, diagnostics);
ensurePackageAccessToMember(method, context.getHolder());
}
private <R, T extends TracedReference<R, ?>> void collectMissing(
T tracedReference, Set<R> missingCollection) {
if (tracedReference.isMissingDefinition()) {
missingCollection.add(tracedReference.getReference());
}
}
private void collectMissingClass(TracedClassImpl tracedClass) {
assert tracedClass.isMissingDefinition();
collectMissing(tracedClass, missingClasses);
}
private void collectMissingField(TracedFieldImpl tracedField) {
assert tracedField.isMissingDefinition();
collectMissing(tracedField, missingFields);
}
private void collectMissingMethod(TracedMethodImpl tracedMethod) {
assert tracedMethod.isMissingDefinition();
collectMissing(tracedMethod, missingMethods);
}
private void registerField(ProgramField field) {
DefinitionContext referencedFrom = DefinitionContextUtils.create(field);
addType(field.getType(), referencedFrom);
field
.getAnnotations()
.forEach(
dexAnnotation ->
registerAnnotation(dexAnnotation, field.getHolder(), referencedFrom));
}
private void registerMethod(ProgramMethod method) {
DefinitionContext referencedFrom = DefinitionContextUtils.create(method);
addTypes(method.getParameters(), referencedFrom);
addType(method.getReturnType(), referencedFrom);
method
.getAnnotations()
.forEach(
dexAnnotation ->
registerAnnotation(dexAnnotation, method.getHolder(), referencedFrom));
method
.getParameterAnnotations()
.forEachAnnotation(
dexAnnotation ->
registerAnnotation(dexAnnotation, method.getHolder(), referencedFrom));
}
private void traceCode(ProgramMethod method) {
method.registerCodeReferences(new MethodUseCollector(method));
}
private void registerSuperType(
DexProgramClass clazz, DexType superType, DefinitionContext referencedFrom) {
addType(superType, referencedFrom);
// If clazz overrides any methods in superType, we should keep those as well.
clazz.forEachProgramVirtualMethod(
method -> {
DexClassAndMethod resolvedMethod =
appInfo()
.resolveMethodOn(
superType,
method.getReference(),
superType.isNotIdenticalTo(clazz.getSuperType()))
.getResolutionPair();
if (resolvedMethod != null
&& !resolvedMethod.isProgramMethod()
&& isTargetType(resolvedMethod.getHolderType())) {
addSuperMethodFromTarget(resolvedMethod, method, referencedFrom);
}
});
}
private void registerAnnotation(
DexAnnotation annotation, DexProgramClass context, DefinitionContext referencedFrom) {
DexType type = annotation.getAnnotationType();
assert type.isClassType();
if (type.isIdenticalTo(factory.annotationMethodParameters)
|| type.isIdenticalTo(factory.annotationReachabilitySensitive)
|| type.getDescriptor().startsWith(factory.dalvikAnnotationOptimizationPrefix)
|| type.getDescriptor().startsWith(dalvikAnnotationCodegenPrefix)) {
// The remaining system annotations
// dalvik.annotation.EnclosingClass
// dalvik.annotation.EnclosingMethod
// dalvik.annotation.InnerClass
// dalvik.annotation.MemberClasses
// dalvik.annotation.Signature
// dalvik.annotation.NestHost (*)
// dalvik.annotation.NestMembers (*)
// dalvik.annotation.Record (*)
// dalvik.annotation.PermittedSubclasses (*)
// are not added as annotations in the DexParser.
//
// (*) Not officially supported and documented.
return;
}
if (type.isIdenticalTo(factory.annotationDefault)) {
assert referencedFrom.isClassContext();
annotation
.getAnnotation()
.forEachElement(
element -> {
assert element.getValue().isDexValueAnnotation();
registerEncodedAnnotation(
element.getValue().asDexValueAnnotation().getValue(),
context,
referencedFrom);
});
return;
}
if (type.isIdenticalTo(factory.annotationSourceDebugExtension)) {
assert annotation.getAnnotation().getNumberOfElements() == 1;
assert annotation.getAnnotation().getElement(0).getValue().isDexValueString();
return;
}
if (type.isIdenticalTo(factory.annotationThrows)) {
assert referencedFrom.isMethodContext();
registerDexValue(
annotation.annotation.elements[0].value.asDexValueArray(), context, referencedFrom);
return;
}
assert !type.getDescriptor().startsWith(factory.dalvikAnnotationPrefix)
: "Unexpected annotation with prefix "
+ factory.dalvikAnnotationPrefix
+ ": "
+ type.getDescriptor();
registerEncodedAnnotation(annotation.getAnnotation(), context, referencedFrom);
}
void registerEncodedAnnotation(
DexEncodedAnnotation annotation,
DexProgramClass context,
DefinitionContext referencedFrom) {
addClassType(
annotation.getType(),
referencedFrom,
resolvedClass -> {
addClass(resolvedClass, referencedFrom);
// For annotations in target handle annotation "methods" used to set values.
annotation.forEachElement(
element -> {
for (DexEncodedMethod method : resolvedClass.methods()) {
if (method.getName().isIdenticalTo(element.name)) {
TracedMethodImpl tracedMethod = new TracedMethodImpl(method, referencedFrom);
consumer.acceptMethod(tracedMethod, diagnostics);
}
}
// Handle the argument values passed to the annotation "method".
registerDexValue(element.getValue(), context, referencedFrom);
});
});
}
private void registerDexValue(
DexValue value, DexProgramClass context, DefinitionContext referencedFrom) {
if (value.isDexValueType()) {
addType(value.asDexValueType().getValue(), referencedFrom);
} else if (value.isDexValueEnum()) {
DexField field = value.asDexValueEnum().value;
handleRewrittenFieldReference(field, context, referencedFrom);
} else if (value.isDexValueArray()) {
for (DexValue elementValue : value.asDexValueArray().getValues()) {
registerDexValue(elementValue, context, referencedFrom);
}
}
}
private void handleRewrittenFieldReference(
DexField field, DexProgramClass context, DefinitionContext referencedFrom) {
addType(field.getHolderType(), referencedFrom);
addType(field.getType(), referencedFrom);
FieldResolutionResult resolutionResult = appInfo().resolveField(field);
if (resolutionResult.hasSuccessfulResolutionResult()) {
resolutionResult.forEachSuccessfulFieldResolutionResult(
singleResolutionResult -> {
DexClassAndField resolvedField = singleResolutionResult.getResolutionPair();
if (isTargetType(resolvedField.getHolderType())) {
handleMemberResolution(field, resolvedField, context, referencedFrom);
TracedFieldImpl tracedField = new TracedFieldImpl(resolvedField, referencedFrom);
consumer.acceptField(tracedField, diagnostics);
}
});
} else {
TracedFieldImpl tracedField = new TracedFieldImpl(field, referencedFrom);
collectMissingField(tracedField);
consumer.acceptField(tracedField, diagnostics);
}
}
class MethodUseCollector extends UseRegistry<ProgramMethod> {
private final DefinitionContext referencedFrom;
public MethodUseCollector(ProgramMethod context) {
super(appView(), context);
this.referencedFrom = DefinitionContextUtils.create(context);
}
// Method references.
@Override
public void registerInvokeDirect(DexMethod method) {
MethodLookupResult lookupResult = graphLens().lookupInvokeDirect(method, getContext());
assert lookupResult.getType().isDirect();
DexMethod rewrittenMethod = lookupResult.getReference();
if (getContext().getHolder().originatesFromDexResource()) {
handleRewrittenMethodResolution(
rewrittenMethod,
appInfo().unsafeResolveMethodDueToDexFormat(rewrittenMethod),
SingleResolutionResult::getResolutionPair);
} else {
BooleanBox seenMethod = new BooleanBox();
appView
.contextIndependentDefinitionForWithResolutionResult(rewrittenMethod.getHolderType())
.forEachClassResolutionResult(
holder -> {
DexClassAndMethod target = rewrittenMethod.lookupMemberOnClass(holder);
if (target != null) {
handleRewrittenMethodReference(rewrittenMethod, target);
seenMethod.set();
}
});
if (seenMethod.isFalse()) {
handleRewrittenMethodReference(rewrittenMethod, null);
}
}
}
@Override
public void registerInvokeInterface(DexMethod method) {
MethodLookupResult lookupResult = graphLens().lookupInvokeInterface(method, getContext());
assert lookupResult.getType().isInterface();
handleInvokeWithDynamicDispatch(lookupResult);
}
@Override
public void registerInvokeStatic(DexMethod method) {
MethodLookupResult lookupResult = graphLens().lookupInvokeStatic(method, getContext());
assert lookupResult.getType().isStatic();
DexMethod rewrittenMethod = lookupResult.getReference();
handleRewrittenMethodResolution(
rewrittenMethod,
appInfo().unsafeResolveMethodDueToDexFormat(rewrittenMethod),
SingleResolutionResult::getResolutionPair);
}
@Override
public void registerInvokeSuper(DexMethod method) {
MethodLookupResult lookupResult = graphLens().lookupInvokeSuper(method, getContext());
assert lookupResult.getType().isSuper();
DexMethod rewrittenMethod = lookupResult.getReference();
handleRewrittenMethodResolution(
method,
appInfo().unsafeResolveMethodDueToDexFormat(rewrittenMethod),
result -> result.lookupInvokeSuperTarget(getContext().getHolder(), appView, appInfo()));
}
@Override
public void registerInvokeVirtual(DexMethod method) {
MethodLookupResult lookupResult = graphLens().lookupInvokeVirtual(method, getContext());
assert lookupResult.getType().isVirtual();
handleInvokeWithDynamicDispatch(lookupResult);
}
private void handleInvokeWithDynamicDispatch(MethodLookupResult lookupResult) {
DexMethod method = lookupResult.getReference();
if (method.getHolderType().isArrayType()) {
assert lookupResult.getType().isVirtual();
addType(method.getHolderType(), referencedFrom);
return;
}
assert lookupResult.getType().isInterface() || lookupResult.getType().isVirtual();
handleRewrittenMethodResolution(
method,
lookupResult.getType().isInterface()
? appInfo().resolveMethodOnInterfaceHolder(method)
: appInfo().resolveMethodOnClassHolder(method),
SingleResolutionResult::getResolutionPair);
}
private void handleRewrittenMethodResolution(
DexMethod method,
MethodResolutionResult resolutionResult,
Function<SingleResolutionResult<?>, DexClassAndMethod> getResult) {
BooleanBox seenSingleResult = new BooleanBox();
resolutionResult.forEachMethodResolutionResult(
result -> {
if (result.isFailedResolution()) {
result
.asFailedResolution()
.forEachFailureDependency(
type -> addType(type, referencedFrom),
methodCausingFailure ->
handleRewrittenMethodReference(
method, methodCausingFailure.asDexClassAndMethod(appView)));
return;
}
seenSingleResult.set();
handleRewrittenMethodReference(method, getResult.apply(result.asSingleResolution()));
});
if (seenSingleResult.isFalse()) {
resolutionResult.forEachMethodResolutionResult(
failingResult -> {
assert failingResult.isFailedResolution();
if (!failingResult.asFailedResolution().hasMethodsCausingError()) {
handleRewrittenMethodReference(method, null);
}
});
}
}
private void handleRewrittenMethodReference(
DexMethod method, DexClassAndMethod resolvedMethod) {
addType(method.getHolderType(), referencedFrom);
addTypes(method.getParameters(), referencedFrom);
addType(method.getReturnType(), referencedFrom);
if (resolvedMethod != null) {
DexEncodedMethod definition = resolvedMethod.getDefinition();
assert resolvedMethod.getReference().match(method)
|| resolvedMethod.getHolder().isSignaturePolymorphicMethod(definition, factory);
if (isTargetType(resolvedMethod.getHolderType())) {
handleMemberResolution(
method, resolvedMethod, getContext().getHolder(), referencedFrom);
TracedMethodImpl tracedMethod = new TracedMethodImpl(definition, referencedFrom);
consumer.acceptMethod(tracedMethod, diagnostics);
}
} else {
TracedMethodImpl tracedMethod = new TracedMethodImpl(method, referencedFrom);
collectMissingMethod(tracedMethod);
consumer.acceptMethod(tracedMethod, diagnostics);
}
}
// Field references.
@Override
public void registerInitClass(DexType clazz) {
DexType rewrittenClass = graphLens().lookupType(clazz);
DexField clinitField = appView.initClassLens().getInitClassField(rewrittenClass);
handleRewrittenFieldReference(clinitField, getContext().getHolder(), referencedFrom);
}
@Override
public void registerInstanceFieldRead(DexField field) {
handleFieldAccess(field);
}
@Override
public void registerInstanceFieldWrite(DexField field) {
handleFieldAccess(field);
}
@Override
public void registerStaticFieldRead(DexField field) {
handleFieldAccess(field);
}
@Override
public void registerStaticFieldWrite(DexField field) {
handleFieldAccess(field);
}
private void handleFieldAccess(DexField field) {
FieldLookupResult lookupResult = graphLens().lookupFieldResult(field);
handleRewrittenFieldReference(
lookupResult.getReference(), getContext().getHolder(), referencedFrom);
}
// Type references.
@Override
public void registerTypeReference(DexType type) {
addType(graphLens().lookupType(type), referencedFrom);
}
// Call sites.
@Override
public void registerCallSite(DexCallSite callSite) {
super.registerCallSite(callSite);
// For lambdas that implement an interface, also keep the interface method by simulating an
// invoke to it from the current context.
LambdaDescriptor descriptor =
LambdaDescriptor.tryInfer(callSite, appView(), appInfo(), getContext());
if (descriptor != null) {
for (DexType interfaceType : descriptor.interfaces) {
ClassResolutionResult classResolutionResult =
appView.contextIndependentDefinitionForWithResolutionResult(interfaceType);
if (classResolutionResult.hasClassResolutionResult()) {
classResolutionResult.forEachClassResolutionResult(
interfaceDefinition -> {
DexEncodedMethod mainMethod =
interfaceDefinition.lookupMethod(descriptor.getMainMethod());
if (mainMethod != null) {
registerInvokeInterface(mainMethod.getReference());
}
for (DexProto bridgeProto : descriptor.bridges) {
DexEncodedMethod bridgeMethod =
interfaceDefinition.lookupMethod(bridgeProto, descriptor.getName());
if (bridgeMethod != null) {
registerInvokeInterface(bridgeMethod.getReference());
}
}
});
} else {
TracedClassImpl tracedClass = new TracedClassImpl(interfaceType, referencedFrom);
collectMissingClass(tracedClass);
consumer.acceptType(tracedClass, diagnostics);
}
}
}
}
}
}
}